리덕스라는 도구는 리액트에 종속된 도구가 아니라 독립적인 도구입니다.
import React,{ useState } from 'react';
import './style.css';
export default function App() {
const [number, setNumber] = useState(1);
return (
<div id="container">
<h1>Root</h1>
<div id='grid'>
<Left1 number={number}></Left1>
<Right1 onIncrease={() => {
setNumber(number + 1);
}}></Right1>
</div>
</div>
);
}
function Left1(props) {
return (
<div>
<h1>Left1 :{props.number}</h1>
<Left2 number={props.number}></Left2>
</div>
);
}
function Left2(props) {
return (
<div>
<h1>Left2 : {props.number}</h1>
<Left3 number={props.number}></Left3>
</div>
);
}
function Left3(props) {
return (
<div>
<h1>Left3 : {props.number}</h1>
</div>
);
}
function Right1(props) {
return (
<div>
<h1>Right1</h1>
<Right2 onIncrease={() => {
props.onIncrease();
}}></Right2>
</div>
);
}
function Right2(props) {
return (
<div>
<h1>Right2</h1>
<Right3 onIncrease={() => {
props.onIncrease();
}}></Right3>
</div>
);
}
function Right3(props) {
return (
<div>
<h1>Right3</h1>
<input type='button' value="+" onClick={() => props.onIncrease()}/>
</div>
);
}
값을 바꾸고 싶을 때 몇 개의 컴포넌트가 서로 부모 자식 관계로 끝없이 이어져 있고 이런 식으로 하게 되면 컴포넌트가 4개가 아닌 1억 개라면 절망적이겠죠. 비유하자면 props를 통해 유선으로 연결된 거라고도 할 수 있어요. 그럼 Left의 말단에 있는 스테이트와, Right의 말단에 있는 이벤트를 무선으로 연결하면 얼마나 좋을까요 이 꿈을 실현해 주는 환상적인 도구가 리덕스입니다.
리액트 리덕스(React Redux)라는 도구를 이용해서 앞서 만든 예제에 리액트 리덕스를 추가해보겠습니다. 리덕스는 독립적인 도구입니다. 독립적인 리덕스와 또 하나의 독립적인 도구인 리액ㅌ를 서로 연결하는 도구가 바로 리액트 리덕스라는 도구입니다.
npm install redux react-redux
스토어를 만들어보겠습니다. 이렇게 만든 스토어는 수정하면 안 되기 때문에 상수로 선언하고 createStore()로 스토어 생성합니다.
import { createStore } from "redux";
const store = createStore();
스토어를 생성할 때 반드시 주입해야 하는 것이 리듀서(reducer)입니다. 리듀서는 스토어에 있는 스테이트를 어떻게 바꿀 것인지 결정한는 역할을 합니다. 그래서 reducer는 두 개의 파라미터를 받습니다. 첫 번째는 현재의 스테이트 값이 currentState이고, 두 번째는 현재의 값을 어떻게 바꿀 것인지에 대한 요청인 action입니다. 이렇게 받은 값을 리턴하며 리턴한 값이 새로운 스테이트의 값이 됩니다.
const store = createStore(reducer);
function reducer(currentState, action){
if(currentState === undefined){
return {
number : 1
};
}
const newState = {...currentState};
return newState;
}
이때 리덕스는 각각의 스테이트의 변환를 불변하게 유지해야 하는데, 그러기 위한 방법을 새로운 스테이트를 만들 때 과거의 스테이트를 복제하는 것입니다. 어떻게 하던 현재의 객체가 복제돼서 복제본이 만들어지고, 그 복제본을 수정하면 불변성을 유지할 수 있습니다. 그리고 이렇게 변화시킨 스테이트를 리턴하는 것입니다. 그런데 currentState의 값이 undefined라면 그것 무슨 뜻일까요? 스테이트가 정의되지 않았다는 것이므로 기본 스테이트 값을 리턴함으로써 기본값을 설정할 수 있습니다. 즉, 코드를 보면 number 스테이트의 기본값이 1이 됩니다.
리액트 리덕스에는 4인방이 있습니다. 첫 번째는 Provider 컴포넌트입니다. 두 번째는 useSelector로, 어떤 스테이트 값을 쓰고 싶은지 선택하는 것입니다. 세 번째 useDispatch로서 스테이트 값을 변경할 때 사용합니다. 마지막으로 connet가 있습니다. 모두 리액트 리덕스에서 제공하는 기능입니다.
import { createStore } from "redux";
import { Provider, useSelector, useDispatch, connect } from "react-redux";
Provider는 스테이트를 어떤 컴포넌트들에 제공한 것인지 가장 바깥쪽에 있는 울타리를 정하는 역할을 합니다.
<div id='grid'>
<Provider store={store}>
<Left1></Left1>
<Right1></Right1>
</Provider>
</div>
무선으로 연결하기 위해 useSelection를 사용합니다.
function Left3(props) {
const number = useSelector((state) => state.number);
return (
<div>
<h1>Left3 : {number}</h1>
</div>
);
}
전역적이라고도 할 수 있는 store에 저장된 number 스트이트와 말단에 있는 컴포넌트의 number를 무선으로 연결한 것입니다.
디스패치는 useDispatch를 이용해 가져올 수 있습니다. 이렇게 가져온 디스패치 type 값으로 PLUS라는 액션을 전달합니다.
const store = createStore(reducer);
function reducer(currentState, action){
if(currentState === undefined){
return {
number : 1
};
}
const newState = {...currentState};
if(action.type ==='PLUS'){
newState.number++;
}
return newState;
}
function Right3(props) {
const dispatch = useDispatch();
return (
<div>
<h1>Right3</h1>
<input type='button' value="+" onClick={() => {
dispatch({type: "PLUS"})}}/>
</div>
);
}
디스패치는 useDispatch를 이용해 가져올 수 있습니다. 이렇게 가져온 디스패치에 type 값으로 PLUS라는 액션을 전달합니다.
이것이 리덕스를 사용했을 때의 환상적인 효과 중 하나고, 이를 쉽게 만드러주는 도구가 리액트 리덕스인 것입니다. 또 한가지 흥미로운 점은 Left3에 console.log('3')을 추가하고, Left2에 console.log('2)를 추가한 다음 [+] 버튼을 눌러보면 콘솔에 3만 출력되는 모습을 볼 수 있습니다.
즉, 스테이트를 사용하는 number만 값이 바뀌고, 그 부모는 다시 렌더링되지 않기 때문에 퍼포먼스 측면에서도 상당한 이점이 있습니다.
import React,{ useState } from 'react';
import './style.css';
import { createStore } from "redux";
import { Provider, useSelector, useDispatch, connect } from "react-redux";
const store = createStore(reducer);
function reducer(currentState, action){
if(currentState === undefined){
return {
number : 1
};
}
const newState = {...currentState};
if(action.type ==='PLUS'){
newState.number++;
}
return newState;
}
export default function App() {
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) {
console.log('2');
return (
<div>
<h1>Left2</h1>
<Left3></Left3>
</div>
);
}
function Left3(props) {
const number = useSelector((state) => state.number);
console.log('3');
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"})}}/>
</div>
);
}
connect() 추가
import React from 'react';
import './style.css';
import { createStore } from "redux";
import { Provider, connect } from "react-redux";
const store = createStore(reducer);
function reducer(currentState, action) {
if (currentState === undefined) {
return {
number: 1
};
}
const newState = { ...currentState };
if (action.type === 'PLUS') {
newState.number++;
}
return newState;
}
export default function App() {
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>
<ConnectedLeft2></ConnectedLeft2>
</div>
);
}
function Left2(props) {
console.log('2');
return (
<div>
<h1>Left2</h1>
<ConnectedLeft3></ConnectedLeft3>
</div>
);
}
function Left3(props) {
console.log('3');
return (
<div>
<h1>Left3 : {props.number}</h1>
</div>
);
}
const mapStateToPropsLeft3 = (state) => {
return {
number: state.number
};
};
const ConnectedLeft3 = connect(mapStateToPropsLeft3)(Left3);
const ConnectedLeft2 = connect()(Left2); // Connecting it so it's consistent, even if we don't use Redux props/actions here
function Right1(props) {
return (
<div>
<h1>Right1</h1>
<ConnectedRight2></ConnectedRight2>
</div>
);
}
function Right2(props) {
return (
<div>
<h1>Right2</h1>
<ConnectedRight3></ConnectedRight3>
</div>
);
}
function Right3(props) {
return (
<div>
<h1>Right3</h1>
<input type='button' value="+" onClick={props.increaseNumber} />
</div>
);
}
const mapDispatchToPropsRight3 = (dispatch) => {
return {
increaseNumber: () => dispatch({ type: "PLUS" })
};
};
const ConnectedRight3 = connect(null, mapDispatchToPropsRight3)(Right3);
const ConnectedRight2 = connect()(Right2); // Connecting it so it's consistent, even if we don't use Redux props/actions here
'리액트(React)' 카테고리의 다른 글
리액트(React) - useReducer (0) | 2023.09.24 |
---|---|
리액트(React) - Context API (0) | 2023.09.22 |
리액트(React) - 스타일 컴포넌트(styled components) (0) | 2023.09.22 |
리액트(React) - react-router-dom (0) | 2023.09.22 |
리액트(React) - 리액트 기초 (0) | 2023.09.22 |