상태는 동적으로 표현되는 데이터를 의미하며, 특정 컴포넌트 안에서만 관리되는 로컬 상태와 프로덕트 전체 혹은 여러 가지 컴포넌트가 동시에 관리되는 전역 상태로 구분할 수 있다.
로컬 상태: 폼 데이터, input box, select box 등 입력값을 받는 경우(장바구니 예시: 선택한 수량)
전역 상태: 데이터 로딩 여부, 라이트 모드/다크 모드 테마 설정, 국제화, Undo/Redo를 위한 History기능(포토샵, 일러스트에서 쓰임), (장바구니 예시: 장바구니에 담긴 물품, 상품 선택 여부)
서로 다른 컴포넌트가 사용하는 상태의 종류가 다르면, 서로 다른 출처가 있어도 상관없지만, 서로 다른 컴포넌트가 동일한 상태를 다룬다면, 출처는 오직 한 곳(전역 공간)이어야 한다.
따라서, 데이터의 무결성을 위해 동일한 데이터는 항상 같은 출처에서 가져오도록한다.(Single source of truth)
상태관리를 위해 상태관리 툴이 반드시 필요한 것은 아니지만, 위의 상태관리 툴을 이용하면, 전역 상태 저장소를 제공받을 수 있고, Props drilling 이슈를 해결할 수 있다.
상위 컴포넌트의 state를 props를 통해 전달하고자 하는 컴포넌트로 전달하기 위해 그 사이에 있는 컴포넌트들은 props를 전달하는 용도로만 쓰이며 데이터를 전달하는 현상을 의미한다.
예를들어 순서대로 Component A~D가 존재한다고 했을 때, Component A의 state를 Component D로 전달하기 위해서는 Component B,C를 거쳐야하는 데, 이때 Component B,C는 props를 전달하는 용도로만 쓰이는 것이다.
Props의 전달 횟수가 많지 않으면 큰 문제가 되지 않지만(5회 이하), 그 이상으로 늘어난다면 이하의 문제를 발생시킨다.
코드의 가독성 떨어짐.
코드의 유지보수가 힘들어짐.
전달과정에서 props의 전달만을 위해 쓰이는 컴포넌트들 또한 리렌더링 되므로, 성능 약화를 야기함.
따라서, 과도한 props drilling을 방지하기 위해, 컴포넌트와 관련있는 state는 최대한 가까이 있을 수 있도록 유지하거나, 상태관리 라이브러리를 통해 전역으로 관리하는 저장소에서 직접 state를 꺼내쓸 수 있도록 한다.
// App.js
import React, { useState } from 'react';
import styled from 'styled-components';
// CSS 생략
export default function App() {
const [number, setNumber] = useState(1);
const plusNum = () => {
setNumber(number + 1);
};
const minusNum = () => {
setNumber(number - 1);
};
console.log('Parents');
return (
<Container>
<Text>Parents Component</Text>
<Text>
가장 안 쪽 컴포넌트에 있는 버튼을 통해 state 변경
</Text>
<Quantity>{`state : ${number}`}</Quantity>
<ComponentB plusNum={plusNum} minusNum={minusNum} />
</Container>
);
}
function ComponentB(
{/* props로 전달받은 값 가져오기*/
plusNum,minusNum
}
) {
return (
<Container>
<Text>Component B(props 전달 용도로만 쓰임)</Text>
{/* props로 전달*/}
<ComponentC plusNum={plusNum} minusNum={minusNum}/>
</Container>
);
}
function ComponentC({ plusNum, minusNum }) {
return (
<Container>
<Text>ComponentC</Text>
<Button onClick={plusNum}>👍</Button>
<Button onClick={minusNum}>👎</Button>
</Container>
);
}
// 전역으로 관리하는 저장소(js파일)
export const reducer = (initialState = 1, action) => {
let newState = initialState;
switch (action.type) {
case 'Plus':
return newState + 1;
case 'Minus':
return newState - 1;
default:
return initialState;
}
};
// App.js
import React, { useState } from 'react';
import styled from 'styled-components';
import { useSelector, useDispatch } from 'react-redux';
// css 생략
export default function App() {
const number = useSelector((state) => state);
return (
<Container>
<Text>Parents Component</Text>
<Text>
가장 안 쪽 컴포넌트에 있는 버튼을 통해 state 변경
</Text>
<Quantity>{`state : ${number}`}</Quantity>
<ComponentB />
</Container>
);
}
function ComponentB() {
return (
<Container>
<Text>Component B</Text>
<ComponentC />
</Container>
);
}
function ComponentC() {
const dispatch = useDispatch();
const plusNum = () => {
dispatch({ type: 'Plus' });
};
const minusNum = () => {
dispatch({ type: 'Minus' });
};
return (
<Container>
<Text>Component C</Text>
<Button onClick={plusNum}>👍</Button>
<Button onClick={minusNum}>👎</Button>
</Container>
);
}
Reference: 코드스테이츠