Portfolio, Project/Project(Programming)

React - useReducer / Context

잇(IT) 2023. 9. 21. 11:29
- useReducer

- useReducer는 컴포넌트에서 상태변화 로직을 분리하기 위해 사용한다.

 

const [state, dispatch] = useReducer(reducer, initialState);

-----------------------------------

function reducer(state, action) {
  switch (action.type) {
    case 'INCREMENT':
      return { count: state.count + 1 };
    case 'DECREMENT':
      return { count: state.count - 1 };
    default:
      return state;
  }
}

-------------------------------------

dispatch({ type: 'INCREMENT' }); // 상태를 증가시키는 액션 디스패치
dispatch({ type: 'DECREMENT' }); // 상태를 감소시키는 액션 디스패치

- 기본적으로 useReducer는 위와 같이 사용된다.

1. reducer : 상태를 어떻게 업데이트할지를 결정하는 함수이다. 이 함수는 현재 상태('state')와 액션('action')을 받아서 새로운 상태를 반환한다.

2. initialState : 상태의 초기값을 나타낸다. useReducer를 호출 할 때 이 값을 제공한다.

3. useReducer : 이 두 가지 인자를 받아 현재 상태('state')와 액션을 디스패치하는 함수 ('dispatch')를 반환한다.

4. 상태를 업데이트하기 위해 'dispatch' 함수를 사용한다. 'dispatch' 함수는 액션을 인자로 받고, 이 액션은 'reducer' 함수에 전달되어 상태를 업데이트하게 된다.

 

// const [data, setData] = useState([]);

  const [data, dispatch] = useReducer(reducer, []);

- 기존의 useState 함수를 아래와 같이 useReducer 함수로 변경 할 수 있다.

- data의 초기화 값은 빈 배열로 초기화 된다.

 

const onCreate = useCallback((author, content, emotion) => {

    const created_date = new Date().getTime();
    const newItem = {
      author,
      content,
      emotion,
      created_date,
      id: dataId.current,
    };
    
    dataId.current += 1;
    setData((data) => [newItem, ...data]);
    함수형 업데이트
  }, []);

- 기존의 useState를 사용할 때 data 변수도 외부에 종속적으로 동작하였다.

 

const onCreate = useCallback((author, content, emotion) => {

    dispatch({
      type: "CREATE",
      data: { author, content, emotion, id: dataId.current },
    });
    
    dataId.current += 1;
  }, []);

- useReducer를 사용하게 되면, 위와 같이 외부에 종속적인 데이터 없이, 필요한 값을 외부에 전달하여 원하는 상태를 반환 받을 수 있게 된다.

- dispatch 안의 작성된 type에 의해 reducer 함수에서 switch에서 해당 type과 동일한 ("CREATE")를 찾아 상태 변화를 시킨다.

- 위와 같이 상태 변화를 위해 추가적으로 필요한 데이터를 전달 할 수 있다. dispatch에 의해 전달된 값들은 action 객체에 속하게 되고, action.type, action.data와 같이 호출하여 사용할 수 있다.

 

const reducer = (state, action) => {
  switch (action.type) {
    case `INIT`: {
      return action.data;
    }
    case `CREATE`: {
      const created_date = new Date().getTime();
      const newItem = {
        ...action.data,
        created_date,
      };
      return [newItem, ...state];
    }

- 위의 onCreate에서 전달된 dispatch에 의해 type이 CREATE인 case를 찾아 실행하게 된다.

- onCreate에서 전달된 dispatch의 data 값이 action.data를 통해 호출된 것을 확인 할 수 있다.

- 결과적으로 return으로 반환된 값이 state의 값을 변화시키고 최종적으로 useReducer의 data의 값을 변화시키게 된다.


- Context

- Context란 React 애플리케이션에서 전역 상태를 관리하고 데이터를 컴포넌트 트리 내에서 공유하는 방법을 제공하는 기능이다. Context를 사용하면 상위 컴포넌트에서 하위 컴포넌트로 데이터를 명시적으로 전달하지 않고도 데이터를 공유할 수 있으며, 컴포넌트 간의 상태 및 설정을 전역으로 관리할 수 있다.

 

- 사용 방법

1. React.createContext()를 통해 Context 객체를 생성한다. (export로 생성해야 한다. 외부에 전달하는 것이기 때문이다.)

2. 부모 컴포넌트에서 "객체명".Provider value={전달할 prop} 를 통해 하위 컴포넌트로 전파시킨다.

3. 상위 컴포넌트로부터 전달 받기위해선 const {전달받은 prop 중 사용할 prop} = useContext("Context 객체명"); 을 통해 상위 컴포넌트로부터 prop을 전달 받아 사용할 수 있다.

 

- 아래 코드들을 통해 사용 방법에 대해 알아보겠다.

 


- App.js

const [data, dispatch] = useReducer(reducer, []);

const onCreate = useCallback((author, content, emotion) => {
    dispatch({
      type: "CREATE",
      data: { author, content, emotion, id: dataId.current },
    });
    
    //onCreate처럼 컴포넌트가 onEdit, onRemove가 있다고 가정한다.
    
    ................
    
export const DiaryStateContext = React.createContext();

export const DiaryDispatchContext = React.createContext();

// Context 객체를 생성해준다.
// 두개를 나눠서 사용하는 이유는 아래 Provider로 전달할 때 결국 Context도 prop이기 때문에
// 상태가 변경되면 계속해서 리렌더링 되기 떄문이다.
.................
    
    const memoizedDispatches = useMemo(() => {
    return { onCreate, onRemove, onEdit };
  }, []);
    
    .............
    
    return (
    <DiaryStateContext.Provider value={data}>
      <DiaryDispatchContext.Provider value={memoizedDispatches}>
      ..............

- Context 객체를 2개로 나눈 이유는 state 상태를 나타내는 data와, 상태 변화 함수를 나누기 위해서이다.

- memoizedDispatches는 useMemo를 통해 onCreate, onRemove, onEdit 함수가 리렌더링 될 때 마다 재 생성되는 것을 방지한다.

 

    return (
    <DiaryStateContext.Provider value={data}>
      <DiaryDispatchContext.Provider value={memoizedDispatches}>

- 위 코드를 통해 하위 컴포넌트 어디든 value={data}, valu={memoizedDispatches}를 호출하여 사용할 수 있게 된다.

 

- DiaryEditor.js

...........

const DiaryEditor = () => {
  const { onCreate } = useContext(DiaryDispatchContext);
  
  ............
  
onCreate(state.author, state.content, state.emotion);
    alert("저장 성공");
    setState({
      author: "",
      content: "",
      emotion: "",
    });
  };

- useContext{}를 이용하여 부모 컴포넌트의 Provider 중 원하는 컴포넌트를 받아서 사용할 수 있다.

728x90