Programming/JavaScript

[React ] Redux와 useReducer의 차이, Context API 사용하기

 

redux 사용전 컴포넌트의 상태 변화의 전달 과정
redux를 사용 후 컴포넌트 외부에서 상태관리 과정

Redux를 사용하는 이유

 

리액트 프로젝트의 경우 대부분의 작업시 부모 컴포넌트를 통해 하위 컴포넌트의 데이터를 업데이트 한다.

(데이터의 연관이 있는 컴포넌트끼리 ref를 사용하여 전달할 수 있으나 코드가 꼬이는 문제로 인해 지양한다)

컴포넌트의 갯수가 적을때는 문제가 되지 않지만, 점점 늘어날수록 유지보수의 어려움이 발생한다.

예를 들어 변수명의 변경을 하면 연관된 컴포넌트 파일 모두에 수정을 거쳐야된다.

그러나 리덕스를 사용하게 되면 데이터 상태를 컴포넌트 외부에서 관리하기 때문에 과정을 단순화 시킬 수 있게 된다.

 

- Reducer : 업데이트 로직을 정의하는 함수

 

< Redux 흐름 >

subscribe(상태 변화 감지요청) >> action(상태 변화) >> dispatch(상태업데이트, store에 action 전달)

>> Store(state 갱신) >> listener(상태 변화 알림) >> 컴포넌트 리렌더링

 

- 여러 컴포넌트를 거칠 필요없이 부모 컴포넌트에서 다이렉트로 받는거처럼 리덕스 스토어에서 원하는 상태값을 전달한다.

(단방향 데이터 흐름으로 데이터 구조의 단순화)

 

< Redux 의 3 원칙 >

1. Single source of truth
    애플리케이션 내에 Store는 반드시 1개 뿐. Store는 반드시 1개만 존재한다.

2. State is read-only
    state를 직접 변경해서는 안.된.다.
    state를 변화시키는 유일한 방법은 Action을 Reducer에 dispatch(송신, 전달)하는 방법 뿐이다.
    즉, state의 변경은Reducer만 할 수 있다. Reducer 이외의 공간에서는 state는 읽기 전용인 것이다.

3. Changes are made with pure functions
    Reducer는 순수 함수여야만 한다.
    Reducer 함수는 parameter로 기존의 state와 Action을 받는데, 

    Reducer 함수는 기존의 state를 직접 변경하지 않고, 새.로.운 state object(객체)를 작성해서 return해야한다.


useReducer

useReducer와 context api 로 redux의 기능을 대부분 구현할 수 있다

(그러나 프로젝트의 규모가 크다면 useReducer와 context api 조합의 한계로 비동기적인 작업시 불편하다)

 

import React, { useState, useReducer } from 'react';

const initialState = { //state 정의
	winner: '',
    turn : 'O',
    tableData : [['','',''], ['','',''], ['','','']],
};

const reducer = (state, action) => { //state 변화 정의
	
};
const [state, dispatch] = useReducer(reducer, initialState);

 

- 여러개의 state를 하나의 state로 만들어 state의 갯수를 줄여준다

 

< Redux와 useReducer 차이 >

 

1. 리덕스는 dispatch를 통해 state가 동기적으로 변경, useReducer는 비동기적으로 변경

 

table 최적화 하기

 

useEffect를 사용하여 렌더링 될 때 console.log로 두번째인자(바뀌는 값)을 넣어서 어떠한 값의 변화로 인한 리렌더링인지 확인할 수 있다.

최종 하위 컴포넌트(td) 값의 변화로 리렌더링 될때 부모 컴포넌트까지 렌더링되지 않도록 memo를 사용하여 props의 값을 기억해준다.

 

** React.memo : 기억해둔 props의 값이 같다면 기억해둔 값을 재사용, 다르다면 리렌더링

** useCallback :  함수 자체를 메모이징해서 재사용한다. 불필요한 렌더링을 줄일수 있다.


//부모 컴포넌트
import React, { useReducer, createContext } from 'react';

const Context = createContext({initial});
const value = useMemo(() => ({tableData : state.tableData, dispatch}), [state.tableData]);

<Context.Provider value={ value }>
	<Form />
    <Table />
</Context.Provider>


//하위 컴포넌트
import React, { useContext } from 'react';
import { Context } form '부모컴포넌트';

const Table = () => {
	const { tableData } = useContext(Context);
    ...
};

export default Table;

Context Api

context api를 제외하고 useReducer만 사용할 경우, dispatch 와 reducer를 통해 변경할 데이터(action)을 props로 최종 변경할 컴포넌트까지 전달해주어야한다.

- 자식 컴포넌트 뿐 아니라 해당 데이터를 사용할 컴포넌트 모두에게 다이렉트로 전달할 수 있다.

 

1. createContext(기본값) 으로 Context 객체를 생성

2. Context.Provider : 하위 컴포넌트에 접근 가능하도록 명시

    - Context.Provider 의 value 속성으로 데이터 전달

    - value 속성에 객체로 명시해주면 렌더링시 계속 새로운 객체를 만들기 때문에 최적화에 문제가 발생, 

        >> useMemo를 사용(캐싱)하여 메모이징하여 해결한다. dispatch는 항상 같으므로 변경될 값에 기재하지 않아도된다.

3. useContext : Context(부모 컴포넌트에서 전달될 props)를 자식컴포넌트에서 불러오도록 해준다.

 

< Context API 최적화 >

 

memo, useMemo를 적절히 사용하여 캐싱을 통해 최적화

 

 

 

 

 


출처 👇👇👇

강의정보  👇👇👇

더보기

플랫폼 : 인프런

강의 : 웹 게임을 만들며 배우는 React 

강사 : 제로초(조현영)

개발환경 : vscode, react

url : https://www.inflearn.com/course/web-game-react