[React] 상태관리? 그게 뭔데

이효린·2023년 8월 9일
0

React

목록 보기
3/8
post-thumbnail

상태관리? 그게 뭔데

💡 이 글은 React beta 공식문서와 많은 구글링을 정제하여 쓴 글입니다.

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>
    </>
  );
}
  • 이렇게 지역변수의 값을 바꿔주는 것으로는 우리가 의도한 것처럼 숫자가 올라가지 않는다. 그 이유로는 두 가지가 있다.
  • 첫 째, 지역변수는 렌더링 되면 유지되지 않는다. 따라서 리액트가 이 컴포넌트를 두 번 렌더링 한다면, 렌더링 이전에 변경된 지역변수를 고려하지 않고 처음부터 다시 렌더링 한다.
  • 둘 째, 지역변수가 변경된다고 해서 렌더링이 이뤄지지 않는다. 리액트는 새로운 데이터와 함께 컴포넌트를 렌더링할 필요를 느끼지 못한다.

위와 같은 이유로 우리는 버튼 클릭시 숫자가 업데이트 되도록 하려면

  1. 버튼 클릭시 리렌더링 되어야 한다.
  2. 리렌더링 되면서도 컴포넌트가 숫자 값을 기억하고 업데이트 할 수 있어야 한다.
💡 이러한 상황에서 State를 이용하면 된다 !! 이러한 State는 useState 라는 훅을 통해 상태관리가 가능하다.

2. useState은 뭔데 ?

  • React Hook이란 React 버전 16.8부터 제공하는 요소이며, 기존 class 바탕의 코드를 작성할 필요 없이 여러 기능을 사용할 수 있다.
  • useState는 이러한 Hook 중 하나이며, 상태 관리할 때 사용되는 Hook이다.

2.1 useState 사용하기

  • useState는 두 가지 요소로 구성된다고 볼 수 있다.
    import {useState} from 'React'
    
    function App() {
    	const [상태변수, setter함수] = useState(상태의 초기값);
    	const [index, setIndex] = useState(0);
    }
    • 첫 째로 상태변수(위 예시에선 index)인데, 상태변수는 렌더링시에도 데이터를 잃지 않고 유지한다.
    • 둘 째로 setter함수(위 예시에선 setIndex)인데, setter 함수를 업데이트 하면 리액트도 리렌더링 되고, 상태변수를 업데이트 한다.
  • 지역변수로 작성했던 코드 useState로 바꿔보자
    import {useState} from 'React'
    
    export default function Count() {
      const [index, setIndex] = useState(0);
    
      function handleClick() {
    		setIndex(index + 1);
      }
      return (
        <>
          <button onClick={handleClick}>
            +1
          </button>
          <h3>  
            {index}
          </h3>
        </>
      );
    }
    • 위 코드를 실행하면 버튼 클릭 시 onClick 이벤트가 실행되어 handleClick함수가 호출된다.
    • handleClick함수가 실행되면 setIndex가 실행되는데, 이 setter 함수가 실행됨에 따라 index 변수의 값은 1 더해지고, 컴포넌트가 리렌더링 된다.
    • index 변수는 상태변수이기에 렌더링시에도 값을 유지하기에 화면에는 초기값 0에 1이 더해진 1이 표시된다.

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)

Untitled

  • 해당 그림은 Root에서 state를 만들고, 이를 Props Drilling 방식으로 하위 컴포넌트에게 state를 내려주고있다.

  • 만일 G 컴포넌트에서 상태가 변경되었고, 이를 J 컴포넌트에서 사용해야한다면

  • G → E → C → A → Root → H → J 순서로 상태가 전달이 것이다.

  • 코드를 통해서 보면 (참고자료)

    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>;
    }

    App > FirstComponent > SecondComponent > ThirdComponent > ComponentNeedingProps

    ComponentNeedingProps 컴포넌트에서 해당 Props를 사용하기 위해선 이렇게 전달하는 과정을 거쳐야 한다.

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

Untitled

  • Context란 application에서 사용할 상태들을 context 내에서 관리하여 보다 컴포넌트들이 편하게 접근 가능하게 한다.
  • 그림으로 알 수 있듯 Context를 사용하면 Props Drilling을 막을 수 있다.

4.1 Context 사용방법

  1. createContext 메서들르 사용해 context를 생성한다
  2. 생성된 context를 가지고 context provider로 컴포넌트 트리를 감싼다.
  3. value props를 사용해 context provider에 원하는 값을 입력한다.
  4. context consumer를 통해 필요한 컴포넌트에서 그 값을 불러온다.

5. 상태관리 라이브러리란 ?

  • 리액트에서 사용하는 데이터를 담는 변수인 State를 전역적으로 관리하는 툴이다.

cf ) 공식문서 보면서 알게된 어휘인데 ‘from scratch’가 처음부터라는 뜻이었다. 그래서 ‘render from scratch’라 하면 ‘처음부터 렌더링된다’ 라고 해석가능하다.

참고자료

https://devowen.com/459

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

https://slog.website/post/13

https://codingpracticenote.tistory.com/56

0개의 댓글