Redux를 비롯한 상태관리 라이브러리를 공부하면서 처음 Context-API를 접했다. Context-API는 Redux보다 개념이 직관적이고, 기존의 React 개발을 경험한 사람이라면 쉽게 쓸 수 있을 것이라고 생각한다. 하지만 개념이 잘 서있지 않았다면 해당 라이브러리를 쓰고자 할 때 다시 공부해야하는데, 그 시간을 허비하는 게 너무 짜증났다. 그래서 오늘, 이 곳에서 Context-API 개념은 마무리 딱 짓는다.
부연 설명하자면, 주로 쓰는 useState의 경우에는 부모 - 자식 컴포넌트 간 수직적으로 하나하나 props를 넘겨줘야 한다. 따라서 특정 컴포넌트의 깊이가 깊으면 props를 계속 내려다 줘야하므로 귀찮음이 있었다. Context-API는 간단하다. 그 props를 전역(global)로 관리하겠다는 것이다.
여기서 두 가지 개념만 알면 끝이다. Context(저장소), Provider(제공자)이다. 참고 예시는 실행되지 않을테니 참고만 하자!!
이는 Context-API에서 변수나 메서드를 전역적으로 관리하는 저장소의 역할을 한다. context와 관련된 두 가지 메서드만 기억하자
// TypeScipt로 작성한 프로젝트의 예시
const TodoListContext = createContext<ITodoListContext>({ // 초깃값 작성
todoList: [],
addTodoList: (todo:string):void => {},
removeTodoList: (index: number):void => {},
isShowInput : false
});
// useContext의 parameter로는 Context 저장소를 직접 넣어준다.
const {todoList, removeTodoList} = useContext<ITodoListContext>(TodoListContext)
Provider는 Context 저장소를 App.tsx or App.jsx에 제공하는 역할이다. 주로 Provider에서 실제로 사용할 state나 method를 구현해놓고 전역으로 관리한다.
// Provider는 Parameter로 children 즉, 프로젝트 전반의 컴포넌트를 받는다. 따라서 앞서 타입을 지정해준다.
type Props ={
children : JSX.Element | Array<JSX.Element>
}
// Provider
const TodoListContextProvider = ({children}:Props) =>{
// 실질적인 state, method 구축
const [todoList, setTodoList] = useState<Array<string>>(["강세훈", "kangsehoon"]);
const addTodoList = (todo:string) => {
const list = [...todoList, todo];
setTodoList(list);
}
const removeTodoList = (index:number) =>{
let list = [...todoList];
list.splice(index, 1);
setTodoList(list);
}
// ... ...
// retur부
return (
// Context 저장소에 value를 담아서 provider로 children에게 제공해준다.
<TodoListContext.Provider value= {{todoList, addTodoList, removeTodoList, isShowInput}}>
{children}
</TodoListContext.Provider>
)
}
export {TodoListContextProvider, TodoListContext}
Context-API를 사용할 때 이러한 흐름으로 머리에 넣어놓으면 좋을 거 같아 정리했다. 해당 구현 순서는 TypeScript 환경 내에서 Context-API를 어떻게 구현할 지에 대한 고민이다. 잘 참고하셔서 도움되었으면 좋겠습니다.
- context(저장소) type 설정(@types - index.d.ts)
- context(저장소) 생성 : createContext
- provider(제공자) 함수 생성
3-1. parameter인 children type 생성 후, parameter 적용
3-2. 실제 state 및 method 구현
3-3. return부 구현- App.tsx에서 provider 적용
- 컴포넌트에서 context 호출 : useContext
이 코드들은 필요에 따라 분절하여 기입한 것으로, 따라친다고 프로젝트에서 돌아가지 않을 가능성이 높습니다. 그냥 눈으로 확인하시고 Context-API의 흐름을 가져가시는 게 더 도움될 것입니다.
interface ITodoListContext {
todoList: Array<string>;
addTodoList: (todo: string) => void;
removeTodoList: (index: number) => void;
isShowInput: boolean;
}
const TodoListContext = createContext<ITodoListContext>({
todoList: [],
addTodoList: (todo:string):void => {},
removeTodoList: (index: number):void => {},
isShowInput : false
});
전체적인 provider의 모습부터 보겠다.
type Props ={
children : JSX.Element | Array<JSX.Element>
}
const TodoListContextProvider = ({children}:Props) =>{
// 실질적인 state, method 구축
const [todoList, setTodoList] = useState<Array<string>>(["강세훈", "박규민"]);
const [isShowInput, setIsShowInput] = useState<boolean>(false);
const addTodoList = (todo:string) => {
const list = [...todoList, todo];
setTodoList(list);
AsyncStorage.setItem('todoList', JSON.stringify(list));
}
const removeTodoList = (index:number) =>{
let list = [...todoList];
list.splice(index, 1);
setTodoList(list);
AsyncStorage.setItem('todoList', JSON.stringify(list));
}
const initData = async() =>{
try {
const list = await AsyncStorage.getItem('todoList');
if(list != null){
setTodoList(JSON.parse(list))
}
} catch (error) {
throw new Error("fuck you")
}
}
useEffect(()=>{
initData()
},[])
return (
// Context 저장소에 value를 담아서 provider 제공해준다. children에게
<TodoListContext.Provider value= {{todoList, addTodoList, removeTodoList, isShowInput}}>
{children}
</TodoListContext.Provider>
)
}
export {TodoListContextProvider, TodoListContext}
provider 코드를 잘라 분석하는 것으로 새로운 코드는 아니다. 3-2, 3-3 동일
type Props ={
children : JSX.Element | Array<JSX.Element> // JSX.Element 또는 Array<JSX.Element>로 컴포넌트를 의미
}
const TodoListContextProvider = ({children}:Props) =>{... ...}
// 실질적인 state, method 구축
const [todoList, setTodoList] = useState<Array<string>>(["강세훈", "박규민"]);
const [isShowInput, setIsShowInput] = useState<boolean>(false);
const addTodoList = (todo:string) => {
const list = [...todoList, todo];
setTodoList(list);
AsyncStorage.setItem('todoList', JSON.stringify(list));
}
const removeTodoList = (index:number) =>{
let list = [...todoList];
list.splice(index, 1);
setTodoList(list);
AsyncStorage.setItem('todoList', JSON.stringify(list));
}
const initData = async() =>{
try {
const list = await AsyncStorage.getItem('todoList');
if(list != null){
setTodoList(JSON.parse(list))
}
} catch (error) {
throw new Error("fuck you")
}
}
useEffect(()=>{
initData()
},[])
return (
// Context 저장소에 value를 담아서 provider 제공해준다. children에게
<TodoListContext.Provider value= {{todoList, addTodoList, removeTodoList, isShowInput}}>
{children}
</TodoListContext.Provider>
)
export {TodoListContextProvider, TodoListContext}
TodoListContextProvider는 이후 App.tsx에 import되어야 하기에 export된다.
TodoListContext는 이후 각 컴포넌트들이 useContext를 쓸 때, 그 파라미터로 context 저장소가 지정되어야 하기에 export 한다.
결론적으로, context 스크립트를 완성하면 provider와 context 저장소는 빼준다고 생각하면 된다.
4) App.tsx에서 provider 적용
import { TodoListContextProvider } from './Context/TodoListContext';
const App =() => {
return (
<TodoListContextProvider>
<TodoListView/>
</TodoListContextProvider>
);
};
provider만 import하고 전체 컴포넌트를 TodoListContextProvider 태그로 감싸준다. 이 코드만 한정지어 설명하자면, TodoListView 내에 해당 프로젝트의 모든 컴포넌트들을 포함하고 있기에 TodoListView 컴포넌트를 감싸준 것이다.
const {todoList, removeTodoList} = useContext<ITodoListContext>(TodoListContext)
각 컴포넌트 내에서 위에서 보는 것과 같이 context를 받아오면 된다.
이번 스크립트는 코드가 중요하다기 보다, 다음에 Context-API를 구현할 때 머리에서 일어날 흐름들에 대해 나의 식대로 적어보았다. 답은 분명 아닐 것이고 다 나은 방식은 있을 것이다. 하지만 여기서부터 시작해보자!