리액트(3)

수민·2022년 12월 6일
0

code

목록 보기
21/47

프로토타입에서 그림과 같이 컴포넌트를 찾아냈습니다. 이렇게 먼저 컴포넌트를 만들고, 다시 페이지를 조립해나갑니다.

즉, 상향식(bottom-up)으로 앱을 만듭니다. 이것의 가장 큰 장점은 테스트가 쉽고 확장성이 좋습니다.
그래서 여러분이 기획자나 PM, 또는 UX 디자이너로부터 앱의 디자인을 전달받고 나면, 이를 컴포넌트 계층 구조로 나누는 것이 가장 먼저 해야 할 일입니다.

여기에 그림과 같은 형태로 컴포넌트 디자인을 해볼 것입니다. 왜 이런 식으로 나누었냐고요? 이는 단일 책임 원칙에 따른 구분입니다. 하나의 컴포넌트는 한 가지 일만 합니다.

이를 트리 구조로 나타내면 다음 그림과 같습니다.
컴포넌트를 만드는 방법은 앞서 배웠습니다. 이제는 데이터를 어디에 둘지를 결정합시다.


컴포넌트는 컴포넌트 바깥에서 props를 이용해 데이터를 마치 전달인자(arguments) 혹은 속성(attributes)처럼 전달받을 수 있습니다.

즉 데이터를 전달하는 주체는 부모 컴포넌트가 됩니다. 이는 데이터 흐름이 하향식(top-down)임을 의미합니다.

이 원칙은 매우 중요합니다. 얼마나 중요하냐면, 단방향 데이터 흐름(one-way data flow)이라는 키워드가 React를 대표하는 설명 중 하나일 정도입니다.
또한 컴포넌트는 props를 통해 전달받은 데이터가 어디서 왔는지 전혀 알지 못합니다.

애플리케이션에서 필요한 데이터가 무엇인지 먼저 정의합시다. 다음과 같은 데이터를 생각해볼 수 있습니다.

  • 전체 트윗 목록
  • 사용자가 작성 중인 새로운 트윗 내용

이 중에 변하는 값과 변하지 않는 값은 무엇인가요? 확실히 사용자 입력은 이벤트에 따라 얼마든지 변할 수 있습니다. 이것은 상태(state)입니다. 트윗 목록이 추가 또는 삭제될 여지가 없다면 사실 state로 둘 필요가 없습니다. 그러나, 우리는 새 트윗 추가라는 기능을 염두에 두고 있으므로, 트윗 목록 역시 상태(state)입니다.

상태를 어디에 위치시켜야 하는지 살펴봅시다.

상태가 특정 컴포넌트에서만 유의미하다면, 특정 컴포넌트에만 두면 되니까 크게 어렵지 않지만, 만일 하나의 상태를 기반으로 두 컴포넌트가 영향을 받는다면 이때에는 공통 소유 컴포넌트를 찾아 그곳에 상태를 위치해야 합니다.

즉, 두 개의 자식 컴포넌트가 하나의 상태에 접근하고자 할 때는 두 자식의 공통 부모 컴포넌트에 상태를 위치해야 합니다.

앞서 정의한 데이터를 기반으로 위치를 정해봅시다. "전체 트윗 목록" 상태는 어디에 위치하는 것이 좋을까요?
전체 트윗 목록은, Tweets에서 필요로 하는 데이터입니다.

그런데, 새 글을 추가하는 이벤트가 발생할 경우, 이때 전체 트윗 목록에 새로운 트윗 객체를 추가할 수 있어야 합니다. 즉, 두 컴포넌트 모두 트윗 목록에 의존합니다.

그렇다면, 두 컴포넌트의 부모는 무엇인가요? 바로 Twittler입니다. 전체 트윗 목록 상태는 여기에 위치합시다.

"사용자가 작성 중인 새로운 트윗 내용"이라는 상태를 위치해봅시다.

NewTweetForm에서는 사용자가 트윗 내용을 작성할 수 있습니다. 사용자 입력에 따라 값이 변하므로 "작성 중인 트윗 내용"은 상태입니다. 그럼 Tweets나 다른 컴포넌트가 작성중인 내용을 가질 필요가 있나요?

NewTweetForm은 그저 버튼이 눌린 후 완성된 하나의 트윗 객체를 전체 트윗 목록에 전달하기만 하면 됩니다. 입력에 따라 실시간으로 다른 컴포넌트가 변한다면 모를까, 여기에서는 그렇지 않으므로 다른 컴포넌트와 공유할 필요가 없습니다. "작성 중인 트윗 내용"이라는 상태는 NewTweetForm에 두는 것으로 충분합니다.

앞서 리액트는 단방향 데이터 흐름을 갖고 있다고 말씀드렸습니다. 그런데, 갑자기 웬 역방향 데이터 흐름이라는 이야기가 나올까요?

상태 위치를 전부 정하고 나서 생각해보니, 부모 컴포넌트에서의 상태가 하위 컴포넌트에 의해 변하는 것을 발견할 수 있을 것입니다.
바로 새로운 트윗 추가가 대표적인 예죠. 버튼을 통한 이 액션은, 부모의 상태를 변화시켜야 합니다.

하위 컴포넌트(NewTweetForm)에서의 클릭 이벤트가, 부모의 상태를 바꾸어야만 하는 상황이 왔습니다. 이를 어떻게 해결할 수 있을까요?
이를 해결할 수 있는 키워드는 바로 "State 끌어올리기(Lifting state up)"입니다.

결론부터 말하자면, 이는 상태를 변경시키는 함수(handler)를 하위 컴포넌트에 props로 전달해서 해결할 수 있습니다. 이는 마치 콜백 함수를 사용하는 방법과 비슷합니다.
그럼 다음 강의에서 본격적으로 상태 끌어올리기를 배워봅시다.

예제앱

예제 앱은 부모와 자식 컴포넌트가 하나씩 존재하는 트리 구조입니다. 그리고, 상태를 변경시킬 수 있는 메서드가 존재한다고 생각해 보세요. 다음과 같은 구성을 가지게 될 것입니다.




import React, { useState } from "react";

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

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

  return (
    <div>
      <div>값은 {value} 입니다</div>
      <ChildComponent />
    </div>
  );
}

function ChildComponent() {
  const handleClick = () => {
    // 이 버튼을 눌러서 부모의 상태를 바꿀 순 없을까?
  };

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

callback 다시보기

콜백(callback)은 다른 함수(고차 함수)의 인자로 전달되는 함수를 의미합니다.


// 고차함수
function each(array, iterator) {
  for(let i = 0; i < array.length; i++) {
    let element = array[i]
    iterator(element, i, array)
  }
}

// 콜백 함수
function printElement(element) {
  console.log(element)
}

each(['hello', 'world'], printElement);

앞서 React의 해결책은 다음과 같다고 말했습니다.

상위 컴포넌트의 "상태를 변경하는 함수" 그 자체를 하위 컴포넌트로 전달하고, 이 함수를 하위 컴포넌트가 실행한다

다시 React 예제로 돌아가서, 상태를 변경하는 함수는 무엇인가요? 하위 컴포넌트로는 어떻게 전달할 수 있을까요?

상태를 변경하는 함수는 handleChangeValue 입니다. 전달은 props를 이용합시다. props 이름은 적절하게 지어줍시다. 하위 컴포넌트가 버튼 클릭 이벤트에 따라 상태를 변경하려고 하므로 이름은 handleButtonClick이라고 지어줍시다.

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

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

  return (
    <div>
      <div>값은 {value} 입니다</div>
      <ChildComponent handleButtonClick={handleChangeValue}  />
    </div>
  );
}

는 마치 고차 함수가 인자로 받은 함수를 실행하듯, props로 전달받은 함수를 컴포넌트 내에서 실행할 수 있게 됩니다. "상태 변경 함수"는 버튼이 클릭할 때 실행되기를 원하므로, 해당 부분에 콜백 함수를 실행합시다.


function ChildComponent({ handleButtonClick }) {
const handleClick = () => {
  // Q. 이 버튼을 눌러서 부모의 상태를 바꿀 순 없을까?
  // A. 인자로 받은 상태 변경 함수를 실행하자!

  handleButtonClick()
}

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

필요에 따라 설정할 값을 콜백 함수의 인자로 넘길 수도 있습니다.

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

const handleChangeValue = (newValue) => {
  setValue(newValue);
};

// ...생략...
}

function ChildComponent({ handleButtonClick }) {
const handleClick = () => {
  handleButtonClick('넘겨줄게 자식이 원하는 값')
}

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


profile
헬창목표

0개의 댓글