본문 바로가기

상태관리 라이브러리/Redux

[Redux] 사용법부터 이론까지 살펴보는 Redux: 3. 리엑트에서의 사용 방법

상태관리 라이브러리인 리덕스를 리엑트와 연결시키는 방법에 대해 알아보자.

 

세팅


redux, react-redux

 

일반적으로는 여기에서 보일러 플레이트를 간편하게 세팅하고 dev tool을 사용하기 위해 redux-toolkit을 추가한다. 하지만, 여기서는 리엑트에 리덕스를 연결시키는 방법에만 집중하기 위해 다음 기회에 다루겠다.

 

 

스토어 생성 및 앱에 바인딩하기


provider

provider은 react-redux가 제공하는 컴포넌트다. 이걸로 리엑트 컴포넌트를 감싸주면, 해당 컴포넌트에서 리덕스 스토어를 가져오고 dispatch 할 수 있다.

 

//@ 리덕스 스토어를 세팅한 후, Provider 앱에 import
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
  <Provider store={store}>
    <App />
  </Provider>,
)

 


참고. 스토어 세팅

리덕스 스토어는 일반적으로 스토어, 리듀서, 액션 생성자, 액션 타입 지정용 상수(enum)로 구성된다. 앱의 규모에 따라 이 셋이 방대해지면 별도의 모듈로 분리하는 것이 좋다.

import { createStore } from "redux";
import { USER_ADD, USER_DELETE } from "./utils/contants";

// 액션 생성자
export const actionCreator = {
  addTodo: (text) => ({ type: USER_ADD, payload: { id: Date.now(), text } }),
  deleteTodo: (id) => ({ type: USER_DELETE, payload: { id } }),
};

// 리듀서
const RootReducer = function (
  state = {
   user=[],
   content=[],
  },
  action
) {
  switch (action.type) {
    case 작업1: {
      return //새로운 스테이트 반환
    case 작업2: {
   	  return //새로운 스테이트 반환
    }
    default:
      return state;
  }
};

// 스토어
const store = createStore(RootReducer);

export default store;

 

이들의 역할은 이전 글에서 다루었으니 필요 시 참고

2024.03.18 - [Redux] - [Redux] 사용법부터 이론까지 살펴보는 Redux: 2 바닐라 자바스크리트에서의 사용 방법

 

 

컴포넌트에서 store 데이터 가져오기


훅을 사용하는 방식과 그렇지 않은 방식으로 구분된다. 리덕스 공식 문서는 간결한 코드 문법 등의 이유로 전자를 권장하지만, 훅이 등장하기 전 프로젝트의 유지 보수를 위해서 후자의 방식도 함께 안내한다.

 

리덕스 훅을 사용하는 방식

const Home = () => {
    const store = useSelector(state => state);
};
  • useSelector(selectFuntion) | 스토어의 데이터를 가져오는 react-redux의 훅.
    • selectFuntion | 리덕스 store을 인자로 하여서 가져올 값을 반환한다.
    • 거대한 단일 스토어를 가지는 리덕스 특성 상, 컴포넌트에 필요한 데이터만을 가져오는 것이 좋다. useSelector 훅은 선택자 함수가 호출될 때마다 전체 상태 객체를 비교하고, 상태가 변경되었을 때 새로운 값으로 간주하여 해당 컴포넌트를 다시 렌더링한다. 때문에, 거대한 단일 스토어를 가지는 리덕스 특성 상, 불필요한 렌더링을 피하기 위해서는  컴포넌트에 필요한 데이터만을 가져오는 것이 좋다. 

 

리렌더링 메커니즘

리덕스는 dispatch로 스토어에 변경이 생기면 useSelector훅을 사용한 모든 컴포넌트에 리렌더링 필요 여부를 체크한다. 이 때, useSelector의 기존 인자와 store의 새로운 state간 비교를 하여 다르면 리렌더링된다.

따라서 비교 대상인 두 값이 원시값이면 실제 값이 바뀌지 않아도 동일하게 인식할 것이다. 하지만, 객체라면 실제 값이 바뀌지 않았더라도 참조 주소가 바뀐 다른 객체이니 다른 값으로 인식하여 리렌더링된다.

 

이를 최적화하기 위해, useSelector은 두번째 인자로 비교 함수를 넣을 수 있게 한다.

const equalFn = function (oldValue, newValue) {
  if (oldValue.length !== newValue.length) return false;
  if (JSON.stringify(oldValue) !== JSON.stringify(newValue)) return false;
  console.log("검사 통과");
  return true;
};


const Home = () => {
    const store = useSelector(state => state,equalFn);
   //위와 동일, const store = useSelector(state => state,{ equalityFn: equalFn });
};
  • 비교 함수는 두 개의 인자를 가진다. 첫 번째는 이전의 state, 두 번째는 새로운 state이다.
  • false를 반환하면 두 값이 다른 걸로 간주하고 리렌더링
  • true를 반환하면 두 값이 같은 걸로 간주하고 리렌더링

 

 

리덕스 훅을 사용하지 않는 방식

function Home (){
return <div>home</div>
}


function mapStateToProps(state,ownProps){
return {store:state,...ownProps}
}

export default connect(mapStateToProps)(Home);

 

  • connect(mapStateToProps)(targetComponent)
    • targetComponent  | store를 가져올 컴포넌트
    • mapStateToProps | store와 컴포넌트 사이의 미들웨어 역할
  • mapStateToProps 
    • 함수 타입
    • state | 리덕스 스토어의 데이터
    • ownProps | 부모 컴포넌트에게 전달받은 props
    • return | 객체 형태이어야 한다. 이 반환 값은 연결된 컴포넌트의 props가 된다.
    •  

 

  • 리덕스에서는 컴포넌트가 스토어의 데이터를 가져올 때 필요한 부분만을 가져가는 것을 권장한다. 왜냐하면 이러한 함수가 호출될 때마다 전체 상태 객체를 비교하고, 상태가 변경되었을 때 새로운 값으로 간주하여 해당 컴포넌트를 다시 렌더링하기 때문이다. 이는 불필요한 렌더링을 유발할 수 있다.

 

컴포넌트에서 dispatch 사용하기


앞서 본 것과 마찬가지로, 훅을 사용하는 방식과 그렇지 않은 방식으로 구분된다. 리덕스 공식 문서는 간결한 코드 문법 등의 이유로 전자를 권장하지만, 훅이 등장하기 전 프로젝트의 유지 보수를 위해서 후자의 방식도 함께 안내한다.

 

리덕스 훅을 사용하는 방식

const Home = () => {
    const dispatch = useDispatch();
    const action ={type:"USER_ADD",payload:{id:Date.now(),name:"foo"}}
    dispatch(action)
};
  • useDispatch() | dispatch를 생성한다. 반환된 dispatch로 스토어의 리듀서에 액션을 보낼 수 있다.

 

리덕스 훅을 사용하지 않는 방식

 function mapDispatchToProps(dispatch, ownProps) {
   const customDispatch1= (payload)=>dispatch({type:"test1", payload})
   return { customDispatch1};
 }

 export default connect(null, mapDispatchToProps)(Home);
  • connect(mapStateToProps, mapDispatchToProps)(targetComponent)
    • targetComponent  | store를 가져올 컴포넌트
    • mapStateToProps | store와 컴포넌트 사이의 미들웨어 역할. null로 설정할 시, state와 props는 컴포넌트에 전달되지 않는다.
    • mapDispatchToProps | dispatch를 컴포넌트로 전달한다. 이 인자를 설정하지 않으면 컴포넌트에 기본적인 dispatch가 전달된다.
  • mapDispatchToProps   
    • 함수 타입
    • dispatch| 리덕스 스토어의 dispatch 함수
    • ownProps | 부모 컴포넌트에게 전달받은 props
    • return | 객체 형태이어야 한다. 이 반환 값은 연결된 컴포넌트의 props로 추가된다.(이 반환 객체는 기존의 props 객체와 합쳐진다)
    • 역할 | dispatch 함수를 컴포넌트의 필요에 맞게 커스터마이징할 때 사용한다.