Redux

zimablue·2024년 3월 6일

third-party

목록 보기
3/3

redux

이미지출처: https://opentutorials.org/module/4078/24935

dispatch(action creator) → reducer(action) → store(reducer) → subscribe() → getState()



state

  • store 는 state 가 저장되는 곳
  • reducer의 return 값을 store에 초기화
let store = Redux.createStore(reducer);



reducer

  • reducer는 dispatch에서 action을 전달 받고 변경된 state를 return
function reducer(state, action) {

// state의 초기값을 color:'yellow'로 설정 설정
	if (state === undefined) {
		return {color:'yellow'}
	}

	let newState;

	if (action.type === 'CHANGE_COLOR') {
// 빈 객체에 기존 state와 {color:action.color}를 덮어 씌워 newState에 초기화
		newState = Object.assign({}, state, {color:action.color});
	}
	return newState;
}
// 덮어질 객체에 덮어씌울 객체를 덮어 씌운 뒤 덮어질 객체를 반환
Object.assign({덮어질 객체}, {덮어씌울 객체}, ...)



getstate

  • render에서 getState()로 state를 사용
let state = store.getState()

console.log(state);
// {color: "yellow"}

console.log(state.color);
// "yellow"



dispatch

  • dispatch는 reducer에 action을 전달하고, subscribe()에 호출
store.dispatch({type:'CHANGE_COLOR', color:'red'});

// action = {type:"CHANGE_COLOR", color:"red"}



render

  • render는 현재 state를 참조하여 UI를 만드는 곳
// 버튼을 클릭하면 dispatch를 통해 action을 reducer에 전달
function red() {
	document.querySelector('#red').innerHTML = `
		<div class="container" id="component_red">
			<h1>red</h1>
			<input type="button" value="fire" onclick"
				store.dispatch({type:'CHANGE_COLOR', color:'red'});
			">
		</div>
	`;
}
red()



subscribe

  • subscribe()는 state가 변경될 때마다 render가 호출되게 함
// red 함수를 구독 상태로
store.subscribe(red);





react-redux



설치 방법

npm install redux
npm install react-redux



import

import React, { useState } from 'react';
import { createStere } from 'redux';
import { Provider, useSelector, useDispatch, connect } from 'react-redux'



createStore

  • reducer의 return 값을 provider가 component들에게 전달 할 수 있게 store에 초기화
  • createStore(reducer, [preloadedState], [enhancer])
  • reducer 현재 state 를 다음 상태로 반환하는 리듀싱 함수
  • preloadedState store의 초기상태
  • enhancer 저장소 인핸서, Redux와 함께 제공되는 인핸서는 [applyMiddleware()](https://ko.redux.js.org/api/applymiddleware)

const store = createStore(reducer);
// reducer 에서 받아온 state 를 변수 store 에 초기화
// Provider 에서 store 를 통해 useSelector 에게 넘겨줄 예정 



reducer

  • currentState를 받아 newState로 바꿔서 return
  • 단 각각의 state를 불변하게 유지해야하기 때문에 destructuring(…) 을 통해 currentState를 복제한 후에 변화를 주어 newState를 생성
  • return 값은 createStore로 전달
  • currentState 현재 상태
  • action dispatch 에서 보내는 type 과 payload
function reducer(currentState, action) {
  if (currentState === undefined) 
		{ //현재 상태가 undefined(정의되지 않음) 라면 {number: 1} 리턴
    return {
      number: 1,
    };
  }

  const newState = { ...currentState }; //과거 상태를 newState에 복제 후 리턴

	if (action.type === 'PLUS') { 
//useDispatch로 부터 받은 action.type이 'PLUS 라면 number 에 1을 더해줌
    newState.number++;
  }

  return newState;
}



Provider

  • Provider의 하위 컴포넌트들은 createStore에 접근 가능
  • reducer → createStore로 전달된 state를 store을 통해
    어떤 component들에게 제공할 것인가에 대한 가장 바깥 울타리
function App() {
  const [number, setNumber] = useState(1); //상태 초기값은 1
  return (
    <div id="container">
      <h1>Root</h1>
      <div id="grid">

        <Provider store={store}>
          <Left1></Left1>
          <Right1></Right1>
        </Provider>

      </div>
    </div>
  );
}
//Left1과 Right1에 state를 제공



useSelector

  • reducer → createStore → provider를 통해 전달된 state 를 불러오기
  • 쓰고 싶은 state를 선택하는 역할
  • 함수를 인자로 받음
function Left3(props) {
// 전달받은 state 값중 number를 쓰겠다는 의미
  const number = useSelector((state) => state.number);
  return (
    <div>
      <h1>Left3 : {number}</h1>
    </div>
  );
}



useDispatch

  • state 를 바꾸고 싶을 때 dispatch 사용
  • reducer로 action을 전달, action은 기본적으로는 객체 형식이여야 함
    (redux-promise는 Promise를, redux-thunk는 Functions 형식을 사용 가능하게 함)
function Right3(props) {

  const dispatch = useDispatch(); //useDispatch() 선언

  return (
    <div>
      <h1>Right3</h1>
      <input type="button" value="+" onClick={() => {

        dispatch({ type: 'Plus' })
 //reducer 함수의 두번째 인자에 { type: 'PLUS'}

      }}>
			</input>
    </div>
  );
}



전체 예시 코드

Right3 의 버튼을 누르면 left3 의 count 가 증가

import React, { useState } from 'react';
import './style.css';
import { createStore } from 'redux';
import { Provider, useSelector, useDispatch, connect } from 'react-redux';

function reducer(currentState, action) {
  if (currentState === undefined) {
    return {
      number: 1,
    };
  }
  const newState = { ...currentState };
  if (action.type === 'PLUS') {
    newState.number++;
  }
  return newState;
}

const store = createStore(reducer);
export default function App() {
  const [number, setNumber] = useState(1);
  return (
    <div id="container">
      <h1>Root</h1>
      <div id="grid">
        <Provider store={store}>
          <Left1></Left1>
          <Right1></Right1>
        </Provider>
      </div>
    </div>
  );
}
function Left1(props) {
  return (
    <div>
      <h1>Left1 </h1>
      <Left2></Left2>
    </div>
  );
}
function Left2(props) {
  return (
    <div>
      <h1>Left2 : </h1>
      <Left3></Left3>
    </div>
  );
}
function Left3(props) {
  const number = useSelector((state) => state.number);
  return (
    <div>
      <h1>Left3 : {number}</h1>
    </div>
  );
}
function Right1(props) {
  return (
    <div>
      <h1>Right1</h1>
      <Right2></Right2>
    </div>
  );
}
function Right2(props) {
  return (
    <div>
      <h1>Right2</h1>
      <Right3></Right3>
    </div>
  );
}
function Right3(props) {
  const dispatch = useDispatch();
  return (
    <div>
      <h1>Right3</h1>
      <input type="button" value="+" onClick={() => {
        dispatch({ type: 'Plus' })
      }}></input>
    </div>
  );
}





redux-toolkit

사용이유

  • 설정 간소화
  • 미들웨어 설치
  • 반복되는 코드
  • 불변성 유지의 어려움



설치 방법

npm install @reduxjs/toolkit
  • configureStore 로 reducer 들의 집합 만들기
  • createSlice 로 필요한 slice 만들고 state 제어하기
  • Provider 로 하위폴더 감싸기
  • useDispatch 로 slice로 action 보내기
  • useSelector 로 state 사용하기



정리

  • App.js 에서 useSelector((state) ⇒ {}) 의 state 는
    store.js 에서 configureStore() 의 reducer 이고
  • App.js 에서 state.counter 은
    store.js 에서 reducer.counter 이고
  • store.js 에서 reducer.counter 는
    counterSlice.reducer 이고
  • store.js 에서 counterSlice.reducer은
    counterSlice.js 에서 createSlice의 initialstate 또는 reducers의 return 값
  • counterSlice.actions은 counterSlice.reducers
  • counterSlice.reducers 들은 dispatch 로 보내어서 필요할 때 사용
  • counterSlice.reducer 들은 configureStore을 통해 useSelector로 보냄



App.js

  • useSelector
import React from 'react';

import { useSelector, useDispatch, Provider } from 'react-redux';

import store from './store';
import { up } from './counterSlice';

function Counter() {
  const dispatch = useDispatch();

// useSelector
// state = configureStore의 reducer
// state.counter = counterSlice.reducer
// counterSlice.reducer = counterSlice의 reducer이 return 하는 state
// counsterSlice.reducer의 state의 key는 현재 value

  const count = useSelector((state) => {
    return state.counter.value;         
  });                                    
  return (                              
    <div>
      <button

// useDispatch
// reducer 에게 전달
        onClick={() => {
					// dispatch({type:'counterSlice/up', payload:2});
// counterSlice.js 내의 이름이 counterSlice인 slice
// {type: name/reducers의 key, payload: action.payload}

					dispatch(counterSlice.actions.up(2));
        }}
      >
        +
      </button>{' '}
      {count}  //useSelector의 return값
    </div>
  );
}

export default function App() 
  return (

// Provider
// store 전달
    <Provider store={store}> 
      <div>
        <Counter></Counter>
      </div>
    </Provider>
  );
}



counterSlice.js

  • slice를 사용하면 ruducer에서 destructuring을 사용하지 않아도 됨
  • slice는 store를 작게 나눈 느낌, 기존 redux의 reducer 대신 사용
  • createSlice는 name, initialState, reducers 가 필요함
  • name: slice 이름
  • initialState: state의 초기값
  • reducers: type 별 조건문
import {createSlice} from '@reduxjs/toolkit';

// 필요한 Slice 만들기
// ex)timerSlice

// console.log(counterSlice);
// { 
// name: 'counterSlice', 
// actions: {up}, 
// getInitialState: f, 
// reducer: f 
// }

const counterSlice = createSlice({ 
  name:'counterSlice', 
// createSlice가 유니크한 action 이름을 만들어줌, action creater
  initialState:{value:0}, 
  reducers:{
    up:(state, action)=>{ 
// createSlice를 사용하면 reducer에서 return과  ...state를 사용할 필요 없음
// console.log(action) // {type: 카운터 슬라이스의 name/up, payload:2}
      state.value = state.value + action.payload;
    }
  }
});
export default counterSlice;
export const {up} = counterSlice.actions;

// up 은 action creator
// createSlice에서 reducers를 정의하면, 
// 각 Reducer 함수의 이름 자체가 Action Creator가 된다.



store.js

  • 기존 redux의 store 대신 사용
  • reducer 객체를 App.js의 provider로 전달
import {configureStore} from '@reduxjs/toolkit';
import counterSlice from './counterSlice';

const store = configureStore({  
//각각 Slice의 reducer들의 집합
  reducer:{  
    counter:counterSlice.reducer 
// counterSlice의 reducers의 return 모음

});
export default store;





Redux-toolkit-etc

dispatch 함수에 액션 함수를 넘겨주면, 알아서 dispatch 함수를 액션 함수의 반환 함수의 인자로 넣고 실행.

dispatch() 안에 함수의 return 이 async 함수이면 async의 매개변수에 dispatch를 받아올 수 있는 것인가?



0개의 댓글