0. 공부하게 된 계기
- Hipspot 프로젝트를 진행하면서 상태관리 라이브러리로 recoil을 사용했습니다.
- 그러나 Hipspot 프로젝트에 참여할 당시에 나는 UI도 겨우 짤 정도의 수준이었고, 기능구현은 구글링해가며 겨우 해냈었습니다.
- 때문에 상태가 정확히 무엇인지도, 상태관리 라이브러리가 무엇인지도 몰랐고, Recoil의 Atom을 사용하는 방법도 몰랐습니다.
- Hipspot을 진행하며 눈치껏 팀원들 코드 보며, 문서를 조금 읽으며 어느정도 사용하는 방법을 이해하게 되었습니다.
- 프로젝트가 마무리되면서 상태관리 및 상태관리 라이브러리에 대해서 정확히 이해하고자 공부를 시작하게 되었습니다 !
1. 상태(State)는 무엇인가?
- State란, 컴포넌트가 기억하는 것이라고 생각하면 편합니다.
- 예를들어 input에 우리가 타이핑을 하면 그 타이핑된 값이 쳐져야 하고
- 이미지를 옆으로 넘기는 버튼을 누르면 다음 이미지가 보여져야합니다.
- 위 예시들에서 컴포넌트는 타이핑된 값을 보여주기 위해선 타이핑 내용을 기억해야 하고, 다음 이미지로 넘어가기 위해선 현재 이미지를 기억해야 합니다.
- 이렇듯 컴포넌트가 기억해야 하는 값을 우리는 상태라고 부릅니다.
- 함수형 컴포넌트에서는
‘useState’
라는 Hook을 이용해 state를 다룰 수 있습니다
1.1 State를 사용하는 이유, 지역변수의 값을 바꿔주는거로는 안되나?
export default function Count() {
let index = 0;
function handleClick() {
index = index + 1;
}
return (
<>
<button onClick={handleClick}>
+1
</button>
<h3>
{index}
</h3>
</>
);
}
- 이렇게 지역변수의 값을 바꿔주는 것으로는 우리가 의도한 것처럼 숫자가 올라가지 않습니다. 그 이유로는 두 가지가 있습니다.
- 첫 째, 지역변수는 렌더링 되면 유지되지 않습니다. 따라서 리액트가 이 컴포넌트를 두 번 렌더링 한다면, 렌더링 이전에 변경된 지역변수를 고려하지 않고 처음부터 다시 렌더링 합니다.
- 둘 째, 지역변수가 변경된다고 해서 렌더링이 이뤄지지 않습니다. 리액트는 새로운 데이터와 함께 컴포넌트를 렌더링할 필요를 느끼지 못합니다.
위와 같은 이유로 우리는 버튼 클릭시 숫자가 업데이트 되도록 하려면
- 버튼 클릭시 리렌더링 되어야 합니다.
- 리렌더링 되면서도 컴포넌트가 숫자 값을 기억하고 업데이트 할 수 있어야 합니다.
2. useState은 뭔데 ?
- React Hook이란 React 버전 16.8부터 제공하는 요소이며, 기존 class 바탕의 코드를 작성할 필요 없이 여러 기능을 사용할 수 있습니다.
- useState는 이러한 Hook 중 하나이며, 상태 관리할 때 사용되는 Hook입니다.
2.1 useState 사용하기
- useState는 두 가지 요소로 구성된다고 볼 수 있다.
- 첫 째로 상태변수(위 예시에선 index)인데, 상태변수는 렌더링시에도 데이터를 잃지 않고 유지합니다.
- 둘 째로 setter함수(위 예시에선 setIndex)인데, setter 함수를 업데이트 하면 리액트도 리렌더링 되고, 상태변수를 업데이트 합니다.
- 지역변수로 작성했던 코드 useState로 바꿔보자
- 위 코드를 실행하면 버튼 클릭 시 onClick 이벤트가 실행되어 handleClick함수가 호출됩니다.
- handleClick함수가 실행되면 setIndex가 실행되는데, 이 setter 함수가 실행됨에 따라 index 변수의 값은 1 더해지고, 컴포넌트가 리렌더링 됩니다.
- index 변수는 상태변수이기에 렌더링시에도 값을 유지하기에 화면에는 초기값 0에 1이 더해진 1이 표시됩니다.
import {useState} from 'React'
function App() {
const [상태변수, setter함수] = useState(상태의 초기값);
const [index, setIndex] = useState(0);
}
2.2 상태 넘겨주기
- 리액트에서 상태를넘겨줄 땐 Props를 이용합니다.
2.2.1 State vs Props
State
부모 컴포넌트에서 자녀 컴포넌트로 데이터를 보내는 것이 아닌 해당 컴포넌트 내부에서 데이터를 전달할 때 State를 이용합니다.
- 예를들어 검색 창에 글을 입력할 때 글이 변하는 것은 State을 바꿉니다.
- State는 변경 가능합니다.
- State가 변하면 re-render 됩니다.
State = {
message: ' ',
attachFile : undefined,
openMenu : false,
};
Props
- Properties의 줄임말입니다.
- Props는 상속하는 부모 컴포넌트로부터 자녀 컴포넌트에 데이터등을 전달하는 방법입니다.
- Props는 읽기 전용으로 자녀 컴포넌트 입장에서는 변하지 않습니다. 변경하고자 하면 부모 컴포넌트에서 state를 변경시켜주어야 합니다.
2.2.2 Props로 전달
const [todoData, setTodoData] = useState({title: 'todo1', time : 1});
<Lists todoDatas={todoData}/> // 이런식으로 props를 넘겨준다
<자녀컴포넌트 이름 자녀컴포넌트에서 사용할 이름 = {부모컴포넌트에서의 이름} />
//List.js todoDatas 객체 통째로 받고
function List (todoDatas) {
return (
<>
//컴포넌트 내에서 직접 접근
<div>할 일 : {todoDatas.name} </div>
<div>걸리는 시간 : {todoDatas.time} </div>
</>
)
}
//List.js, 애초에 받을 때 구조분해 할당으로 받아버려
function List ({name, time}) {
return (
<>
<div>할 일 : {name} </div>
<div>걸리는 시간 : {time} </div>
</>
)
}
3. 그래서 뭐가 문제야 ? (Props Drilling)
- 해당 그림은 Root에서 state를 만들고, 이를 Props Drilling 방식으로 하위 컴포넌트에게 state를 내려주고있습니다.
- 만일 G 컴포넌트에서 상태가 변경되었고, 이를 J 컴포넌트에서 사용해야한다면
- G → E → C → A → Root → H → J 순서로 상태가 전달이 것입니다.
- 코드를 통해서 보면 (참고자료) App > FirstComponent > SecondComponent > ThirdComponent > ComponentNeedingProps
- ComponentNeedingProps 컴포넌트에서 해당 Props를 사용하기 위해선 이렇게 전달하는 과정을 거쳐야 합니다.
import React from "react";
import "./styles.css";
export default function App() {
return (
<div className="App">
<FirstComponent content="Who needs me?" />
</div>
);
}
function FirstComponent({ content }) {
return (
<div>
<h3>I am the first component</h3>;
<SecondComponent content={content} />
</div>
);
}
function SecondComponent({ content }) {
return (
<div>
<h3>I am the second component</h3>;
<ThirdComponent content={content} />
</div>
);
}
function ThirdComponent({ content }) {
return (
<div>
<h3>I am the third component</h3>;
<ComponentNeedingProps content={content} />
</div>
);
}
function ComponentNeedingProps({ content }) {
return <h3>{content}</h3>;
}
3.1 Props Drilling
3.1.1 Props Drilling이란?
- Props Drilling이란 props를 ‘하위 컴포넌트로 전달하는 용도로만 쓰이는’ 컴포넌트를 거치며 React Component 트리의 한 부분에서 다른 부분으로 데이터를 전달하는 과정이다.
- 이는 컴포넌트가 3~4개일 때는 문제가 되지 않지만, 여러 개의 컴포넌트가 있을 때는 문제가 될 수 있다.
3.1.2 Props Drilling의 장점
- 컴포넌트 간에 데이터를 가장 쉽고 빠르게 전달할 수 있다.
- 작은 규모의 application일 경우, 컴포넌트를 잘게 분해해서 props drilling을 한다면, 코드를 실행하지 않고 정적으로 따라가는 것만으로도 어떤 데이터가 어디서 사용됐는 지 파악하기 쉽고 수정도 용이하다
3.1.3 Props Drilling의 문제점
- 필요보다 많은 props를 전달하다가, 컴포넌트 분리하는 과정에서 필요하지 않은 props가 계속 남거나 전달되는 문제
- props 전달을 누락했는데 default props가 사용되어 props의 미전달을 인지하기 어려운 문제
- props의 이름이 전달중에 변경되어 데이터를 추적하기 어려워지는 문제
3.1.4 Props Drilling을 피하려면
- 렌더링 될 컴포넌트를 불필요하게 여러 컴포넌트로 나누지 않는다.
- React는 단 하나의 컴포넌트에 application 전체를 작성하더라도 기술적인 제약이 없다.따라서 불필요한 컴포넌트 쪼개기를 할 필요가 없다.컴포넌트를 재사용해야할 상황을 기다렸다 분할해도 괜찮으며, 불필요한 props drilling을 방지할 수 있다.
- defaultprops를 필수 컴포넌트에 사용하지 않는다.
- deafultProps를 사용하면 필요한 props가 전달되지 못한 상황임에도 오류가 가려지게된다. 따라서 defaultProps를 필수적이지 않은 컴포넌트에만 사용하면 props drilling으로 인한 문제를 막을 수 있다.
- 가능한 관련성이 높은 곳에 state를 위치한다.
- 어떤 데이터가 application의 특정 위치에서만 필요하면 최상위 컴포넌트에 state를 위치시키는게 아닌, 해당 state를 필요로하는 컴포넌트들의 최소 공통 부모 컴포넌트에서 관리하는 것이 효율적이다.
- 상태관리 도구를 사용한다.
- 데이터를 필요로하는 컴포넌트가 props drilling의 깊숙히 위치한다면, React의 Context API를 사용하거나, Redux, Recoil 등의 외부 전역 상태관리 라이브러리를 사용해서 문제를 해결할 수 있다.
- Children을 사용한다. (Legacy API)
- children을 사용하여 리팩토링을 진행하면, 하나의 컴포넌트에서 값을 관리하고, 그 값을 하위요소로 전달할 때 코드추적이 어려워지지 않게됩니다.
- 그러나 리액트 공식문서에도 나와있듯, children은 잘 사용되지 않고 빈약한 코드 (fragile code)로 이어질 수 있습니다.
4. Context
- Context란 application에서 사용할 상태들을 context 내에서 관리하여 보다 컴포넌트들이 편하게 접근 가능하게 합니다.
- 그림으로 알 수 있듯 Context를 사용하면 Props Drilling을 막을 수 있습니다.
4.1 Context 사용방법
- createContext 메서드를 사용해 context를 생성합니다.
- 생성된 context를 가지고 context provider로 컴포넌트 트리를 감쌉니다.
- value props를 사용해 context provider에 원하는 값을 입력합니다.
- context consumer를 통해 필요한 컴포넌트에서 그 값을 불러옵니다.
5. 상태관리 라이브러리란 ?
- 리액트에서 사용하는 데이터를 담는 변수인 State를 전역적으로 관리하는 툴입니다.
- 상태관리 라이브러리에 대한 자세한 설명은 추후 포스팅하도록 하겠습니다.
cf ) 공식문서 보면서 알게된 어휘인데 ‘from scratch’가 처음부터라는 뜻이었다. 그래서 ‘render from scratch’라 하면 ‘처음부터 렌더링된다’ 라고 해석가능합니다.
참고자료
https://react.dev/learn/scaling-up-with-reducer-and-context
https://react.vlpt.us/basic/22-context-dispatch.html
https://redux.js.org/tutorials/essentials/part-1-overview-concepts
'🩵 React' 카테고리의 다른 글
[React/Vite] VITE의 기본 margin 없애기 (1) | 2024.06.13 |
---|---|
[JS] ISO 8601 표준시를 12시간제로 바꾸기 (2) | 2024.06.13 |
[React]Javascript로 잡코리아 채용공고 크롤링하기 (0) | 2024.06.13 |
[React] Vite, TS 환경에서 SVGR을 사용해 SVG를 ReactComponent로 사용하기 (0) | 2024.06.13 |
[React] MSW 공식문서와 함께 MSW 기본세팅 (0) | 2023.08.09 |