State 상태
- 컴포넌트 안에서 관리되는 것
- 자식 컴포넌트들 간의 다이렉트 데이터 전달은 불가능하다
- 자식 컴포넌트들 간의 데이터를 주고 받을 때는 상태를 관리하는 부모 컴포넌트를 통해서 주고 받는다
- 상태를 관리하는 상위 컴포넌트에서 계속 내려 받아야 한다.
Redux
- 여러 컴포넌트가 공유하는 상태를 관리하기 위한 라이브러리
- 리액트 컨텍스트에 기반을 둔 라이브러리로 Provider 컴포넌트가 항상 취상위로 동작해야 한다.
- 자바스크립트 앱을 위한 예측 가능한 상태 컨테이너이다.
- 서로 다른 환경(서버, 클라이언트, 네이티브)에서 작동하고, 테스트하기 쉬운 앱을 작성하도록 도와준다.
- 리덕스를 사용하면 상태값을 컴포넌트에 종속시키지 않고, 상태 관리를 컴포넌트의 바깥에서 관리할 수 있게 된다.
- 전역 상태 저장소 제공
- Props Drilling 이슈 해결
Store 상태 저장소
- 모든 상태를 하나의 중앙 저장소인 store에 저장한다
- 상태가 관리되는 오직 하나의 공간
- 스토어 안에서 App에서 필요한 상태를 담는다
- 컴포넌트에서 상태정보가 필요할 때 스토어에 접근한다
Action
- 상태 변경을 설명하는 객체
- App에서 Store에 운반할 데이터를 말한다
- JSON 객체 형식으로 돼있다
- 상태를 변경하려면 액션을 dispatch해야 한다
- 주로 타입과 payload(변경할 데이터)를 포함한다
Reducer
- 액션을 받아서 새로운 상태를 반환하는 함수
- 이전 상태와 액션을 인자로 받아 새로운 상태를 계산해 반환한다
- Action을 Reducer에 전달한다
- Reducer가 주문을 보고 Store의 상태를 업데이트 하는 것
- Action을 Reducer에 전달하기위해서는 dispatch() 메서드를 사용해야 한다
=> 컴포넌트 dispatch() ➖ Reducer => action ➖ Store ➡️ 컴포넌트 리렌더링
Dispatch
- 액션을 리듀서에 전달하여 상태를 변경하는 방법
- 액션을 dispatch하면 Reducer가 새로운 상태를 계산하고, Redux는 새로운 상태를 Store에 반영한다.
Subscription
- 리액트 컴포넌트와 같은 UI는 상태변경을 반영하기 위해 redux store에 구독할 수 있다
- 상태가 변경되면 구독된 컴포넌트가 자동으로 업데이트된다.
- Redux는 상태변수를 관리하는 모듈이다
- Redux는 컴포넌트들의 배치와 상관없는 store 라는 공간을 만든다
- store 안에 용도별로 slice라는 모듈들을 박아넣는다
- slice 마다 관리하고자하는 상태변수를 만든다 ( 상태변수 여러개 가능 )
- slice마다 setter함수들을 넣는다 <= (reducer)
- App.js 하위 컴포넌트에서 필요한 상태변수를 바로 받을 수 있다. 그 변수를 여러개의 컴포넌트가 받을 수 있다.
- 어떤 컴포넌트가 reducer 함수를 호출했다면 해당 슬라이스의 상태변수를 업데이트하고, App.js를 타고 모든 컴포넌트에 전파된다.
- useState 상태변수를 전역화시키는 모듈이다
- 다른 상태변수는 다른페이지 갔다오면 리셋되는데 Redux는 계속 유지된다
설치
# NPM
npm install redux
npm install react-redux
# Yarn
yarn add redux react-redux
redux 예제
slice라고 불리는 모듈을 먼저 작업한다
📁 src / slices / CounterSlice.js 파일
⬇️ reduxjs/toolkit 패키지로부터 createSlice함수 가져오기 (📁src>slices>CounterSlice.js)
import {createSlice} from '@reduxjs/toolkit';
⬇️ createSlice 함수 호출할 때 json 객체로 몇가지 설정을 해준다. (📁src>slices>CounterSlice.js)
const CounterSlice = createSlice( {
// 1) Slice 객체 이름 => Slice 객체명과 동일하게 설정 (헷갈리지 않게)
name: 'CounterSlice',
// 2) 이 모듈이 관리하고자하는 상태값들을 명시 (전역변수 같은) => 이 안의 변수들은 자유롭게 나열
initialState: {
number: 0,
color: '#000'
},
.....
initialState : 모든 페이지에서 인식 가능한 전역변수
(📁src>slices>CounterSlice.js)
const CounterSlice = createSlice( {
........
/** 3) 상태값을 갱신하기 위한 함수들 구현
* => 파라미터는 state, action 으로 고정되어 있다
* => state : 현재 상태값을 가리킨다
* => 컴포넌트에서 이 함수들을 호출할 때 전달되는 파라미터는 `action.payload`로 전달된다
* => initialState 와 동일한 구조의 JSON을 리턴해야 한다
*/
reducers: {
plus: (state, action) => {
const numberValue = state.number + action.payload;
let colorValue = '#000';
if (numberValue > 0) {
colorValue = '#2f77eb';
} else if (numberValue < 0) {
colorValue = '#f60';
}
return {
number: numberValue,
color: colorValue
}
},
minus: (state, action) => {
...........
}
}
} );
reducers => json
initialState 변수들의 setter 함수들을 나열한다고 보면 된다
상태값은 state, action으로 고정
⬇️ plus, minus => action 함수 (📁src>slices>CounterSlice.js)
reducers: {
plus: (state, action) => { ... },
minus: (state, action) => { ... }
}
⬇️ action 함수 export (📁src>slices>CounterSlice.js)
// 액션함수들 내보내기
export const {plus, minus} = CounterSlice.actions;
컴포넌트에서 액션함수 사용 가능
( 컴포넌트의 이벤트 안에서 호출 한다거나 )
컴포넌트에서 +,- 버튼 클릭 이벤트시 리듀서의 plus, minus 호출 하는 것
버튼을 누르면서 개발자가 보내고 싶은 몇가지 파라미터를 보낸다.
파라미터가 action으로 들어가고, ( 컴포넌트 => slice - reducer - action )
initialState가 state로 들어온다.
action 값에 따라 state를 조작한다. ( action => state => initialState )
return값은 initialState를 갱신한다.
상태값은 store를 통해 컴포넌트에 반영된다. ( initialState의 변수들 => store )
⬇️ 컴포넌트에서 5라는 파라미터 값은 action의 paylod로 들어온다.
// 컴포넌트 일부
<button onClick={e => dispatch(plus(5))}> +5 </button>
(📁src>slices>CounterSlice.js)
plus: (state, action) => {
const numberValue = state.number + action.payload;
⬇️ action 함수의 return 값은 initialState와 동일한 구조의 JSON 이어야 한다. (📁src>slices>CounterSlice.js)
return {
number: numberValue,
color: colorValue
}
⬇️ 기본으로 reducer export (reducers 아니고 reducer) (📁src>slices>CounterSlice.js)
// 리듀서 객체 내보내기
export default CounterSlice.reducer;
Slice 모듈을 store.js에 탑재시킨다.
⬇️ 📁src>store.js
import CounterSlice from './slices/CounterSlice';
reducer: {
CounterSlice
}
⬆️ ( JSON )
❓ ( ⬇️ JavaScript 문법 => key, value 같을 때 key 생략 가능하다 )
const x = 100;
const data = {
"x" : x
}
// key, value 같을 때 => key 생략 가능
const data = {
x
}
⬇️ 📁src>index.js
react-redux 패키지로부터 Provider 컴포넌트를 가져온다
// 리덕스 구성을 위한 참조
import { Provider } from 'react-redux';
import store from './store';
import { Provider } from 'react-redux';
import store from './store';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
// 리덕스 스토어 연결
<Provider store={store}>
<BrowserRouter basename={process.env.PUBLIC_URL} future={{v7_startTransition: true, v7_relativeSplatPath: true}}>
<App />
</BrowserRouter>
</Provider>
);
⬆️ Provider는 store가 갖고있는 모든 slice를 App의 모든 컴포넌트에 전파한다.
📁src>pages>counter>index.js
⬇️ Slice로부터 액션함수 가져오기
// Slice에 정의된 액션함수들 참조
import {plus, minus} from '../../slices/CounterSlice';
⬇️ 상태값을 넘겨받을 useSelector, useDispatch 가져오기
// 상태값을 로드하기 위한 hook 과 action 함수를 dispatch 할 hook 참조
import {useSelector, useDispatch} from 'react-redux';
useDispatch 함수를 통해 action함수를 호출한다.
const Counter = memo(() => {
// hook을 통해 slice가 관리하는 상태값 가져오기
const {number, color} = useSelector(state => state.CounterSlice);
⬆️ state => store가 관리하는 모든 상태값 📁src>pages>counter>index.js
CounterSice의 상태변수가 리턴되고, 구조분해로 number, color에 값이 들어간다.
⬇️ dispatch 함수를 통해 action함수 plus, minus 를 호출한다. 📁src>pages>counter>index.js
const dispatch = useDispatch();
return (
<CounterContainer>
<h2>Counter</h2>
<div className='counter-box'>
<button onClick={e => dispatch(plus(5))}> +5 </button>
<h2 className="number" style={{ color: color }}> {number} </h2>
<button onClick={e => dispatch(minus(3))}> -3 </button>
</div>
</CounterContainer>
);
[ 📁src>pages>counter>index.js ] ➖ (컴포넌트) dispatch(plus(5)) ➡️
[ 📁src>slices>CounterSlice.js ] ➖ reducers.plus ➖ action.payload ➡️ initialState.number, color 갱신 ➡️
[ 📁src>store.js ] ➡️ [ 📁src>pages>counter>index.js ] ➖ useSelector => number, color ➡️ (컴포넌트)안의 {number}
refer to
메가IT아카데미 이광호쌤
'IT > React' 카테고리의 다른 글
vite (0) | 2025.02.12 |
---|---|
Sass (2) | 2025.02.11 |
React 개발환경 구성 (2) | 2024.11.15 |