[React] React 데이터 흐름

KIM DA MI·2023년 3월 31일
0

React

목록 보기
3/7
post-thumbnail

1. React 데이터 흐름


컴포넌트를 만드는 방법

  • React의 개발 방식은 페이지 단위가 아닌 컴포넌트 단위로 시작하며, 상향식(bottom-up)으로 앱을 만들어가는 것이 특징이다.
  • 이 방식은 테스트가 쉽고 확장성이 뛰어나다는 장점이 있다.
  • 그래서 앱의 디자인을 전달받은 후 가장 먼저해야 하는 일은 컴포넌트 계층 구조로 나누는 것이다.
    • 프로토타입
    • 컴포넌트 단위
  • 이는 단일 책임 원칙에 따른 구분이며, 하나의 컴포넌트는 한 가지 일만 해야 한다.
    • 간단한 트위터 클론 Twittler에서 컴포넌트 디자인을 해보자.
    • 이를 트리 구조로 나타내면 다음과 같다.

데이터는 위에서 아래로 흐른다 (하향식)

  • 컴포넌트는 컴포넌트 바깥에서 props를 이용해 데이터를 마치 전달인자(arguments) 혹은 속성(attributes)처럼 전달받을 수 있다.
  • 이는 데이터 흐름이 하향식(top-down)이라는 것을 의미하며, 이 원칙은 매우 중요하다.
  • 단방향 데이터 흐름이라는 개념은 React를 설명하는 중요한 특징 중 하나이다.
    또한 컴포넌트는 props를 통해 전달받은 데이터가 어디서 왔는지 전혀 알지 못한다.

데이터 정의

  • React 애플리케이션에서 필요한 데이터를 정의할 때, 변하는 값과 변하지 않는 값에 대해 고민해야 한다.
  • 변하는 값은 상태(state)로, 변하지 않는 값은 props로 관리할 수 있다.
  • 상태가 많아질수록 애플리케이션은 복잡해지기 때문에 모든 데이터를 상태로 두는 것은 좋지 않으며, 최소화하는 것이 좋다.
  • 이를 판단하기 위해서는 다음 세 가지 질문을 고려해야 한다.
    1. 부모로부터 props를 통해 전달되는가? ➡ 그러면 확실히 state가 아니다.
    2. 시간이 지나도 변하지 않는가? ➡ 그러면 확실히 state가 아니다.
    3. 컴포넌트 안의 다른 stateprops를 가지고 계산 가능한가? ➡ 그렇다면 state가 아니다.
    • 이러한 기준을 토대로 적절하게 stateprops를 관리하여 React 애플리케이션을 구성할 수 있다.

상태(state) 위치 정하기

  • 특정 컴포넌트에서만 유의미한 상태는 해당 컴포넌트에 위치시키면 되지만, 하나의 상태를 기반으로 두 컴포넌트가 영향을 받는 경우에는 공통 소유 컴포넌트를 찾아 그곳에 상태를 위치시켜야 한다.

"전체 트윗 목록"

  • 예를 들어 앞서 정의한 데이터를 기반으로 위치를 정해보자면,
    "전체 트윗 목록" 상태는 아래와 같은 두 컴포넌트가 의존한다.
    • Tweets : 개별 트윗을 출력하기 위해 필요
    • NewTweetForm : 새 글을 추가하는 이벤트가 발생할 경우 전체 트윗 목록에 새로운 트윗 객체를 추가하기 위해 필요
  • 따라서 두 컴포넌트의 부모인 Twittler에 "전체 트윗 목록" 상태를 위치시킨다.

"작성 중인 트윗 내용"

  • NewTweetForm은 버튼이 눌린 후 완성된 하나의 트윗 객체를 전체 트윗 목록에 전달하기만 하면 되기 때문에 다른 컴포넌트와 공유할 필요가 없다.
    따라서 "작성 중인 트윗 내용"이라는 상태는 NewTweetForm에 두는 것으로 충분하다.

역방향 데이터 흐름 추가

  • 리액트에서는 단방향 데이터 흐름을 갖고 있지만, 부모 컴포넌트에서의 상태가 하위 컴포넌트에 의해 변화되는 상황이 발생할 수 있다.
  • 이때, 이를 해결하기 위해 "State 끌어올리기(Lifting state up)"라는 기술을 사용한다.
    이는 상태를 변경시키는 함수를 하위 컴포넌트에 props로 전달하는 것으로, 마치 콜백 함수를 사용하는 방법과 비슷하다.
  • 이를 통해 하위 컴포넌트에서 발생하는 이벤트를 부모 컴포넌트에서 처리할 수 있게 된다.
    대표적인 예시로는 새로운 트윗 추가가 있다.
    • 하위 컴포넌트(NewTweetForm)에서의 클릭 이벤트가 부모의 상태를 바꾸어야 하는 상황에서 이를 해결하기 위해 State 끌어올리기를 사용할 수 있다.



2. State 끌어올리기 (Lifting State Up)


State 끌어올리기란?

  • React에서 하위 컴포넌트에서 발생한 이벤트로 인해 상위 컴포넌트의 상태(state)를 변경해야 하는 경우, 상위 컴포넌트의 상태를 변경하는 함수(handler)를 하위 컴포넌트로 전달하고,
    하위 컴포넌트에서 이 함수를 실행시켜서 상위 컴포넌트의 상태를 변경하는 패턴을 말한다.
  • 이는 단방향 데이터 흐름의 원칙에 부합하면서도 하위 컴포넌트에서 상위 컴포넌트의 상태를 변경할 수 있는 해결책이다.

예제1.

  • 예제 앱은 부모와 자식 컴포넌트가 하나씩 존재하는 트리 구조이다.
    그리고, 아래와 같은 상태를 변경시킬 수 있는 메서드가 존재한다.

    import React, { useState } from "react";
    
    export default function ParentComponent() {
      const [value, setValue] = useState("날 바꿔줘!");
    
      const handleChangeValue = () => {
        setValue("보여줄게 완전히 달라진 값");
      };
    
      return (
        <div>
          <div>값은 {value} 입니다</div>
          <ChildComponent handleButtonClick={handleChangeValue} />
        </div>
      );
    }
    
    function ChildComponent({ handleButtonClick }) {
      const handleClick = () => {
        // 이 버튼을 눌러서 부모의 상태를 바꿀 순 없을까?
        handleButtonClick();
      };
    
      return <button onClick={handleClick}>값 변경</button>;
    }
    • ParentComponent는 부모 컴포넌트 역할을 하며 상태 값(value)과 상태를 변경하는 함수(handleChangeValue)를 가지고 있다.

    • ChildComponentParentComponent의 자식 컴포넌트 역할을 하며, ParentComponent로부터 전달받은 상태를 변경하는 함수(handleButtonClick)를 버튼 클릭 이벤트에 따라 실행시킨다.

    • 상태를 변경하는 함수(handleChangeValue)는 부모 컴포넌트인 ParentComponent에서 정의하고, 자식 컴포넌트인 ChildComponent에서 실행된다.
      이렇게 상위 컴포넌트에서 하위 컴포넌트로 함수를 전달하여 하위 컴포넌트에서 실행하는 것을 "상태 끌어올리기" 패턴이라고 한다.

    • 결과적으로, ChildComponent에서 버튼을 클릭하면, handleButtonClick 함수가 실행되고, 이는 ParentComponent에서 정의한 handleChangeValue 함수를 실행하게 된다.
      이를 통해 상태가 변경되고, ParentComponent가 다시 렌더링되어 변경된 상태를 화면에 보여줄 수 있게 된다.


예제2. Twittler 예제 분석하기

  • 다음 예제 코드는 작성자와 글 내용을 입력받아 새로운 트윗을 추가하는 기능을 가진 코드이다.

  • 어떻게 <NewTweetForm> 컴포넌트에서 tweets 상태를 변화시킬 수 있을까?
    (어떻게 State 끌어올리기를 적용할 수 있을까?)

    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 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
        };
        // TDOO: 여기서 newTweet이 addNewTweet에 전달되어야 한다.
        onButtonClick(newTweet);
        setNewTweetContent("");
      };
    
      return (
        <div id="writing-area">
          <textarea
            id="new-tweet-content"
            onChange={onTextChange}
            value={newTweetContent}
          ></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;
    
    • 이 앱에서 State 끌어올리기를 적용하기 위해서는 NewTweetForm 컴포넌트에서 입력된 새로운 트윗을 Twittler 컴포넌트의 상태인 tweets 배열에 추가해야 한다.
    • 따라서 Twittler 컴포넌트에서 상태 변경 함수인 addNewTweet을 정의하고, 이 함수를 NewTweetForm 컴포넌트의 props로 전달한다.
    • NewTweetForm 컴포넌트에서는 버튼 클릭 이벤트가 발생할 때, 입력된 내용과 현재 작성자 정보를 포함하는 새로운 트윗 객체를 생성하고, 이를 Twittler 컴포넌트에서 전달받은 addNewTweet 함수를 호출하여 상태를 변경한다.
    • 이를 통해 새로운 트윗이 추가되고, UI가 업데이트 된다.

0개의 댓글