React - State 끌어올리기

0

React / React-Native

목록 보기
4/6
post-thumbnail

React를 사용해서 State를 끌어올리는 개념을 이해해보자!

데이터 흐름

트위터와 같은 간단한 SNS를 예로 들어보자

일반적인 트위터를 트리구조로 표현하면 위와 같다.
이 중에서 변하지 않는 값은 무엇일까?
사용자가 입력을 실시간을 하는 Tweet Form의 경우 계속해서 상태가 변하므로 상태, 즉 State라고 볼 수 있다. 또한 기존 Tweets 목록의 경우 새로운 Tweet들이 계속 입력될 경우 상태가 변하므로 State라고 볼 수 있다.

그러면 모든 데이터들을 State로 두게 되는데, 그럴 필요는 없다. State는 적으면 적을수록 좋기 때문... 너무 많으면 애플리케이션 상태가 매우 복잡해진다.

State의 조건

부모로부터 props를 통해 전달되는가? -> State 아님
시간이 지나도 변하지 않는가? -> State 아님
컴포넌트 안의 다른 State나 Props를 가지고 계산이 가능한가? -> State 아님

그렇다면 상태를 도대체 어디다가 배치하라는건가...
만일 상태가 특정 컴포넌트에서만 유의미하다면, 해당 컴포넌트에만 상태를 두면 되니까 어려운 일이 아니지만, 하나의 상태를 기반으로 두 컴포넌트가 영향을 받는다면, 공통 소유 컴포넌트를 찾아 그곳에 상태를 위치하는 것이 베스트이다. 즉 해당 컴포넌트들의 부모에 상태를 위치시키면 된다는 말씀

자 그러면 다시 위의 그림을 보자. 트윗목록 State를 어디에다 위치시키면 될까? 바로 최상단 부모인 '트위터'에 위치시키면 된다.
그리고 작성중인 트윗 State는 어디에다가 하면 좋을까? 실시간 작성중인 Tweet의 경우 기존 트윗 목록들에 영향을 주지 않으므로 작성중인 트윗 State는 그냥 New Tweet Form에 두어도 충분하다.

상태 위치를 정하고 보니, New Tweet Form 컴포넌트로 인해 그 위의 부모 컴포넌트의 상태가 변경되는 점이 보인다. 하위 컴포넌트에 의해 상위 컴포넌트의 상태가 변경된다...이는 React 문법에 위배된다.(단방향 데이터 흐름)
이를 해결하는 것이 바로 "State 끌어올리기" 이다.

State 끌어올리기

단방향 데이터 흐름 원칙을 따른다면, 하위 컴포넌트는 상위 컴포넌트의 데이터의 형태나 타입만 알 수 있고, 데이터가 State로부터 왔는지 아니면 하드코딩인지의 여부는 알지 못한다. 그렇기에 하위 컴포넌트의 이벤트로 인해 상위 컴포넌트의 상태가 바뀐다? 이건 매우 어색하게 보인다..
React는 이에 대한 해결책으로 '상위 컴포넌트의 상태 변경 함수 그 자체를 하위 컴포넌트로 전달하고, 이 함수를 하위 컴포넌트가 실행하는 것'이라고 말하며, 이를 통해 이건 여전히 단방향 데이터 흐름 원칙에 부합한다는 것으로 말했다. 이것이 바로 '상태 끌어올리기'이다.

예제

function ParentComponent() {
  const [value, setValue] = useState("날 바꿔줘!");

  const handleChangeValue = () => {
    setValue("보여줄게 완전히 달라진 값");
  };

  return (
    <div>
      <div>값은 {value} 입니다</div>
      <ChildComponent handleButtonClick={handleChangeValue}  />
    </div>
  );
}
function ChildComponent({ handleButtonClick }) {
  const handleClick = () => {
    // Q. 이 버튼을 눌러서 부모의 상태를 바꿀 순 없을까?
    handleButtonClick()
    //해결! 인자로 받은 상태 변경 함수를 실행하자!
  }

  return (
    <button onClick={handleClick}>값 변경</button>
  )
}

트위터 비슷한 SNS 예제

import React, { useState } from "react";
import "./styles.css";

const currentUser = "김코딩";

function Twittler() {
  const [tweets, setTweets] = useState([
    {
      uuid: 1,
      writer: "김코딩",
      date: "2020-10-10",
      content: "안녕 리액트"
    },
    {
      uuid: 2,
      writer: "박해커",
      date: "2020-10-12",
      content: "좋아 코드스테이츠!"
    }
  ]);

  const addNewTweet = (newTweet) => {
    setTweets([...tweets, newTweet]);
  }; // 이 상태 변경 함수가 NewTweetForm에 의해 실행되어야 합니다.

  return (
    <div>
      <div>작성자: {currentUser}</div>
      <NewTweetForm />
      {/*#########<NewTweetForm onButtonClick = {addNewTweet} /> 로 변경하기#########*/}
      <ul id="tweets">
        {tweets.map((t) => (
          <SingleTweet key={t.uuid} writer={t.writer} date={t.date}>
            {t.content}
          </SingleTweet>
        ))}
      </ul>
    </div>
  );
}

function NewTweetForm({ onButtonClick }) {
  const [newTweetContent, setNewTweetContent] = useState("");

  const onTextChange = (e) => {
    setNewTweetContent(e.target.value);
  };

  const onClickSubmit = () => {
    let newTweet = {
      uuid: Math.floor(Math.random() * 10000),
      writer: currentUser,
      date: new Date().toISOString().substring(0, 10),
      content: newTweetContent
    };
    //########onButtonClck(newTweet) 추가하기########
    //newTweet값이 addNewTweet으로 전달되어야 하는데, 그러기 위해서는 인자로 받은 상태 변경 함수를 이용한다!
  };

  return (
    <div id="writing-area">
      <textarea id="new-tweet-content" onChange={onTextChange}></textarea>
      <button id="submit-new-tweet" onClick={onClickSubmit}>
        새 글 쓰기
      </button>
    </div>
  );
}

function SingleTweet({ writer, date, children }) {
  return (
    <li className="tweet">
      <div className="writer">{writer}</div>
      <div className="date">{date}</div>
      <div>{children}</div>
    </li>
  );
}

export default Twittler;

상위 컴포넌트에서 상태변경함수를 하위 컴포넌트인 NewTweetForm에 Props로 전달해주고, 하단에 있는 onClickSubmit에서 부모 컴포넌트 상태를 변경할 수 있는 상태 변경 함수를 사용할 수 있도록 넘겨받은 Props인 onButtonClick을 이용해서 newTweet을 인자로 넘겨주어 새 트윗이 생성되게 한다. 전형적인 State 끌어올리기의 예제이다.🫡🫡

0개의 댓글