[React] Redux(리덕스)
리액트: 사용자 정의 태그, 컴포넌트를 만들어서 체계적이고 잘 정돈된 애플리케이션을 만들게 해주는 기술
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>