1) useState의 불편함
컴포넌트에서 생성한 상태를 다른 컴포넌트로 보낼 때
Props를 통해 부모 컴포넌트에서 자식 컴포넌트로 보내주었다.
만약 조부모 👉 손자 컴포넌트로 값을 전달할 때
부모 컴포넌트도 손자 컴포넌트에게 전달하기 위해
불필요하게 거쳐가야 한다. (조부모 👉 부모 👉 손자)
또한 자식 컴포넌트에서는 부모 컴포넌트로 값을 보내지 못하는 불편함이 있다.
2) Redux의 등장
Redux는 중앙 데이터 관리소로 state로 관리한 데이터들을 말그대로 중앙에서 관리한다.
➡️ 모든 컴포넌트들이 중앙 데이터를 접근해서 조회하고 업데이트할 수 있다.
Local State (지역 상태)
Global State (전역 상태)
yarn add redux react-redux
Redux를 통해 카운터 초기값을 만들고 저장소를 통해
값을 출력해보자.
<파일 구조>
📁src 👉 📁 redux
📁redux 👉 📁 config 👉 📜 configStore.js
📁 redux 👉 📁 modules 👉 📜 counter.js
📁 config : Redux 설정 관련 폴더
📁 modules : state의 그룹
📜configstore.js : 중앙 state 관리소 설정 코드
counter.js
//초기값 설정
const initalState = {
number:0
};
//리듀서 함수 작성
// 👉액션의 타입에 따라 상태를 제어하는 함수
const counter = (state = initalState, action) => {
switch(action.type) {
default:
return state;
}
};
export default counter;
상태(state)
와 액션(action)
을 인자로 받아온다. configStore.js
//중앙 데이터 관리소를 설정하는 코드
import { createStore, combineReducers } from 'redux';
import counter from './../modules/counter';
//기본 리듀서
const rootReducer = combineReducers({
//생성된 리듀서들을 넣어줌
counter
});
const store = createStore(rootReducer);
export default store;
import { Provider } from 'react-redux';
import store from './redux/config/configStore';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<Router>
<App />
</Router>
</Provider>
);
import { useSelector } from 'react-redux';
import './App.css';
export default function App() {
const counter = useSelector((state) => {
return state.counter;
})
return (
<div className="App">
</div>
);
}
👉 state를 출력한 값이다.
👉 현재 count 리듀서만 추가되어 있기 때문에 count 상태만 확인 가능하다.
여러 state들 중에서 count만 접근하고 싶다면 state.count를 통해 값을 가져올 수 있다.
카운터에서 +,- 버튼 클릭 이벤트를 통해 count 값을
저장소에 업데이트 해보자.
counter.js
const initialState = {
number: 0
};
const counter = (state = initialState, action) => {
switch (action.type) {
case 'added':
return { number: state.number + 1 };
case 'subtracted':
return { number: state.number - 1 };
default:
return state;
}
};
import { useDispatch, useSelector } from 'react-redux';
import './App.css';
export default function App() {
//store에 접근하여 count의 값을 읽어온다.
const counter = useSelector((state) => {
return state.counter;
})
//디스패치 생성
const dispatch = useDispatch();
//액션 객체 전달
const handleAdd = () => {
dispatch({ type: 'added' });
}
const handleMinus = () => {
dispatch({ type: 'subtracted' });
}
return (
<>
<div>
현재 카운트 {counter.number}
</div>
<button onClick={handleAdd}>+</button>
<button onClick={handleMinus}>-</button>
</>
);
}
쉽게 말하면 + 버튼을 누르면 1씩 더하라는 의미로 'added'라는 액션객체의 타입을 전달한다.
➡️ +,- 버튼을 통해 데이터가 업데이트 되는 것을 확인할 수 있다.
만약 카운터 외에도 다른 모듈에서도 덧셈, 뺄셈의 액션이 있을 수 있어 액션 타입을 작성할 때
세분화된 이름을 작성하는 것이 좋다.
만약 액션 타입의 명칭을 변경하게 되면 📜counter.js와
📜 App.jsx에서 각각 타입들을 다시 변경해줘야 하는 번거로움이 있다.
👉만일 몇백개의 컴포넌트에서 수정하는 과정에서
실수할 수 있기 때문에 액션 타입과 값을 변수에 저장하는 습관을 가지는 것이 좋다!
counter.js
//액션 타입 변수에 저장
export const COUNTER_ADDED = 'counter/added';
export const COUNTER_SUBSTRACTED = 'counter/substracted';
const initialState = {
number: 0
};
const counter = (state = initialState, action) => {
switch (action.type) {
case COUNTER_ADDED:
return { number: state.number + 1 };
case COUNTER_SUBSTRACTED:
return { number: state.number - 1 };
default:
return state;
}
};
export default counter;
APP.jsx에서 이용하기 위해 내보내는 export 키워드를 쓴다.
App.jsx
mport { COUNTER_ADDED, COUNTER_SUBSTRACTED } from './redux/modules/counter';
export default function App() {
//store에 접근하여 count의 값을 읽어와보자.
const counter = useSelector((state) => {
return state.counter;
})
const dispatch = useDispatch();
const handleAdd = () => {
dispatch({ type: COUNTER_ADDED });
}
const handleMinus = () => {
dispatch({ type: COUNTER_SUBSTRACTED });
}
}
👉 추후에 카운터 액션 타입을 변경할 상황이 오면
counter.js에서 액션 타입을 지정한 변수에만 값을 변경하면 된다.
또한 액션 객체 자체를 함수에 저장해서 활용할 수 있다.
export const added = () => {
return {type:COUNTER_ADDED}
}
말 그대로 action value를 반환하는 함수이다.
dispatch를 통해 액션 객체를 전달할 때 저장된 함수로
접근 가능하다.
import {added} from './redux/modules/counter';
dispatch(added());
action 객체에서 특정 값을 전달할 때 payload를 이용한다.
예를 들어, input에서 원하는 숫자만큼 올린 다음,
➕ 버튼을 누르면 해당 숫자만큼 증가하고 ➖ 버튼을 누르면 해당 숫자만큼 감소하도록 해보자.
App.jsx
import {addedN, subtractedN } from './redux/modules/counter';
export default function App() {
const [num, setNum] = useState(0);
const counter = useSelector((state) => {
return state.counter;
})
const dispatch = useDispatch();
const handleAdd = () => {
dispatch(addedN(num))
}
const handleMinus = () => {
dispatch(subtractedN(num))
}
return (
<>
<div>
현재 카운트 {counter.number}
</div>
<input type='number' value={num} onChange={(e) => setNum(+e.target.value)} /> <br />
<button onClick={handleAdd}>+</button>
<button onClick={handleMinus}>-</button>
</>
);
}
num
)를 인자로 전달해줘야 한다. counter.js
const ADDED_N = 'counter/added_n';
const SUBSTRACTED_N = 'counter/substracted_n'
//액션객체를 리턴하는 함수 생성
export const addedN = (payload) => {
//payload는 input에서 입력한 숫자 값
return {
type: ADDED_N,
payload
}
}
export const subtractedN = (payload) => {
return {
type: SUBSTRACTED_N,
payload
}
}
//리듀서 함수
const counter = (state = initialState, action) => {
switch (action.type) {
case ADDED_N:
return { number: state.number + action.payload };
case SUBSTRACTED_N:
return { number: state.number - action.payload };
default:
return state;
}
export default counter;
};
action.payload
로 값을 접근할 수 있다. 모듈 파일 1개에 Action Type, Action Creator, Reducer가
모두 존재하는 방식이다.