[패스트캠퍼스] 프론트엔드 취업 완성 과정 3기 - React의 상태 끌어올리기(State Lifting)

JYROH·2022년 12월 30일
0
post-thumbnail

React 강의

현재 Udemy에서 React 완벽 가이드 with Redux, Next.js, TypeScript 라는 강의를 수강중에 있다. 무려 40시간에 이르는 시간이지만 내가 밥벌어먹고 살 기술인데 이정도 내용과 길이는 있어야할것 같아 골랐다. 또한, 기본적인 개념도 다 설명해주고, 또한 필수적으로 이용할줄알아야하는 라이브러리들까지 세세하게 알려주는것이 마음에 들었습니다!

지금은 강의 예제를 따라 만들고있는데 React가 상당히 낯설고 어렵습니다... 그런데, State만 변경되면 모든것이 알아서 이루어지는것이 너무나 신기하고 슬슬 편해지려고 하는것 같습니다 👍

상태 끌어올리기

지금까지 공부중 가장 낯설고 어려웠었던 상태 끌어올리기(State lifting)에 대해서 알아보려고 합니다.

대부분의 리액트의 파일들은 컴포넌트로 이루어져있습니다.

컴포넌트가 각 기능을 이루는 가장 작은 단위이고 부모-자식 간의 관계로 컴포넌트끼리의 통신을 수행합니다. 또한, 리액트는 단방향(하향식) 데이터 흐름으로 state관리를 하는데, 이런 방식으로는 부모로부터 자식방향으로 데이터를 전달할수있습니다. 이 방식은, 직관적이고 더 유지보수를 편리하게 할 수 있습니다. 또한, 리액트의 가상DOM방식과 궁합이 잘맞는데, DOM 전체를 갱신하지않고 diffing, Reconcilation의 과정을 통해 렌더링을 하기 때문입니다.

그렇다면 자식에서 부모로 정보 전달을 어떻게 해야할까요??

제가 수행하고 있는 예제를 예시로 들어보겠습니다.

// Expenses.jsx
const Expenses = (props) => {
  const [filteredYear, setFilteredYear] = useState("2022");

  const handleFilterTrigger = (year) => {
    setFilteredYear(year);
  };

  const filteredExpenses = props.items.filter((item) => {
    return item.date.getFullYear() == Number(filteredYear);
  });

  return (
    <Card className="expenses">
      {/* selected로 값을 내려주어 양방향 바인딩. 이렇게 하면 ExpenseFilter에서 초기값 설정 가능. */}
      <ExpenseFilter
        onFilterTrigger={handleFilterTrigger}
        selected={filteredYear}
      />
      <ExpensesChart expenses={filteredExpenses} />
      <ExpensesList items={filteredExpenses} />
    </Card>
  );
};

export default Expenses;

Expenses 라는 부모 컴포넌트 입니다. 해당 컴포넌트에는 ExpenseFilterExpenseChart, ExpenseLists 컴포넌트가 들어있습니다.

현재 프로젝트의 모습입니다. 다른것은 제쳐두고 빨간 상자를 볼수있는데, 저 부분이 바로 ExpenseFilter 컴포넌트 입니다.

// ExpenseFilter.jsx
const ExpenseFilter = (props) => {
  const handleFilterChange = (event) => {
    props.onFilterTrigger(event.target.value);
  };
  return (
    <div className="expenses-filter">
      <div className="expenses-filter__control">
        <label>Filter by year</label>
        {/* 양방향 바인딩 */}
        <select onChange={handleFilterChange} value={props.selected}>
          <option value="2022">2022</option>
          <option value="2021">2021</option>
          <option value="2020">2020</option>
          <option value="2019">2019</option>
        </select>
      </div>
    </div>
  );
};

export default ExpenseFilter;

ExpenseFilter 컴포넌트 에서는 select와 option 태그를 활용하여 연도를 선택할 수 있습니다. 지금 필요한 로직은

  1. ExpenseFilter에서 선택된 select값을 부모 Expenses 컴포넌트로 보내야한다.
  2. 해당 연도값을 토대로 Expenses에서 filter 로직을 수행하고, 해당 filter된 리스트를 ExpensesList컴포넌트로 내려주어 해당 연도에 맞는 element들만 출력해주어야 합니다.

어떠한 값을 자식으로 내려주는 것은 props를 통하여 할 수 있습니다. 그렇다면 자식의 값이 부모에서 사용해야할때는 어떻게 해야할까요??

마찬가지로 props를 사용하면 됩니다.

//Expenses.jsx
const [filteredYear, setFilteredYear] = useState("2022");

~~~
  
const handleFilterTrigger = (year) => {
    setFilteredYear(year);
  };

~~~
  
<ExpenseFilter
        onFilterTrigger={handleFilterTrigger}
        selected={filteredYear}
/>

Expenses컴포넌트를 보면 handleFilterTrigger라는 함수를 볼 수 있습니다. 해당 함수는 useState를 활용하여 연도 state값을 변경시켜주는 함수 입니다. 또한 그 함수를 onFilterTrigger라는 사용자 지정 props를 통하여 ExpenseFilter자식 컴포넌트로 내려주고 있습니다. 감이 오실까요...?

///ExpenseFilter.jsx
const handleFilterChange = (event) => {
    props.onFilterTrigger(event.target.value);
  };

~~~

<select onChange={handleFilterChange} value={props.selected}>

ExpenseFilter 컴포넌트에서 작업 한것을 보면, select option이 변경되면 onChange이벤트 핸들러를 통하여 handleFilterChange를 호출하고, 해당 함수에서는 props를 통해 전달받은 onFilterTrigger props에 event.target.value를 전달합니다. 그렇다면 이 value값은 onFilterTrigger props가 지칭하고 있는 부모 컴포넌트 즉, ExpenseshandleFilterTrigger를 실행하게 됩니다. 당연히 파라미터도 그대로 사용할수있습니다.

정리하자면

부모가 자식의 정보가 필요한경우(or 자식이 부모의 정보를 변경해야하는 경우), props를 통해 부모의 state를 관리하는 함수를 내려보내고, 자식 컴포넌트에서는 해당 함수를 사용하여 부모의 state를 변경하면 되는것입니다.

이렇게 된다면, 리액트의 단방향흐름은 여전히 유지하되, 자식에서도 부모에 접근할수있는 방법을 사용할 수 있습니다. 이러한 방법을 상태 끌어올리기라고 합니다.

profile
안녕하세요 노준영입니다.

1개의 댓글

comment-user-thumbnail
2023년 1월 5일

글 잘 읽었습니다!

답글 달기