DEV/WebProgramming

[React] useReducer, useContext

9thxg 2022. 5. 9. 22:41

인프런, 한입 크기로 잘라 먹는 리액트(React.js) : 기초부터 실전까지의 내용 중 useReducer, useContext를 사용한 프로젝트 정리 대해서 수강하고 내용을 정리한다.

 

목차
  1. useReducer - 복잡한 상태변화 로직 분리
  2. useContext - 컴포넌트 트리에 데이터 공급

 


useReducer - 복잡한 상태변화 로직 분리

 

simple-diary 프로젝트에서 App 컴포넌트에는 많은 상태변화 함수가 존재한다.(ex. getData, onCreate, onRemove 등)

복잡하고 긴 상태변화 로직을 컴포넌트 밖에 정의하여 컴포넌트 코드를 좀 더 단순히 만들 수 있다.

// reducer 함수
//  switch문을 통해 action.type을 확인하여 상태변화 처리
// return 된 값으로 state에 반영된다
const reducer = (state, action) => {
	switch(action.type){
    	case '1':{
        	return 1
		}
        case '10':{
        	return 10
		}
        case '100':{
        	return 100
		}
        default:
        	return state
	}
}


function App(){
    // 정의
    const [state, dispatch] = useReducer(reducer, 1);
    // dispatch = 상태변화 함수
    // useReducer 첫 번째 인자 reducer = 상태변화를 처리할 함수
    // useReducer 두 번째 인자 = state의 초기값

	...
    
    // 상태변화 함수 사용
    return(
    	<div OnClick={() => dispatch({ type: 1 })></div>
    )
    
    ...
}

 

프로젝트에선 getData, onCreate, onRemove, onEdit을 처리하기 위해 다음과 같이 변경하여 상태변화에 대한 함수를 보기 쉽게 만들었다.

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]
    }
    case 'REMOVE':{
      return state.filter((it) => it.id !== action.targetId)
    }
    case 'EDIT':{
      return state.map((it) => it.id === action.targetId ? {...it, content:action.newContent} : it)
    }
    default:
      return state;
  }
}

function App() {
  const [data, dispatch] = useReducer(reducer, [])

  const dataId = useRef(0);

  const getData = async() => {
    const res = await fetch('https://jsonplaceholder.typicode.com/comments')
    .then(res => res.json());

  const initData = res.slice(0, 20).map((it) => {
	return{
		author : it.email,
        content : it.body,
        emotion : Math.floor(Math.random() * 5) + 1,
        created_date : new Date().getTime(),
        id : dataId.current++
      }
    })
    dispatch({type:"INIT", data:initData})
  }
  
  const onCreate = useCallback((author, content, emotion) => {

    dispatch({type:'CREATE', data:{author, content, emotion, id:dataId.current}})

    dataId.current += 1;
  }, []);

  const onRemove = useCallback((targetId) => {
    dispatch({type:"REMOVE", targetId})
    console.log(`${targetId}가 삭제되었습니다.`);
  }, []);

  const onEdit = useCallback((targetId, newContent) => {
    dispatch({type:"EDIT", targetId, newContent})
  }, [])
  
}

 

useContext - 컴포넌트 트리에 데이터 공급

 

simple-diary 프로젝트에는 onRemove(), onEdit() 두 함수가 DiaryList 컴포넌트를 단순히 거쳐가는 props로 존재한다. 이를 props drilling이라 부른다. 다음과 같은 문제를 해결하기 위해 모든 데이터를 가지고 있는 Provider가 존재하여 자식 컴포넌트에 직접 데이터를 전달할 수 있다. 

어떤 데이터를 다루고 있는 컴포넌트들의 집합을 문맥(Context)라 부른다. 이를 통해 Provider기준으로 문맥을 생성하여 데이터를 공급할 수 있도록 한다. 그러므로 Provider외부, 문맥 외부 컴포넌트에는 데이터를 전달하지 못한다.

// 데이터를 공급할 상위 컴포넌트(App)

// context 생성
// export 해주어야 컴포넌트에서 사용가능
export const MyContext = React.createContext(defaultValue)

// Context Provider를 통한 데이터 공급
function App() {
	...
    
    return(
    	<MyContext.Provider value={전달할 데이터}>
        	~ Context안에 위치할 컴포넌트들
        </MyContext.Provider>
    )
}


// export default는 import시 이름을 변형하여 사용이 가능하지만 export는 비구조화로 import하여 사용하기 때문에 이름변경 불가

 

자식 컴포넌트에서 Context를 사용하기 위해서 useContext 훅을 사용한다.

import { MyContext } from "./App";

const dataList = useContext(MyContext)

 

단 Provider에 state와 상태변화함수를 함께 전달하게 되면 Provider 또한 컴포넌트라 state변화가 생기면 리렌더하게 됨 따라서 한번 호출된 이후 변경이 필요없는 함수들은 따로 Context를 만들어 사용하는 것이 좋다.

이때, Provider의 value 값을 주기 위해 함수를 묶어야하는데 객체로만 묵게 되면 App 컴포넌트 리렌더시 같이 리렌더 되어 버리기 때문에 useMemo를 사용하여 묶어준다.

자식 컴포넌트에서 해당 함수를 사용하기 위해서는 비구조화 할당으로 useContext를 통해 함수를 할당 받아 사용한다.

// App.js
// Context는 여러개를 만들어 사용할 수 있음
export const DiaryDispatchContext = React.createContext()

function App() {
  ...
  
  // useMemo를 사용하여 재호출 방지
  const memoizedDispatches = useMemo(() => {
    return {onCreate,onRemove,onEdit}
  }, [])
  
  ...
  
   return (
    <DiaryStateContext.Provider value={data}>
      <DiaryDispatchContext.Provider value={memoizedDispatches}>
        <div className="App">
          <DiaryEditor/>
          <DiaryList/>
        </div>        
      </DiaryDispatchContext.Provider>
    </DiaryStateContext.Provider>
  );
}

// DiaryItem.js
// 함수를 비구조화 할당으로 받음
    const {onRemove, onEdit} = useContext(DiaryDispatchContext)

 

'DEV > WebProgramming' 카테고리의 다른 글

[React] 프로젝트 빌드 및 배포  (0) 2022.07.12
[React] Local Storage  (0) 2022.07.08
[VSCode] 웹 프로그래밍시 유용한 확장 프로그램(22.05.28)  (0) 2022.05.29
[React] Page Routing  (0) 2022.05.17
[React] 최적화  (0) 2022.04.25