[React] 동적 Context API

✨ 강은비·2022년 1월 15일
0

React

목록 보기
20/36
post-thumbnail

Context 값을 동적으로 업데이트해야 하는 경우를 알아본다. 일정관리 애플리케이션을 예시로 들어 작성했다.

✨ component

  • TodoTemplate: 하얀 배경색의 전체 박스
    • TodoHead: 오늘 날짜와 앞으로 할 일의 개수를 보여줌.
    • TodoList: 여러 개의 TodoItem을 렌더링함.
      • TodoItem: 각 할 일에 대한 정보를 렌더링함. 토글 기능이 있는 체크 버튼과 삭제 기능이 있는 휴지통 아이콘이 있음. (마우스를 올려야 휴지통 아이콘이 보인다.)
    • TodoCreate: 새로운 할 일을 등록하는 컴포넌트이다. 플러스 아이콘이 있는 버튼을 누르면 할 일을 입력할 수 있는 폼이 나타난다. 버튼을 다시 누르면 폼이 사라진다.

✨ 전역 상태 관리

  • 전역적으로 봤을 때 필요한 state: 할 일에 대한 정보를 담은 배열
const todos = [
    {
        id: 1,
        text: "React 공부하기",
        done: false
    },
    {
        ...
    },
    ...
];
  • 여러 컴포넌트에서 todos가 필요로 함으로 최상위 컴포넌트인 Appstate로 담아 필요한 자식 컴포넌트에게 props로 전달한다.
  • 토글 기능, 삭제 기능, 생성 기능을 담은 setState 함수를 필요로 하는 함수도 App 컴포넌트에서 만들어 함수들을 이벤트 등록에 필요로 하는 자식 컴포넌트에게 props로 전달한다.

  • 이번 예시의 경우 컴포넌트의 구조가 복잡하지 않아 Appstateprops로 전달하는데 큰 무리가 없다.
  • 하지만, 컴포넌트의 구조가 깊어지고 복잡해질수록 여러 컴포넌트를 거쳐 state를 전달해야 한다.
  • 프로젝트의 규모가 커질수록 최상위 컴포넌트인 App에서 모든 상태 관리를 하기엔 무리가 있다.
  • 그래서 전역적으로 관리하거나 필요로 하는 상태가 있을 경우 상태 관리 라이브러리를 이용하거나 Context API를 활용한다.

💡 Context API를 이용한 상태 관리

// TodoContext.js
// import 생략

const initialTodos = [
    {
        id: 1,
        text: "React 공부하기",
        done: false
    }
];

function todoReducer(state, action){
    switch (action.type){
        case: "CREATE":
            return state.concat(action.todo);
        case "TOGGLE":
            return state.map(todo => 
                todo.id === action.id ? {...todo, done: !todo.done} : todo
            );
        case "REMOVE:
            return state.filter(todo => todo.id !== action.id);
        default:
            throw new Error(`Unhandled action type: ${action.type}`);
    }
}

const TodoContext = createContext({
    todos: [],
    dispatch: null,
    nextId: 2
});

function TodoProvider({children}){
    const [state, dispatch] = useReducer(todoReducer, initialTodos);
    const nextId = useRef(2);
    const value = {
        todos: state,
        dispatch,
        nextId
    };
    return (
        <TodoContext.provider value={value}>
            {children}
        </TodoContext.provider>
    );

}

export { TodoProvider };
export default TodoContext;
// App.js

function App() {
  return (
    <TodoProvider>
      <TodoTemplate>
        <TodoHead/>
        <TodoList/>
        <TodoCreate/>
      </TodoTemplate>
    </TodoProvider>
  );
}

  • createContext함수를 호출하여 TodoContext 생성
  • TodoProvider라는 함수형 컴포넌트는 TodoContext에 있는 값을 value props로 설정한 값으로 바꾸고 자식 컴포넌트에게 value props 값을 전달한다.
  • value prop에는 todos 상태값, todoReducer를 호출할 수 있는 dispatch함수, 새로운 항목을 추가할 때 사용할 nextId가 있다.
  • TodoContext에 있는 값을 각 컴포넌트에서 사용하기 위해 useContext를 사용할 것이다.

TodoHead

const TodoHead = () => {
   const { todos } = useContext(TodoContext);
   const undoneTasks = todos.filter(todo => !todo.done).length;
}

TodoCreate

const TodoCreate = () => {
    cosnt { dispatch, nextId } = useContext(TodoContext);
    const [ input, setIntput] = useState("");
    const onChange = useCallback((e) => {
        setInput(e.target.value);
    }, []);
    const onCreate = useCallback((e) => {
    	e.preventDefault();
        const todo = {
            id: nextId.current,
            text: input,
            done: false
        };
        dispatch({
            type: "CREATE",
            todo
        });
        setInput("");
        nextId.current += 1;
    }, [input]);
}

TodoList

const TodoList = () => {
    const { todos } = useContext(TodoContext);
    return (
        <ul>
            {todos.map(todo => 
               <TodoItem 
                key={todo.id}
                done={todo.done}
                text={todo.text}
                id={todo.id}/>
            }
        </ul>
    )
}

TodoItem

const TodoItem = ({id, text, done}) => {
    const { dispatch } = useContext(TodoContext);
    const onToggle = useCallback((id) => {
        dispatch({
            type: "TOGGLE",
            id
        });
    }, []);
    
    const onRemove = useCallback((id) => {
        dispatch({
            type: "REMOVE",
            id
        });
    }, []);

}

💡 Context API와 localStorage

  • 새로고침을 하면 todos에 있던 내용들이 초기화된다.
  • 새로고침을 해도 todos에 있던 내용들을 기억할 수 있도록 localStoragetodos 상태값을 저장한다.
  • todos 상태값이 업데이트될 때마다 업데이트된 todoslocalStorage에 저장하고 초기 렌더링 시 localStorage에 저장된 todos를 불러와 상태값으로 설정한다.
// TodoContext.js

const KEY = "todos";

function TodoProvider({children}){
    const [state, dispatch] = useReducer(todoReducer, [], () => {
        const todos = JSON.parse(localStorage.getItem(KEY));
        return todos ? todos : [];
    });
    useEffect(() => {
    	localStroage.setItem(KEY, JSON.stringfy(state));
    }, [state]);
    const nextId = useRef(1);
    const value = {
        todos: state,
        dispatch,
        nextId
    };
    return (
        <TodoProvider value={value}>
            {children}
        </TodoProvider>
    );

}
  • localStorage API 이용
    • localStorage.getItem(KEY)
    • localStorage.setItem(KEY, item)
  • useEffect를 이용하여 초기 렌더링한 이후와 상태값이 업데이트 된 이후에 상태값을 localStorage에 저장하도록 했다.
  • useReducer의 두 번째 인자로는 빈 배열을 전달하고, 세 번째 인자로 초기 상태값을 설정하는 함수를 전달했다.

참고: 벨로퍼트와 함께하는 모던 리액트

0개의 댓글