Front-end/React

[React] Redux(리덕스)

늘이 2023. 5. 17. 14:29

 
리액트: 사용자 정의 태그, 컴포넌트를 만들어서 체계적이고 잘 정돈된 애플리케이션을 만들게 해주는 기술
 

0. 리덕스의 필요성

1) SPA(Single Page Application)의 단점
- 컴포넌트 간 state 공유 어려움
- 부모 컴포넌트에서 자식 컴포넌트로만 state 공유 가능 
 
2) Context API를 사용할 수 있지만 단점이 존재함
- state 변경 시 Context value 값이 변경되면 value값을 사용하지 않은 컴포넌트까지 재렌더링(쓸데없는 것 까지 재렌더링 됨 )
- useContext를 사용하는 자식 컴포넌트를 재사용하기가 어려움 
 

더보기

1. Context API 사용

1) createContext() 로 Context 만들기

- context는 state 보관함

export let Context1 = createContext();

 

 

2) <Context>로 원하는 컴포넌트 감싸기

<Context1.Provider>
	<Detail shoes={shoes}/>
</Context1.Provider>

 

3) 공유를 원하는 state를 value에 넣기

<Context1.Provider value={{재고, shoes}}>
	<Detail shoes={shoes}/>
</Context1.Provider>

- value에 넣은 state를 Detail 컴포넌트와 그 자식 컴포넌트에서 사용할 수 있음

 

Detail 컴포넌트에서 state 사용하기

1) Context를 import

import {Context1} from './../App';

 

2) useContext(Context)로 state 사용하기

보관함인 Context 해체하기

let {재고, shoes} = useContext(Context1);

 

 

-> 실제로 사용 잘 하지 않음 외부라이브러리(ex. Redux)같은거 사용함

 

 
 

1. 리덕스(Redux)

- 상태를 중앙에서 관리함으로써 데이터가 예측하지 않은 형태로 변할 가능성을 낮춰주는 기술
- 컴포넌트 간 props 없이 state 공유 가능
 

1) 리덕스의 기능

 

a. 타임 트래블링(time traveling)

- 데이터의 상태를 보관하는 기능
- 리덕스가 제공하는 크롬 확장 개발자 도구를 사용하여 리덕스 스토어의 값을 볼 수 있음
 

b. 핫 리로드(hot reload)

- 코드를 바꿔도 애플리케이션 상태가 독립적으로 마지막 상태를 그대로 유지하기 때문에 개발할 때 다시 수정하고 다시 리로드하는 것을 안하도록 도와주는 도구
 

2) 리덕스 동작 원리

- 스토어(store)에 state들을 보관하고, 저장된 state를 모든 컴포넌트가 꺼내 쓸 수 있음
- 값이 필요한 컴포넌트는 스토어를 구독(scribe)하도록 구현하면 컴포넌트에서 값의 변화가 생겼을 때 구독중인 모든 컴포넌트에 그러한 사실이 통보됨

 

 

2. 리덕스(Redux) 도입하기

 

1) 리덕스 설치

npm install redux

 

2) 리덕스 적용

 

a. 스토어 (store)만들기

- createStore함수

import {createStore} from "redux";

* 
* createStore의 첫 번째 인자 값: reducer 함수
* reducer 함수의 첫 번째 인자 state: 데이터
* reducer 함수의 두 번째 인자 action: 데이터에 가해지는 여러가지 행위
* reducer 함수는 기본적으로 state 값을 반환
*
* crateStore의 두 번째 인자 값: enhancer, 미들웨어, 타임트래블링, 지속성 등과 같은 타사 기능으로 스토어를 향상시기키 위해 선택적 지정
* 
*/
export default createStore(function(state, action) {
    // state가 undefined 일 때 number: 0으로 초기화
    if(state === undefined) {
        return {number:0};
    }
    
    // action.type의 값이 INCREMENT 일 때 기존 state.size값에 인자로 전달된 action.size값을 추가해서 반환
    // ...state: 기존 state의 모든 값을 새로 만들어지는 객체에 그대로 추가하되 number의 값만 변경할 때 사용하는 문법 
    // -> 이 구문을 구조 분해 할당(Destructuring assignment) 라고 함 
    if(action.type === 'INCREDMENT') {
        return {...state, number:state.number + action.size}
    }
    
    return state;
}, 
// crateStore의 두 번째 인자 값: 크롬 확장 프로그램인 Redux DevTool을 사용하기 위해 지정
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__())

 

b. 컨테이너 만들기

 

c. 이벤트에 디스패치

 

d. 컴포넌트 래핑(wrapping)  

- 리덕스 적용후 Redux 스토어의 상태에 의존하기 때문에 컴포넌트의 재사용성이 떨어짐을 해결하는 방법
- 컴포넌트를 감싸는 새로운 컴포넌트를 만들고, 그 컴포넌트가 리덕스의 스토어를 핸들링하는 컴포넌트 만들기
-> 컴포넌트 컨테이너

컴포넌트 컨테이너(component container)
리덕스 스토어와 관련된 작업을 실직적으로 처리하는 컴포넌트

프레젠테이셔널 컴포넌트(Presentational component)
리덕스 스토어 관련 코드 없이 단순히 props로 전달받은 데이터를 출력하거나 함수를 호출하는 일만 담당하는 컴포넌트

 
 
 

3. React-Redcux라이브러리

- 필요한 컴포넌트에만 전달하게 도와줌-> 필요한 컴포넌트만 render() 함수 실행
- props로 어떠한 값을 전달해서 이 값이 반영되어야 할 때  컴포넌트를 감싸고 있는 컨테이너 컴포넌트를 걸쳐서 구현해야하는 번거로움이 있음 이 부분을 react-redux를 이용해서 자동화할 수 있음
 

1) 장점

- 애플리케이션의 복잡성을 낮출 수 있음
- 쉽게 리액트와 리덕스를 연동할 수 있음
- 리덕스를 통해 핫 리로드(hot reload) 사용 가능
 

2) react-redux 설치

npm install @reduxjs/toolkit react-redux

설치하기 전에 package.json 파일을 열어서
"react"
"react-dom" 
항목의 버전을 확인, 이거 두개가 18.1.x 이상이면 사용가능
 
 

3) react-redux 적용

 

a. state들을 보관할 store.js 파일을 만들어서 아래 코드 작성

//store.js

import { configureStore } from '@reduxjs/toolkit'

export default configureStore({
  reducer: { }
})

 

b. <Provider>를 통해 store의 저장한 state 공급 받기 

- <App> 컴포넌트가 <Provider>의 맥락 안에 존재하기 때문에 <Provider>컴포넌트의 지배를 받음
- <Provider> 컴포넌트는 store라는 props를 요구함
-  index.js 파일에서 <Provider> 컴포넌트와 위에서 만든 store.js파일 import 하고 <Provider>로 <App>감싸기
-> 최상위인 <App> 컴포넌트를 포함한 모든 하위 컴포넌트는 <Provider>에서 공급한 store에 접근 가능

//index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { BrowserRouter} from 'react-router-dom';
import { Provider } from "react-redux";
import store from './store.js';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <Provider store={store}>
      <BrowserRouter>
        <App />
      </BrowserRouter>
    </Provider>
  </React.StrictMode>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

 
 

c. store에 state 보관하고 사용하기

- createSlice() 로 state 만들기(createSlice()는 useState()와 용도가 비슷함)
- configureStore()안에 등록하기

//store.js

import { configureStore, createSlice } from '@reduxjs/toolkit'

let user = createSlice({
  name : 'user',
  initialState : 'kim'
})

export default configureStore({
  reducer: {
    user : user.reducer
  }
})

 

d. store의 state 변경하기

1) store.js에 state 변경하는 함수 만들기 
- slice 안에 reducers: {} 안에 함수 만들기
- 파라미터 하나 작명 시 기존 state 
- return 우측에 새로운 state 입력하면 그걸로 state를 변경해줌

let user = createSlice({
  name : 'user',
  initialState : 'kim',
  reducers : {
    changeName(state){
      return 'john '
    }
  }
})

 
 
2) export 하기
- 다른 곳에서 사용하기 위해 export
-  slice명.actions는 state 변경 함수가 전부 그 자리에 출력됨, 그걸 변수에 저장했다가 export하라는 것

export let { changeName } = user.actions

 
 
3) 필요할 때 import 해서 사용하기, dispatch()로 감싸서 사용
- store.js에서 원하는 state변경 함수 가져오면 되고
- useDispatch 라는 것도 라이브러리에서 가져 온다
- 그리고 dispatch( state변경함수() ) 이렇게 감싸서 실행하면 state 변경됨
 

(Cart.js)

import { useDispatch, useSelector } from "react-redux"
import { changeName } from "./../store.js"

(생략) 

<button onClick={()=>{
  dispatch(changeName())
}}>버튼임</button>