리액트 문서 리뷰 학습 정리 v17.0.2

study, share · 2021-6-7

← 리스트로

React 문서리뷰 정리

React version v17.0.2 (https://ko.reactjs.org/docs/getting-started.html)

  • 목차
    • 주요개념
      • JSX
      • 엘리먼트 렌더링
      • Components and Props

주요개념

JSX

  • 자바스크립트를 확장한 문법
  • 기존 마크업처럼 UI표현을 할수 있으며 로직과UI를 자연스럽게 연결하기위한 문법
    • Babel에서 해준다.
  • JSX 중괄호 안에 유효한 모든 자바스크립트 표현식을 넣을 수 있다.
  • JSX는 컴파일이 끝나면 자바스크립트 객체로 인식되고 호출된다.
  • 따움표로 문자열 리터럴 표현, 중괄호로 javascript 표현식 삽입
    const element = <div tabIndex="0"></div>;
    const element = <img src={user.avatarUrl}></img>;
    
  • 기본적으로 문자열을 escape하여 xss 방지

엘리먼트 렌더링

  • 엘리먼트는 리액트앱의 가장 작은 단위이다.

리얼DOM에 엘리먼트 렌더링하기

  • 하나의 어플리케이션은 하나의 root를 가지고 있고 reactDOM.render()를 통해 전달한다.

렌더링된 엘리먼트 업데이트하기

  • 엘리먼트는 불변객체 이므로 엘리먼트를 다시 그리는 방법은 새로운 엘리먼트를 만들어서 render함수로 전달하는 것 뿐이다.

변경된 부분만 업데이트하기

  • 엘리먼트 전체를 다시 render함수로 전달했다고해도 실제로 realdom의 반영은 리얼돔과 가상돔의 diff를 통해 변경된 부분에만 적용된다.

Components and Props

  • UI를 개별적으 재사용 가능한 여러 조각으로 나누고 재사용 할 수 있다.
  • props 라는 임의의에 값을 입력 받은 후 React엘리먼트를 반환한다.

함수 컴포넌트와 클래스 컴포넌트

  • 컴포넌트는 함수로 선언하거나 클래스로 선언할 수 있다. 함수형 클래스형 각각 몇가지 추가기능이 있다.

컴포넌트 렌더링

  • DOM태그로 매칭되는 엘리먼트 이외에도 사용자 정의 컴포넌트를 나타낼 수 있다.
  • 사용자 정의 컴포넌트에는 어트리뷰트를 통해 값을 전달할수 있고 props객체를 통해 전달된다.

컴포넌트 합성

  • 자신의 출력에 다른 컴포넌트를 참조하여 사용할 수 있다.

컴포넌트 추출

  • 컴포넌트를 여러개의 작은 컴포넌트로 나누는걸 두려워 하지 말 것.
  • 재활용과 가독성의 측면에서 추출하는 쪽이 도움이 됨.

읽기전용인 Props

  • 리액트는 단방향 흐름의 컨셉으로 예측하기 쉬운 앱의 상태를 목표로 하기 때문에 Props는 직접 변경하면 안된다.

State와 생명주기

컴포넌트 생명주기 활용하기

  • 컴포넌트가 생성되거나 삭제될때의 생명주기를 활용하여 구현할 수 있다.
    • componentDidMount
      • Component가 DOM에 렌더링 된후 실행된다.
    • componentWillUnmount
      • Component가 언마운트 될때 실행된다.

State를 올바르게 사용하기

  • State를 직접 할당하여 수정하면 안된다(바뀐값으로 렌더링 하지 않는다).
    • 대신 setState를 사용해야 한다.
  • setState가 호출될때 성능상의 이유로 내부에서는 비동기로 실행될 수 있으므로 state값을 새로운 setState를 위해 활용해서는 안된다.
    // Wrong
    this.setState({
      counter: this.state.counter + this.props.increment,
    });
    
    // Correct (함수를 인자로 사용하는 형태로 작성해야 함)
    this.setState((state, props) => ({
      counter: state.counter + props.increment
    }));
    
  • state는 병합된다.

Data는 아래로 흐른다

  • state는 자신을 정의한 컴포넌트 이외의 컴포넌트에서는 접근할 수 없다.
  • 컴포넌트는 자신의 state를 자식으로 props를 통하여 전달할 수 있다.
    <FormattedDate date={this.state.date} />
    

이벤트 처리하기

  • React의 이벤트는 소문자 대신 캐멀 케이스(camelCase)를 사용합니다.
  • JSX를 사용하여 문자열이 아닌 함수로 이벤트 핸들러를 전달합니다.
  • React의 이벤트는 합성 이벤트를 사용하기 때문에 브라우저 이벤트의 동작과 상이할 수 있다.
  • JSX 콜백 안에서 this의 의미에 대해 주의해야 한다
    • (bind 해주는 대신 클래스 필드 정의 방법을 사용하거나 화살표 함수로 대체할 수 있다).
    class Toggle extends React.Component {
      constructor(props) {
        // 콜백에서 `this`가 작동하려면 아래와 같이 바인딩 해주어야 한다.
        this.handleClick = this.handleClick.bind(this);
      }
    
      handleClick() {
        this.setState(state => ({
          isToggleOn: !state.isToggleOn
        }));
      }
    
      render() {
        return (
          <button
          ...
      }
    }
    

이벤트 핸들러에 인자 전달하기

  • 아래 두가지 표현 다 옮음
    <button => this.deleteRow(id, e)}>Delete Row</button>
    <button id)}>Delete Row</button>
    

조건부 렌더링 가능

function Greeting(props) {
  const isLoggedIn = props.isLoggedIn;
  if (isLoggedIn) {
    return <UserGreeting />;
  }
  return <GuestGreeting />;
}

엘리먼트 변수

  • 엘리먼트를 변수의 형태로 저장하여 사용할 수 있다.
    if (isLoggedIn) {
      button = <LogoutButton />;
    } else {
      button = <LoginButton />;
    }
    

JSX 안에서 논리연산하기

  • jsx안에서 중괄호를 이용해 표현식을 포함할 수 있으므로
    // && 논리연산을 이용해 조건부 랜더링
    return (
      <div>
        <h1>Hello!</h1>
        {unreadMessages.length > 0 &&
          <h2>
            You have {unreadMessages.length} unread messages.
          </h2>
        }
      </div>
    );
    // 3항 조건식을 이용한 조건부 렌더링
    render() {
      const isLoggedIn = this.state.isLoggedIn;
      return (
        <div>
          {isLoggedIn
            ? <LogoutButton />
            : <LoginButton />
          }
        </div>
      );
    }
    

렌더링 막기

// null을 리턴하면 렌더링을 막는다.
if (!props.warn) {
  return null;
}

리스트와 Key

여러개의 컴포넌트 랜더링하기

  • 아래처럼 반복되는 컴포넌트에는 키값을 지정해야 한다.
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
  <li key={number.toString()}>
    {number}
  </li>
);

key

  • key는 리액트가 어떤 항목을 추가, 수정,삭제할지 판단하는데 도움을 주며, 지정하지 않으면 리스트의 index를 키로 사용한다.
    • 리스트의 순서가 바뀌어 업데이트 되는 등의 상황에서 react가 잘못 해석하여 엉뚱하게 출력하거나, 성능에 문제가 있을수 있다.
  • Key를 선택하는 가장 좋은 방법은 리스트의 다른 항목들 사이에서 해당 항목을 고유하게 식별할 수 있는 문자열을 사용하는 것이다.
    • 형제 사이에서만 고유한 값이면 된다.
  • key는 리액트 내부에서 유용하게 사용되지만 자식 컴포넌트로 값을 전달하지는 않는다.
  • 항목의 순서가 바뀔수 있는 리스트의 경우 index를 키로 지정하는것은 성능상 좋지 않다.

  • 보통 HTML에서 사용하는것 처럼 사용해도 좋지만, 리액트의 "제어 컴포넌트"라는 기술을 사용하는것이 좋다.

제어 컴포넌트 (Controlled Component)

  • React에서는 변경할 수 있는 state가 일반적으로 컴포넌트의 state 속성에 유지되며 setState()에 의해 업데이트된다.
    constructor(props) {
      this.state = {value: ''};
    }
    handleChange(event) {
      this.setState({value: event.target.value});
    }
    handleSubmit(event) {
      alert('An essay was submitted: ' + this.state.value);
      event.preventDefault();
    }
    render() {
      return(
        <form
          <textarea value={this.state.value} />
        </form>
      )
    }
    

제어되는 Input Null 값

  • 제어 컴포넌트에 value prop을 지정하면 의도하지 않는 한 사용자가 변경할 수 없다.
  • null을 입력하면 제어 가능해짐
ReactDOM.render(<input value="hi" />, mountNode);

setTimeout(function() {
  ReactDOM.render(<input value={null} />, mountNode);
}, 1000);

대안

  • 매번 똑같은 패턴을 작성하는게 지겹다면 https://formik.org/ 과 같은 슈퍼셋 컴포넌트를 사용할 수 있다.

State 끌어올리기

  • 종종 동일한 데이터에 대한 변경사항을 여러 컴포넌트에 반영해야 할 필요가 있습니다. 이럴 때는 가장 가까운 공통 조상으로 state를 끌어올리는 것이 좋다.

  • 다른 컴포넌트 사이에 같은 값이 필요하게 되면 그 값을 그들의 가장 가까운 공통 조상으로 끌어올리면 된다. 다른 컴포넌트 간에 존재하는 state를 동기화시키려고 노력하는 대신 하향식 데이터 흐름에 기대면 "보일러 플레이트"는 더 증가할 가능성이 높지만 버그를 격리하여 처리하기 쉽다.


합성과 상속

  • React는 강력한 합성 모델을 가지고 있으며, 상속 대신 합성을 사용하여 컴포넌트 간에 코드를 재사용하는 것이 좋다.

컴포넌트에서 다른 컴포넌트를 담기

  • 자식으로 어떤 컴포넌트가 올지 알수없는 경우 children을 사용할 수 있다.
    • 예를들면 사이드바, 다이얼로그와 같은 박스형 컴포넌트의 경우
  • case 1
    function FancyBorder(props) {
      return (
        <div className={'FancyBorder FancyBorder-' + props.color}>
          {props.children}
        </div>
      );
    }
    function WelcomeDialog() {
      return (
        <FancyBorder color="blue">
          <h1 className="Dialog-title">
            Welcome
          </h1>
        </FancyBorder>
      );
    }
    
  • case 2
    function SplitPane(props) {
      return (
        <div className="SplitPane">
          <div className="SplitPane-left">
            {props.left}
          </div>
          <div className="SplitPane-right">
            {props.right}
          </div>
        </div>
      );
    }
    function App() {
      return (
        <SplitPane
          left={
            <Contacts />
          }
          right={
            <Chat />
          } />
      );
    }
    
  • case 3 (Higher Order 패턴)
    function Dialog(props) {
      return (
        <FancyBorder color="blue">
          <h1 className="Dialog-title">
            {props.title}
          </h1>
          <p className="Dialog-message">
            {props.message}
          </p>
        </FancyBorder>
      );
    }
    
    function WelcomeDialog() {
      return (
        <Dialog
          title="Welcome"
          message="Thank you for visiting our spacecraft!" />
      );
    }
    

상속

  • React에서 권장할만한 컴포넌트 상속 패턴은 없다고 함.

React로 사고하며 만들기

  1. 1단계: UI를 컴포넌트 계층 구조로 나누기
  2. 2단계: React로 정적인 버전 만들기
  • 프로젝트가 작다면 하향식으로, 크다면 상향식으로 만들면 좋다고 함.
  1. 3단계: UI state에 대한 최소한의 (하지만 완전한) 표현 찾아내기
  2. 4단계: State가 어디에 있어야 할 지 찾기
  3. 5단계: 역방향 데이터 흐름 추가하기
  • 리액트의 단방향 흐름 구조를 이해하고 사용하자 (코드가 많아지지만 이해하기 쉬어진다고 함).
    • props을 통해 핸들러를 넘기는 방식을 이해하면 됨

고급 안내서

접근성

시맨틱 HTML

  • React Fragment 를 사용하면 div를 사용하는대신 의미있는 태그를 사용하여 구현할 수 있다.
    function Glossary(props) {
      return (
        <dl>
          {props.items.map(item => (
            // 항목을 매핑할 때 Fragment는 반드시 `key` 프로퍼티가 있어야 합니다.
            <Fragment key={item.id}>
              <dt>{item.term}</dt>
              <dd>{item.description}</dd>
            </Fragment>
          ))}
        </dl>
      );
    }
    

접근성 있는 폼

  • input, textarea와 같은 태그는 라벨을 위한 속성을 사용할 수 있다.
    • 다만 for 애트리뷰트는 JSX에서 htmlFor로 사용해야 한다.
    <label htmlFor="namedInput">Name:</label>
    <input id="namedInput" type="text" name="name"/>
    

프로그래밍적으로 포커스 관리하기

  • 모든 웹 애플리케이션은 키보드만 사용하여 모든 동작을 할 수 있어야 한다.
  • 키보드 포커스는 포커싱된 요소의 윤각선을 표시하며, 프로그래밍 적으로 포커스를 관리할 수 있어야 한다.
  • 아래처럼 React에서 포커스를 지정하면, DOM 엘리먼트에 ref를 사용할 수 있다.
    class CustomTextInput extends React.Component {
    constructor(props) {
      super(props);
      // DOM 엘리먼트를 저장할 textInput이라는 ref을 생성한다.
      this.textInput = React.createRef();
    }
    focus() {
      // DOM API를 사용해 텍스트 input에 정확히 포커스를 맞춘다.
      // 주의: ‘현재’의 DOM 노드에 접근하고 있다.
      this.textInput.current.focus();
    }
    render() {
      // `ref` 콜백으로 텍스트 input DOM을 저장한다.
      // 인스턴스 필드의 엘리먼트 (예를 들어, this.textInput)
        return (
          <input
            type="text"
            ref={this.textInput}
          />
        );
      }
    }
    
  • 고차 컴포넌트에서 ref를 사용할 경우 예시
    function CustomTextInput(props) {
      return (
        <div>
          <input ref={props.inputRef} />
        </div>
      );
    }
    
    class Parent extends React.Component {
      constructor(props) {
        super(props);
        this.inputElement = React.createRef();
      }
      render() {
        return (
          <CustomTextInput inputRef={this.inputElement} />
        );
      }
    }
    
    // 이제 필요할 때마다 포커스를 잡을 수 있다.
    this.inputElement.current.focus();
    

마우스와 포인터 이벤트

  • 마우스 포인터와 관련된 기능을 구현할때는 항상 키보드로도 동작하도록 구현해야 하며, onFocus, onBlue 이벤트를 활용할 수 있다.
    render() {
      // React는 블러와 포커스 이벤트를 부모에 버블링해준다.
      return (
        <div
            
          <button                aria-haspopup="true"
                  aria-expanded={this.state.isOpen}>
    
            Select an option
          </button>
          {this.state.isOpen && (
            <ul>
              <li>Option 1</li>
              <li>Option 2</li>
              <li>Option 3</li>
            </ul>
          )}
        </div>
      );
    }
    

더욱 복잡한 위젯

  • 복잡한 사용자 경험을 갖는 위젯을 만들때도 접근성이 떨어져서는 안된다.
  • ARIA 역할ARIA 상태 및 프로퍼티에 대한 지식이 있다면.JSX에서 모두 지원되는 HTML 어트리뷰트로 이를 통해 완전히 접근성 있고 기능이 우수한 React 컴포넌트를 구성할 수 있다.

기타 고려사항

  • 언어설정
  • 문서 제목 설정
  • 색 대비

개발 및 테스트 도구

  • ESLint 플러그인인 eslint-plugin-jsx-a11y는 JSX 내의 접근성 문제에 대해 즉각적인 AST 린팅 피드백을 제공함. 에디터에서도 통합하여 사용 가능.

코드 분할

  • 여러 모듈들을 하나로 묶어서 하나의 파일로 만들어주는 번들링은 매우 편리하지만, 앱이 커질수록 로딩 시간이 오래 걸릴 수 있다.
  • 번들 파일을 여러 파일로 나누는 전략이 도움이 될 수 있다(일반적인 번들러들은 이 기능을 지원하고 있다)
  • 코드의 양을 줄이지 않고도 사용자가 당장 필요하지 않는 코드는 불러오지 않게하여 사용자 경험을 향상시킬 수 있다.

동적 import 사용

  • 동적 import는 코드분할의 좋은 방법이다.
    import("./math").then(math => {
      console.log(math.add(16, 26));
    });
    

react.lazy

  • react.lazy는 리액트에서 컴포넌트를 불러올때 동적 import를 사용할수 있는 방법이다.
    • (처음 랜더링될때 컴포넌트를 로드함).
    const OtherComponent = React.lazy(() => import('./OtherComponent'));
    
  • fallback prop은 컴포넌트가 로드될 때까지 기다리는 동안 렌더링하려는 React 엘리먼트를 사용자에게 보여준다.
    import React, { Suspense } from 'react';
    
    const OtherComponent = React.lazy(() => import('./OtherComponent'));
    const AnotherComponent = React.lazy(() => import('./AnotherComponent'));
    
    function MyComponent() {
      return (
        <div>
          <Suspense fallback={<div>Loading...</div>}>
            <section>
              <OtherComponent />
              <AnotherComponent />
            </section>
          </Suspense>
        </div>
      );
    }
    

Context

  • context를 이용하면 단계마다 일일이 props를 넘겨주지 않고도 컴포넌트 트리 전체에 데이터를 제공할 수 있다.
  • context는 React 컴포넌트 트리 안에서 전역적(global)이라고 볼 수 있는 데이터를 공유할 수 있도록 고안된 방법이다.
    const ThemeContext = React.createContext('light');
    
    class App extends React.Component {
      render() {
        return (
          <ThemeContext.Provider value="dark">
            <Toolbar />
          </ThemeContext.Provider>
        );
      }
    }
    
    function Toolbar() {
      return (
        <div>
          <ThemedButton />
        </div>
      );
    }
    
    class ThemedButton extends React.Component {
      static contextType = ThemeContext;
      render() {
        // 값을 바로 사용 가능
        return <Button theme={this.context} />;
      }
    }
    

컨텍스트 사용 전에 고려해야 할것

  • 컴포넌트 합성이 더 효과적인 방법일 수 있다.

API

1. React.createContext
  • context 객체를 만듬
    const MyContext = React.createContext(defaultValue);
    
2. Context.Provider
  • 구독하는 컴포넌트들에게 값의 변화를 알리는 역할을 한다.
  • Provider 하위에 또 다른 Provider를 배치하는 것도 가능하며, 이 경우 하위 Provider의 값이 우선.
  • shouldComponentUpdate 메서드에 의해 하위로 전파되는것을 막지 않으므로 주의해야 한다.
    • (상위 컴포넌트가 업데이트를 건너뛰더라도 하위 컨슈머는 업데이트가 되므로)
<MyContext.Provider value={/* 어떤 값 */}>

3. Class.contextType

  • 이렇게 생성하면 모든 생명주기 메서드에서 사용 가능함.
    class MyClass extends React.Component {
      componentDidMount() {
        let value = this.context;
        /* MyContext의 값을 이용한 코드 */
      }
      componentDidUpdate() {
        let value = this.context;
        /* ... */
      }
      componentWillUnmount() {
        let value = this.context;
        /* ... */
      }
      render() {
        let value = this.context;
        /* ... */
      }
    }
    MyClass.contextType = MyContext;
    
4. Context.Consumer
  • 함수 컴포넌트 안에서 context를 구독할 수 있다.
    <MyContext.Consumer>
      {value => /* context 값을 이용한 렌더링 */}
    </MyContext.Consumer>
    
5. Context.displayName
  • 개발자 도구에서 표시할 컨텍스트 네임을 지정할 수 있다.

Error boundaries

  • 에러가 발생할 경우 에러를 숨기기 보다는 표시해주는게 더 좋을 수 있다.
  • try catch도 훌륭하지만 명령형 코드에서만 동작한다.
    • 에러경계는 이벤트 핸들러에서는 동작하지 않으므로이벤트 핸들러에서 에러 포착을 위해서는 try catch를 사용해야 한다.
  • React에서는 컴포넌트 동적 모듈 로드가 실패할경우 에러 화면을 구성하여 보여줄 수 있다.
    // 사용
    <ErrorBoundary>
      <MyWidget />
    </ErrorBoundary>
    
    // 구현
    class ErrorBoundary extends React.Component {
      constructor(props) {
        super(props);
        this.state = { hasError: false };
      }
    
      static getDerivedStateFromError(error) {
        // 다음 렌더링에서 폴백 UI가 보이도록 상태를 업데이트 합니다.
        return { hasError: true };
      }
    
      componentDidCatch(error, errorInfo) {
        // 에러 리포팅 서비스에 에러를 기록할 수도 있습니다.
        logErrorToMyService(error, errorInfo);
      }
    
      render() {
        if (this.state.hasError) {
          // 폴백 UI를 커스텀하여 렌더링할 수 있습니다.
          return <h1>Something went wrong.</h1>;
        }
    
        return this.props.children;
      }
    }
    

Refs 전달하기

forwardRef를 사용하면 ref를 직접 사용하는 것 처럼 전달할 수 있다.

  • key와 마찬가지로 ref역시 리액트 내부에서는 특별하게 처리하므로 props를 통해 전달되지 않는다는 것을 명심해야 한다.
const FancyButton = React.forwardRef((props, ref) => (
  <button ref={ref} className="FancyButton">
    {props.children}
  </button>
));

// 이제 DOM 버튼으로 ref를 작접 받을 수 있습니다.
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;
  • DevTools에서 표시할 ref 이름을 명시할 수도 있다.
    function logProps(Component) {
      class LogProps extends React.Component {
        // ...
      }
    
      function forwardRef(props, ref) {
        return <LogProps {...props} forwardedRef={ref} />;
      }
    
      // DevTools에서 이 컴포넌트에 조금 더 유용한 표시 이름을 지정하세요.
      // 예, "ForwardRef(logProps(MyComponent))"
      const name = Component.displayName || Component.name;
      forwardRef.displayName = `logProps(${name})`;
    
      return React.forwardRef(forwardRef);
    }
    

Fragments

  • 컴포넌트가 여러 엘리먼트를 반환할때 별도의 DOM노드를 추가하지 않고 그룹화 하는 기능
render() {
  return (
    <React.Fragment>
      <ChildA />
      <ChildB />
      <ChildC />
    </React.Fragment>
  );
}
  • 단축문법
class Columns extends React.Component {
  render() {
    return (
      <>
        <td>Hello</td>
        <td>World</td>
      </>
    );
  }
}
  • key가 있는 Fragments
function Glossary(props) {
  return (
    <dl>
      {props.items.map(item => (
        // React는 `key`가 없으면 key warning을 발생합니다.
        <React.Fragment key={item.id}>
          <dt>{item.term}</dt>
          <dd>{item.description}</dd>
        </React.Fragment>
      ))}
    </dl>
  );
}

고차컴포넌트 (Higher Order Component)

  • 컴포넌트를 가져와 새 컴포넌트를 반환하는 함수.

횡단관심사에 고차 컴포넌트 사용

  • 예전에는 Mixin을 권장했으나, Mixin패턴이 많은 문제를 일으킨다는게 발견됨
  • 구현체가 동일한 비슷한 패턴의 컴포넌트가 있을 경우 동일 기능을 묶는 고차 컴포넌트를 만들어서 조합하는 방법을 권장함.
  • 원본 컴포넌트를 수정하지 않으므로 커플링이 완화됨
  • 컨테이너 컴포넌트의 일종으로 봐도 무관함
  • 고차 컴포넌트가 전달받은 원본 컴포넌트의 기능을 변경해서는 안된다.
  • 고차 컴포넌트는 원본 컴포넌트의 본래 관심사와 분리된 격리된 props를 만들어 활용해야한다.
    render() {
      // 이 HOC에만 해당되므로 추가된 props는 걸러내어 이 HOC에 전달되지 않도록 합니다.
      const { extraProp, ...passThroughProps } = this.props;
    
      // 이 Props는 일반적으로 Status값 또는 Instance method 입니다.
      const injectedProp = someStateOrInstanceMethod;
    
      // wrapped component에 props를 전달합니다.
      return (
        <WrappedComponent
          injectedProp={injectedProp}
          {...passThroughProps}
        />
      );
    }
    
  • 고차 컴포넌트는 쉽게 조합하여 사용할수 있다.
  • 고차 컴포넌트도 다른 컴포넌트와 마찬가지로 개발툴에 표시되므로 displayName 속성을 지정해 놓고 개발시 활용할 수 있다.

주의사항

  • ref는 prop이 아닌 특별히 취급되는 속성이므로 주의할것

  • render 메서드 안에서 고차 컴포넌트를 사용하지 말것.

    render() {
      // render가 호출될 때마다 새로운 버전의 EnhancedComponent가 생성됩니다.
      // EnhancedComponent1 !== EnhancedComponent2
      const EnhancedComponent = enhance(MyComponent);
      // 때문에 매번 전체 서브트리가 마운트 해제 후 다시 마운트 됩니다!
      return <EnhancedComponent />;
    }
    
  • 정적 메서드는 따로 복사할것 (기존 컴포넌트가 새 컴포넌트에 감싸지며, 새 컴포넌트는 기존 컴포넌트의 정적 메서드를 잃어버리게 됨)

    function enhance(WrappedComponent) {
      class Enhance extends React.Component {/*...*/}
      // 복사 할 메서드를 정확히 알아야 합니다.
      Enhance.staticMethod = WrappedComponent.staticMethod;
      return Enhance;
    }
    
    • hoist-non-react-statics 함수를 사용하면 모든 정적 메서드를 복사할 수 있다.
    import hoistNonReactStatic from 'hoist-non-react-statics';
    function enhance(WrappedComponent) {
      class Enhance extends React.Component {/*...*/}
      hoistNonReactStatic(Enhance, WrappedComponent);
      return Enhance;
    }
    

다른 라이브러리와 통합하기

  • React는 다른 DOM라이브러리가 임의로 DOM을 변경하는걸 알지 못하며 같이 사용한다면 혼란스러운 상태가 될 것이다. (다른 라이브러리와 통합을 추천하지 않음)
  • 충돌을 피하는 방법은 React관여할 필요없는 빈 div를 렌더링하는 것이다(프로퍼티나 자식을 가지지 않으면 React가 업데이트하지 않는다).
    class SomePlugin extends React.Component {
      componentDidMount() {
        this.$el = $(this.el);
        this.$el.somePlugin();
      }
    
      componentWillUnmount() {
        this.$el.somePlugin('destroy');
      }
    
      render() {
        return <div ref={el => this.el = el} />;
      }
    }
    
    class Chosen extends React.Component {
      render() {
        return (
          <div> // children을 자식으로 갖으면 React가 관여하기 때문에 빈 div아래로 select가 들어가도록 처리
            <select className="Chosen-select" ref={el => this.el = el}>
              {this.props.children}
            </select>
          </div>
        );
      }
    }
    

JSX 이해하기

  • 근본적으로, JSX는 React.createElement(component, props, …children) 함수에 대한 문법적 설탕을 제공할 뿐이다.

JSX 타입을 위한 점 표기법 사용

  • 하나의 모듈에서 복수개의 컴포넌트들을 export할 수 있으며, 점 표기법을 이용해 사용할 수 있다.
    import React from 'react';
    
    const MyComponents = {
      DatePicker: function DatePicker(props) {
        return <div>Imagine a {props.color} datepicker here.</div>;
      }
    }
    
    function BlueDatePicker() {
      return <MyComponents.DatePicker color="blue" />;
    }
    

실행 중에 타입 선택하기

  • React element 타입에 일반적인 표현식은 사용할 수 없다. 만약 element 타입을 지정할 때 일반적인 표현식을 사용하고자 한다면 대문자로 시작하는 변수에 배정한 후 사용해야 한다.
    import React from 'react';
    import { PhotoStory, VideoStory } from './stories';
    
    const components = {
      photo: PhotoStory,
      video: VideoStory
    };
    
    function Story(props) {
      // 잘못된 사용법
      // return <components[props.storyType] story={props.story} />;
    
      // 옮은 사용법
      const SpecificStory = components[props.storyType];
      return <SpecificStory story={props.story} />;
    }
    

JSX 안에서 표현식 사용

  • 하지만 if와 for문은 표현식이 아니므로 jsx내에서 사용할수 없지만, 주변코드에서 사용할 수 있다.
    function NumberDescriber(props) {
      let description;
      if (props.number % 2 == 0) {
        description = <strong>even</strong>;
      } else {
        description = <i>odd</i>;
      }
      return <div>{props.number} is an {description} number</div>;
    }
    

Props의 기본값은 “True”

  • prop에 어떤값도 넘겨주지 않을 경우 기본값을 true이다.
    <MyTextBox autocomplete />
    <MyTextBox autocomplete={true} />
    

속성펼치기

  • 전개 연산자는 유용하지만 불필요한 prop을 컴포넌트에 넘기거나 유효하지 않은 HTML 속성들을 DOM에 넘기기도 합니다. 꼭 필요할 때만 사용하는 것을 권장한다.
  • 아래의 두 표현은 동일하다.
    function App1() {
      return <Greeting firstName="Ben" lastName="Hector" />;
    }
    
    function App2() {
      const props = {firstName: 'Ben', lastName: 'Hector'};
      return <Greeting {...props} />;
    }
    

React 컴포넌트는 element로 이루어진 배열을 반환할 수 있다.

render() {
  // 리스트 아이템들을 추가적인 엘리먼트로 둘러쌀 필요 없습니다!
  return [
    // key 지정을 잊지 마세요 :)
    <li key="A">First item</li>,
    <li key="B">Second item</li>,
    <li key="C">Third item</li>,
  ];
}

JavaScript 표현식을 자식으로 사용하기

  • 동일한 표현

    <MyComponent>foo</MyComponent>
    
    <MyComponent>{'foo'}</MyComponent>
    
  • JSX내에서 표현식을 넣을 수 있으므로 아래와 같이 유용하게 사용 가능하다.

    function Item(props) {
      return <li>{props.message}</li>;
    }
    
    function TodoList() {
      const todos = ['finish doc', 'submit pr', 'nag dan to review'];
      return (
        <ul>
          {todos.map((message) => <Item key={message} message={message} />)}
        </ul>
      );
    }
    

함수를 자식으로 사용하기

  • props.children은 어떠한 표현식으로든지 넘겨줄 수 있으며 따라서 아래처럼 함수로 넘겨주는 것도 가능하다.
    // 자식 콜백인 numTimes를 호출하여 반복되는 컴포넌트를 생성합니다.
    function Repeat(props) {
      let items = [];
      for (let i = 0; i < props.numTimes; i++) {
        items.push(props.children(i));
      }
      return <div>{items}</div>;
    }
    
    function ListOfTenThings() {
      return (
        <Repeat numTimes={10}>
          {(index) => <div key={index}>This is item {index} in the list</div>}
        </Repeat>
      );
    }
    

boolean, null, undefined는 무시된다

  • 따라서 아래의 표현들은 다 동일한 표인이다

    <div />
    
    <div></div>
    
    <div>{false}</div>
    
    <div>{null}</div>
    
    <div>{undefined}</div>
    
    <div>{true}</div>
    
  • true, false와 같은 boolean 값이 출력되지 않기 때문에 아래와 같이도 유용하게 사용할수 있다.

    <div>
      {showHeader && <Header />}
      <Content />
    </div>
    
  • 주의할 점은 0과 같은 경우 booelan값이 아니므로 0이 출력된다는 점이다(아래와 같은 경우 주의해야 한다)

    <div>
      {props.messages.length &&
        <MessageList messages={props.messages} />
      }
    </div>
    

성능 최적화

  • React는 별다른 최적화 없이도 충분히 빠른 서비스를 제공 가능하다. 하지만 그럼에도 더 개선할 수 있는 몇가지 방법이 있다.

프러덕션 빌드를 사용해라

  • 크롬에 react 개발툴 플러그인이 깔려있다면 개발툴 아이콘의 색으로 개발모드와 배포모드 구분이 가능하다.
    • 개발모드는 빨간색 프로덕션 모드는 검은색
  • 프러덕션 모드는 개발에 관련된 잡 코드나 경고가 포함되지 않으므로 더 빠를 수 있다.
  • Create React App을 사용중이라면 npm run build를 통해 프러덕션용 파일을 만들 수 있다.
  • 빌드툴에 terserPlugin을 적용하면 코드를 축소해주므로 빨라질 수 있다.

DevTools Profiler로 컴포넌트 프로파일링

  • react-dom 16.5 부터, react-native 0.57부터 개발툴의 프로파일링 도구를 이용해 성능을 개선할 수 있다.

긴 목록 가상화 컴포넌트를 사용하라

  • react-window 또는 react-virtualized같은 라이브러리를 사용하면 성능을 개선하는데 유용할 수 있다.

리포지션을 피해라

  • 이미 렌더링된 dom과 가상돔을 비교하는 작업이 오래걸릴 수 있으므로, shouldComponentUpdate 메서드로 리랜더링이 필요없는 상황을 이미 알고 있을때 비교 작업을 생략할 수 있다.
  • 대부분의 경우 shouldComponentUpdate 메서드를 직접 구현하는 대신 React.PureComponent에서 상속받아 구현을 생략할 수 있다.
  • React.PureComponent의 shouldComponentUpdate 구현은 얕은 참조 비교만 하므로 구조가 복잡한 prop이나 state의 경우 정상동작하지 않을 수 있으므로 주의해야 한다.

Portal

  • Portal은 부모 컴포넌트의 DOM 계층 구조 바깥에 있는 DOM 노드로 자식을 렌더링하는 최고의 방법을 제공한다.
    • DOM의 다른 계층구조에 children을 출력하고 싶을 경우
        // React는 새로운 div를 생성하지 *않고* `domNode` 안에 자식을 렌더링합니다.
        // `domNode`는 DOM 노드라면 어떠한 것이든 유효하고, 그것은 DOM 내부의 어디에 있든지 상관없습니다.
        return ReactDOM.createPortal(
          this.props.children,
          domNode
        );
      }
      

Portal을 통한 이벤트 버블링

  • portal이 DOM 트리의 어디에도 존재할 수 있다 하더라도 모든 다른 면에서 일반적인 React 자식처럼 동작한다.
    • DOM 트리에서의 위치에 상관없이 portal은 여전히 React 트리에 존재하기 때문이다.

Profiler API

  • Profile 컴포넌트로 특정 컴포는트 트리 구간을 감싸서 사용하며, 콜백을 통해서 해당 구간의 성능지표 값을 확인할 수 있다.

재조정 (Reconciliation)

리액트트리 비교 알고리즘

루트 엘리먼트의 타입이 같은경우
  • 루트엘리먼트의 타입이 틀리면 하위 엘리먼트들은 완전 새로이 구축된다.
루트 엘리먼트의 타입이 같은경우
  • 변경된 속성만 변경함

Ref와 DOM

  • 왠만한 상황에서는 React의 라이프 사이클 과 Prop을 이용해서 구현해야 하며 특별한 경우에만 Ref를 사용하는 것이 좋다.

Ref를 사용해야 할 때

  • 포커스, 텍스트 선택영역, 혹은 미디어의 재생을 관리할 때.
  • 애니메이션을 직접적으로 실행시킬 때.
  • 서드 파티 DOM 라이브러리를 React와 같이 사용할 때.

Ref 생성하기

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.myRef = React.createRef();
  }
  render() {
    return <div ref={this.myRef} />;
  }
}

Ref 접근하기

  • render 메서드 안에서 ref가 엘리먼트에게 전달되었을 때, 그 노드를 향한 참조는 ref의 current 어트리뷰트에 담기게 된다
  • (함수 컴포넌트는 인스턴스가 없기 때문에 함수 컴포넌트에 ref 어트리뷰트를 사용할 수 없다.)
    const node = this.myRef.current;
    

함수형 컴포넌트에 ref 어트리뷰트 사용하기

  • 함수형 컴포넌트는 인스턴스가 없다는걸 인지하고 유의하여 사용하면 된다
  • forwardRef 를 이용하여 함수 컴포넌트를 만들면 클래스 컴포넌트처럼 ref를 통과시켜 사용할 수 있다.

부모 컴포넌트에게 DOM ref를 공개하기

  • 사용할 수 있고 특정 상황에서 유용할수 있지만, 자식컴포넌트가 함수컴포넌트일때는 동작하지 않으므로 주의해야 한다.
    • (함수 컴포넌트는 인스턴스가 없기 때문에)

콜백 ref

  • React는 ref가 설정되고 해제되는 상황을 세세하게 다룰 수 있는 “콜백 ref” 기능을제공

Render Props

  • 컴포넌트간에 데이터를 공유하기위한 테크닉
class Cat extends React.Component {
  render() {
    const mouse = this.props.mouse;
    return (
      <img src="/cat.jpg" style={{ position: 'absolute', left: mouse.x, top: mouse.y }} />
    );
  }
}

class Mouse extends React.Component {
  constructor(props) {
    super(props);
    this.handleMouseMove = this.handleMouseMove.bind(this);
    this.state = { x: 0, y: 0 };
  }

  handleMouseMove(event) {
    this.setState({
      x: event.clientX,
      y: event.clientY
    });
  }

  render() {
    return (
      <div style={{ height: '100vh' }}

        {/*
          <Mouse>가 무엇을 렌더링하는지에 대해 명확히 코드로 표기하는 대신,
          `render` prop을 사용하여 무엇을 렌더링할지 동적으로 결정할 수 있습니다.
        */}
        {this.props.render(this.state)}
      </div>
    );
  }
}

class MouseTracker extends React.Component {
  render() {
    return (
      <div>
        <h1>Move the mouse around!</h1>
        <Mouse render={mouse => (
          <Cat mouse={mouse} />
        )}/>
      </div>
    );
  }
}

정적타입검사

프로젝트에 타입스크립트 추가하기

  1. 설치
npm install --save-dev typescript
  1. 컨피그 생성
npx tsc --init

HOOK


Hook 소개

  • React 16.8부터 기능 추가

  • 클래스 컴포넌트를 사용할 필요 없이 함수컴포넌트로 상태등 여러 기능을 사용할수 있게 됨

  • 믹스인의 대용으로서의 기능도 함

  • 기존 클래스 컴포넌트의 장황함을 해소

  • 함수형 컴포넌트에서도 생명주기 기능을 사용할 수 있게 해준다.

  • 훅은 클래스 컴포넌트에서는 동작하지 않는다

useState

 import React, { useState } from 'react';
 
 function Example() {
   const [count, setCount] = useState(0);
 
   return (
     <div>
       <p>You clicked {count} times</p>
       <button => setCount(count + 1)}>
        Click me
       </button>
     </div>
   );
 }

useEffect

  • componentDidMount 나 componentDidUpdate, componentWillUnmount 의 기능을 대체
  • 매번 랜더링 이후에 effect실행
  • 최상위 컨텍스트에서만 실행되어야 함(반복문, 제어문 중첩된 함수 내에서 사용하면 안됨)
  • 함수 본체에서componentDidMount 나 componentDidUpdate를 구현하고 리턴되는 메스드에서는 componentWillUnmount를 실행
  • 관심사 분리를 위해 여러 여러 이팩트 메서드를 사용할수도 있다.
useEffect(() => {
  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }

  ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); // componentDidMount 나 componentDidUpdate
  return () => {
    ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); // componentWillUnmount
  };
}, [props.friend.id]); // props.friend.id가 바뀔 때만 재구독합니다.

useContect

  • 메모이제이션을 같이 사용해야만 최적화 될수 있음
  • useContext(MyContext)
  • 클래스에서의 static contextType = MyContext 또는 <MyContext.Consumer>와 같다고 보면 된다.

useRef

function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const => {
    // `current` points to the mounted text input element
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button the input</button>
    </>
  );
}

useImperativeHandle

  • 부모컴포넌트에서 참조할 메서드를 구현할때 사용
    function FancyInput(props, ref) {
      const inputRef = useRef();
      useImperativeHandle(ref, () => ({
        focus: () => {
          inputRef.current.focus();
        }
      }));
      return <input ref={inputRef} ... />;
    }
    FancyInput = forwardRef(FancyInput);
    

useMemo

  • render메서드가 실행될때마다 특정 값을 매번 재계산 해주는 걸 방지
  • useMemo를 사용하지 않고도 정상적으로 구현되는 방법을 선택하고 useMemo는 최적화 하는 용도로만 사용하라.

useCallback

  • useMemo와 동일하게 동작하며, 참조의 동일성에 의한 최적화된 콜백을 자식 컴포넌트에 전달할때 용이하다.

커스텀 훅 만들기

  • 컴포넌트간 로직을 재활용하고 싶은 경우 고차컴포넌트, render 프롭 전략 외에 사용할수 있음