[React] 클라이언트 Ajax 요청 (1) - React 데이터 흐름, State 끌어올리기

선정·2022년 6월 13일
0

Today I Learned

  • React 데이터 흐름
  • State 끌어올리기

React 데이터 흐름

React의 개발 방식의 가장 큰 특징은 페이지 단위가 아닌, 컴포넌트 단위로 시작한다는 것이다. 앱의 디자인이 완성되면 먼저 컴포넌트를 찾아 이를 컴포넌트 계층 구조로 먼저 나눈다. 이때, 단일 책임 원칙에 따라 하나의 컴포넌트는 한 가지 일만 해야 한다. 그리고 페이지를 만들기 이전에 컴포넌트를 먼저 만들고 조립한다. 즉, 상향식(bottom-up)으로 앱을 만든다. 이러한 방식은 테스트가 쉽고 확장성이 좋다.

컴포넌트는 컴포넌트 바깥에서 props를 이용해 데이터를 전달 받을 수 있다. 데이터를 전달하는 주체는 부모 컴포넌트가 된다. 즉, 데이터 흐름은 하향식(top-down)이며 단방향 데이터 흐름(one-way data flow)를 따른다.


데이터는 하향식, 단방향 흐름을 따름

// 자식 컴포넌트
function FormattedDate(props) {
  return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
}

// 부모 컴포넌트
class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  componentDidMount() {
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }

  componentWillUnmount() {
    clearInterval(this.timerID);
  }

  tick() {
    this.setState({
      date: new Date()
    });
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <FormattedDate date={this.state.date} />
      </div>
    );
  }
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Clock />);

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


간단한 Todo 앱

import React, { useState, useRef } from 'react';

// 자식 컴포넌트
function TodoForm(props) {
  const [inputValue, setInputValue] = useState('');
  const todoId = useRef(1);

  const handleSubmit = (e) => {
    e.preventDefault();
    props.onSubmit({ id: todoId.current, todo: inputValue });
    todoId.current++;
    setInputValue('');
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={inputValue}
        onChange={(e) => setInputValue(e.target.value)}
      />
      <button>등록</button>
    </form>
  );
}

// 부모 컴포넌트
function TodoList() {
  const [todos, setTodos] = useState([]);

  const onSubmit = (newTodo) => {
    setTodos((prev) => [newTodo, ...prev]);
  };

  return (
    <>
      <TodoForm onSubmit={onSubmit} />
      <ul>
        {todos.map((todo) => (
          <li key={todo.id}>{todo.todo}</li>
        ))}
      </ul>
    </>
  );
}

export default TodoList;

위의 간단한 Todo 앱을 보면 TodoForm 컴포넌트에서 등록 버튼을 누르면 TodoList 컴포넌트의 todos 상태가 변경된다.
앞서 리액트는 단방향 데이터 흐름을 가지고 있다고 했는데 부모 컴포넌트(TodoList)에서의 상태가 하위 컴포넌트(TodoForm)에 의해 변하고 있는 것이다.

상태를 변경시키는 함수를 하위 컴포넌트에 props로 전달하고 이 함수를 하위 컴포넌트가 실행시키고 있는 것이다. 이를 State 끌어올리기(Lifting state up)라고 한다.


React 공식문서 참고



State 끌어올리기 (Lifting State Up)

단방향 데이터 흐름이라는 원칙에 따라, 하위 컴포넌트는 상위 컴포넌트로부터 전달받은 데이터 형태 혹은 타입이 무엇인지만 알 수 있다. 데이터가 state로부터 왔는지, 하드코딩으로 입력한 내용인지 모른다.

그러므로 하위 컴포넌트에서의 어떤 이벤트로 인해 상위 컴포넌트의 상태가 바뀌는 것은 마치 "역방향 데이터 흐름"과 같이 이상하게 느껴진다. React가 제시하는 해결책은 다음과 같다.

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

이는 단방향 데이터 흐름의 원칙에 부합한다. 이것이 "상태 끌어올리기"이다.


import React, { useState } from "react";

export default function Parent() {
  const [value, setValue] = useState("초기값")
  
  const handleChangeValue = () => {
    setValue("변경된 값");
  };
  
  return (
    <div>
      <div>{value}</div>
      <Child handleBtnClick={handleChangeValue} />
    </div>
  )
};

function Child({handleBtnClick}) {
  const handleClick = () => {
    handleBtnClick();
  };
  
  return <button onClick={handleClick}>값 변경</button>
}
profile
starter

0개의 댓글