티스토리 뷰

1. 리액트의 동작원리

 리액트는 DOM을 직접 제어하는 방식이 아니라 중간에 가상의 Virtual DOM을 둔다. Virtual DOM은 실제 DOM의 구조와 비슷한, React 객체 트리다. 개발자는 직접 DOM을 제어하지 않고 Virtual DOM을 제어하고, React에서 적절하게 Virtual DOM을 DOM에 반영하는 작업을 하게 된다.

 

 DOM에 엘리먼트 렌더링하기

 ReactDOM.render(
    React.createElement(App)
  , document.getElementById('root'));

 

ReactDOM.render

ReactDOM.render(element, container[, callback])

React 엘리먼트를 container DOM에 렌더링하고 컴포넌트에 대한 참조를 반환한다.

React 엘리먼트가 이전에 container 내부에 렌더링 되었다면 해당 엘리먼트는 업데이트하고 최신의 React 엘리먼트를 반영하는 데 필요한 DOM만 변경한다. 추가적인 콜백이 제공된다면 컴포넌트가 렌더링되거나 업데이트된 후 실행된다.

 

React.createElement()

React.createElement(type, [props], [...children])

인자로 주어지는 타입에 따라 새로운 React 엘리먼트를 생성하여 반환한다. type 인자로는 태그 이름 문자열('div' 또는 'span' 등), React 컴포넌트 타입, 또는 React Fragment 타입 중 하나가 올 수 있다. JSX로 작성된 코드는 React.createElement()를 사용하는 형태로 변환된다. JSX를 사용할 경우 React.createElement()를 직접 호출하는 일은 거의 없다.

 

 

2. JSX

JSX는 HTML 문법과 자바스크립트 문법이 결합된 문법이라고 볼 수 있다. JSX는 다음과 같은 특징을 가진다.

 

  • React 엘리먼트(element) 생성
  • JavaScript를 확장한 문법

JSX는 기본적으로 위에서 살펴본 React.createElement의 syntactic sugar이다. 

<div className="greeting" color={getColor()}>{isNew ? 'Nice to Meet you' : 'Hello'}</div>

위의 JSX 코드를 babel로 실행하면 다음과 같이 변한다.

// babel 실행 결과
React.createElement("div", {
    className: "greeting",
    color: getColor()
  }, isNew ? 'Nice to Meet you' : 'Hello');
}

JSX에서 중괄호를 열면 자바스크립트의 표현식을 사용할 수 있다. 여기서 중요한 것은 값과 표현식만 중괄호에 넣을 수 있다는 것이다. 그 이유는 babel 실행 결과를 보면 알 수 있다. 중괄호 안의 코드가 "color={getColor()}" 처럼 HTML의 어트리뷰트 위치에 있다면 React.createElement에서 객체로 묶인다. 객체 프로퍼티의 value는 값과 표현식만을 받을 수 있기 때문에 문은 사용할 수 없다. 또한 HTML 태그의 콘텐츠에 위치한 중괄호는 값을 가져야하기 때문에 마찬가지로 값과 표현식만이 사용될 수 있다. 따라서 중괄호 안에는 자바스크립트의 값과 표현식만이 올 수 있다.

 

 

3. 제어컴포넌트와 비제어 컴포넌트

제어 컴포넌트(Controlled Component)

  • React에 값이 완전히 제어되는 Input과 Form Element
  • State를 값으로 넘기고 그 State을 다룰 수 있는 핸들러를 콜백으로 넘긴다.
  • input의 값은 항상 React의 state 값과 동일하다. (신뢰 가능한 단일 출처 - Single source of truth)

 

비제어 컴포넌트(Uncontrolled Component)

  • 전통적인 HTML처럼 DOM에 제어되는 Input Element
  • 오직 사용자만 값과 상호작용
  • ref를 사용하여 직접 DOM의 값을 가져온다.

 

일반적으로 제어 컴포넌트의 사용이 권장된다.

제어 컴포넌트를 사용해야 다양한 interaction을 주기가 편리하기 때문이다.

 

 

Controlled and uncontrolled form inputs in React don't have to be complicated

There are many articles saying you should use setState, and the docs claim refs are bad. So contradictory. How are you supposed to make forms?

goshakkk.name

 

 

4. props를 변경하지 못하는 이유

props는 읽기 전용입니다. 함수 컴포넌트나 클래스 컴포넌트 모두 컴포넌트의 자체 props를 수정해서는 안 됩니다. (리액트 공식문서 중)

 

리액트에서는 왜 props를 변경하지 못하게 하는 걸까?

 

리액트에서 컴포넌트는 다음과 같은 총 네 가지의 경우에 업데이트된다.

 

  1. props가 바뀔 때
  2. state가 바뀔 때
  3. 부모 컴포넌트가 리렌더링 될 때
  4. this.forceUpdate로 강제로 렌더링을 트리거할 때 

 

 기본적으로 리액트에서는 setState만을 사용하여 state를 변경하게 한다. 이는 상태가 변경될 때 리액트에게 리렌더링을 하라는 명령을 요청하기 위함이다. 즉, setState는 리액트에게 상태가 변경되었으니 리렌더링을 하라는 트리거이다. 하지만 props를 단순하게 할당연산자로 변경하는 경우 setState를 사용하지 않았기 때문에 props가 바뀌었음에도 불구하고 리액트가 상태가 변경됐는지 알아채지 못해 리렌더링을 하지 않는다. 만약 부모 컴포넌트의 state를 자식 컴포넌트에서 변경하고 싶다면 state 끌어올리기를 사용하면 된다. state 끌어올리기는 부모 컴포넌트의 state를 변경하는 함수(setState)를 자식 컴포넌트에게 props로 넘겨 자식 컴포넌트에서 부모의 state를 변경하게 하는 것이다.

 

 

리액트에서는 위와 같은 이유로 props의 변경을 원천 차단하고 있다.

import React from "react";

export default class Test extends React.Component {
  render() {
    this.props.name = "Lee";
    // TypeError: Cannot assign to read only property 'name' of object '#<Object>'
    return (
      <div>
        <h1>name: {this.props.name}</h1>
        <h1>age: {this.props.age}</h1>
      </div>
    );
  }
}

자식 컴포넌트에서 props를 변경하려고 하면 프로그램이 에러를 뱉으며 죽어버린다.

import React from "react";

export default class Test extends React.Component {
  render() {
    console.log(Object.getOwnPropertyDescriptor(this.props, 'name'))
    // {value: "kim", writable: false, enumerable: true, configurable: false}
    return (
      <div>
        <h1>name: {this.props.name}</h1>
        <h1>age: {this.props.age}</h1>
      </div>
    );
  }
}

객체의 속성을 보면 writable이 false임을 알 수 있다. 즉, 할당 연산자로 변경 자체가 불가능하게 설정되어 있다.

 

5. 컴포넌트를 어떻게 나눌 것인가?

리액트를 사용하면서 가장 어려웠던 부분을 꼽으라면 컴포넌트와 디렉토리 구조를 어떻게 가져가야 하는가였다.

내 나름대로의 컴포넌트에 대한 정의를 내리자면 컴포넌트는 props와 state를 관리하여 화면에 보여지는 UI를 담당하는 기본 단위라고 말할 수 있을 것 같다. 그렇다면 기본 단위는 어떻게 가져가야할까? 바로 재사용성이다. 컴포넌트를 재사용성이 높은 작은 단위로 가져가야 다양한 곳에서 컴포넌트를 재사용 할 수 있다. 하지만 컴포넌트를 얼마나 작게 가져가야하는지 그리고 어떤 컴포넌트가 재사용성이 높은지에 대해서는 경험이 필요한 것 같다. 이번 미션은 기능이 간단하여 재사용성 보다는 컴포넌트를 기능 유형 단위로 분리하여 진행하였다.

 

6. 성급한 최적화

Jbee님의 답변

 

'When to useMemo and useCallback' 를 읽고

Kent C. Dodds의 'When to useMemo and useCallback' 을 읽고 복습한 개념 정리

rinae.dev