서론

우선 리액트 공식 문서에서 리액트를 '사용자 인터페이스를 구축하기 위한 선언적이고 효율적이며 유연한 JavaScript 라이브러리'로 소개하고 있다. SPA(Single Page Application)형태로 하나의 페이지에서 보여지는 컴포넌트 조합을 동적으로 바꿔가며 화면을 표현하는 형태다. Virtual DOM이라는 개념을 사용해 이러한 SPA를 구현하고 있다. 장, 단점이 존재하지만 자세한 내용은 나중에 다루도록 하겠다.

 

React 특징

1. Component 구조

2. JSX

3. Data Flow

4. Virtual DOM

 

 

1. Component

"컴포넌트"라고 불리는 작고 고립된 부품을 이용해 복잡한 UI를 구성하도록 해주는 것이 리액트이다. 컴포넌트에는 몇 가지 종류가 있다. 크게 2가지로 나누자면 '함수형 컴포넌트', '클래스형 컴포넌트'가 있다. 우선 클래스형 컴포넌트는 다음과 같은 구조를 가지고 있다. 아래에 간단한 예시를 작성해봤다.

class MyClassComponent extends React.Component {
    render() {
    	return(
            <div className="my-component">
            	<h1>Hello World!</h1>
                <ul>
                    <li>React!</li>
                    <li>React Native</li>
                </ul>
            </div>
        );
    }
}

이러한 컴포넌트는 재사용 가능하고 여러 컴포넌트들을 조합해 다양한 형태의 UI를 쉽게 만들 수 있다. 불필요한 반복적 코드를 줄이고 빠르게 UI를 구성할 수 있어 개발 생산성에 있어 장점을 가진다. 또한 작은 형태의 컴포넌트는 테스트하기 용이해 코드를 유지보수하기에도 도움이 된다. 아래에 여러 작은 컴포넌트를 조합한 예시 코드가 있다.

class App extends Component {
  render() {
    return (
      <Layout>
        <Header />
        <Navigation />
        <Content>
          <Sidebar></Sidebar>
          <Router />
        </Content>
        <Footer></Footer>
      </Layout>
    );
  }
}

 

 

2. JSX

리액트에서는 HTML의 태그 문법과 비슷한 형태로 ui 화면을 표현하고 있다. HTML과 비슷하지만 HTML과는 다르다. 리액트에서는 JSX라 하는 JavaScript를 확장한 문법을 사용한다. 간단하게 JavaScript이지만 화면을 표시하기 위해서 조금 더 확장된 개념이라고 보면 된다. 

JSX란?
Javascript를 확장한 문법입니다.

React에서는 이벤트가 처리되는 방식, 시간에 따라 state가 변하는 방식, 화면에 표시하기 위해 데이터가 준비되는 방식 등 렌더링 로직이 본질적으로 다른 UI 로직과 연결된다는 사실을 받아들입니다.

React는 별도의 파일에 마크업과 로직을 넣어 기술을 인위적으로 분리하는 대신, 둘 다 포함하는 “컴포넌트”라고 부르는 느슨하게 연결된 유닛으로 관심사를 분리합니다. 이후 섹션에서 다시 컴포넌트로 돌아오겠지만, JS에 마크업을 넣는 게 익숙해지지 않는다면 이 이야기가 확신을 줄 것입니다.

React는 JSX 사용이 필수가 아니지만, 대부분의 사람은 JavaScript 코드 안에서 UI 관련 작업을 할 때 시각적으로 더 도움이 된다고 생각합니다. 또한 React가 더욱 도움이 되는 에러 및 경고 메시지를 표시할 수 있게 해줍니다.

출처: React 공식문서

 

 

3. Data Flow - 단방향 데이터 바인딩

단방향 데이터 바인딩은 간단히 말해 데이터의 흐름이 한 방향으로만 이뤄진다. 부모 요소에서 자식 요소로 전달이 되고 적절한 Event를 통해 데이터를 갱신하게 된다. 양방향 바인딩은 앱의 규모가 커질수록 추적하기 어려워지고 그에 따라 복잡해지게 된다. 이런 단점을 보완해 데이터 흐름을 보다 예측 가능하도록 하고자 리액트에서 단방향 형태로 바인딩을 하도록 했다고 한다. 단방향, 양방향 각각 장, 단점이 존재하기에 사용하는 프레임워크나 라이브러리에 따라 사용되는 데이터 바인딩 형태도 다 다르다. 조금 더 깊은 내용은 검색을 하면 많이 나오니 참고하면 될 것 같다.

 

 

4. Virtual DOM

브라우저는 화면을 그리기 위해서 DOM(Document Object Model)이라는 개념을 사용한다. DOM은 HTML 파일 내용을 토대로 만들어지는데, JavaScript와 같은 스크립팅 언어로 수정할 수 있도록 만들어진, 웹 페이지의 객체 지향 표현이다. DOM은 브라우저가 화면을 그리기 위해서 필요한 정보가 트리 형태로 저장된 데이터다. DOM에 변화가 생기면 렌더 트리를 재생성하고 레이아웃을 만들고 다시 보여주는 과정이 반복된다. DOM 트리가 재생성되는 과정에서 모든 요소들이 다시 계산되고 많은 연산을 반복하게 되면서 비효율적이게 된다. React에서는 이런 불필요하고 비효율적인 방식을 개선하고자 Virtual DOM이라는 개념을 도입했다.

React에서 Virtual DOM은 UI의 이상적인 또는 가상의 표현을 메모리에 저장하고 ReactDOM과 같은 라이브러리에 의해 실제 DOM과 동기화하는 프로그래밍적 개념이다. 뷰에서 변화가 생기면 실제 DOM에 적용되기 전 가상 DOM에서 먼저 적용이 되고 비교를 통해 최종적 결과가 실제 DOM으로 전달이 된다. 이런 DOM 관리 과정을 리액트 내에서 자동화, 추상화를 통해 불필요한 연산 비용을 줄이고 성능을 높이도록 해준다.

 

참고자료: 리액트 공식문서 - Virtual DOM

 

반응형

리액트에서 테스트를 위해서 많이 사용하는 도구 중 하나인 React Testing Library에 대한 포스트입니다.

테스트에 사용되는 라이브러리들이 많으니 각자 본인에게 맞는 것을 활용해 공부하며 적용해 보는 것이 좋을 것 같습니다.

아래에 리액트 공식 사이트에서 사용되는 테스트 유틸 및 라이브러리 예시를 확인해보세요.

  • Enzyme: React를 위한 JavaScript 테스트 유틸.
  • Jest: React를 포함한 JavaScript 테스트 프레임워크.
  • react-testing-library: 가벼운 React DOM 테스트 유틸.
  • React-unit: React를 위한 가벼운 단위테스트 라이브러리.
  • Skin-deep: 얕은 렌더링을 지원하는 React 테스트 유틸.
  • Unexpected-react: React 컴포넌트와 이벤트를 발생시켜주는 플러그인.

설치

yarn add @testing-library/react @testing-library/jest-dom

npm install --save react-testing-library @testing-library/jest-dom

npm 또는 yarn을 이용해 react-testing-library를 설치한다. react-testing-library@testing-library/react 로 변경되었으니 주의!

jest-dom@testing-library/jest-dom 으로 변경됐다고 한다.

react-testing-library는 '사용자 관점'에서 테스트를 진행한다. 보통 Enzyme를 이용한 테스트 방식은 상태값, 상태 변수에 대해 테스트를 한다. 하지만 전자는 상태 관리는 컴포넌트의 구현 세부사항일 뿐이다. 즉, 상태 변수가 언제든 다른 컴포넌트로 옮겨지거나, react에서 vue.js로 바뀌더라도 테스트에는 문제가 없어야한다.

다양한 쿼리

  • getBy* 쿼리 (ex. getByTestId, getByText, getByRole): 이 함수들은 동기적(synchronous)이며 그 요소가 현재 DOM 안에 있는지 확인한다. 즉, 현재상태만 확인한다. 그렇지 않으면 에러를 발생시킨다.
  • findBy* 쿼리 (ex. findByText): 이 함수들은 비동기적(asynchronous)이다. 그 요소를 찾을 때까지 일정 시간(기본 5초)을 기다린다. 만약 그 시간이 지난 후에도 요소를 찾을 수 없으면 에러를 발생시킨다.
  • queryBy* 쿼리: 이 함수들은 getBy* 처럼 동기적이다. 하지만 요소를 찾을 수 없어도 에러를 발생시키지 않는다. 단지 null 값을 리턴한다.

테스트 디버깅하기

render(<SomeComponent />);
screen.debug(); //원하는 실행 지점에서 DOM트리를 console에 출력해, 디버깅할 수 있다.

screen.debug() 는 브라우저의 개발 도구처럼 편리하고 상호작용적이진 않지만, 테스트 환경에서 어떤 일이 일어나고 있는지 확실히 알 수 있도록 도와준다.

const link = screen.getByRole('link', { name: /how it works/i });
screen.debug(link);

위와 같이 debug 함수에 파라미터를 전달하면 해당 요소만을 콘솔에 출력해준다.

Mocking

  • jest.fn: Mock a function
  • jest.mock: Mock a module → 자동적으로 모듈의 모든 함수를 mocking 해준다.
  • jest.spyOn: Spy or mock a function → 마찬가지로 모든 함수를 mocking 해주면서, 원래의 함수를 다시 복원할 수도 있다.

가장 기본적인 사용 방식은 함수를 mock 함수로 재할당하는 것이다. 재할당 된 함수가 쓰이는 어디서든지 mock 함수가 원래의 함수 대신 호출 될 것이다.

예시
  1. jest.fn() mocking
// app.js
import * as math from './math.js';

export const doAdd      = (a, b) => math.add(a, b);
export const doSubtract = (a, b) => math.subtract(a, b);
export const doMultiply = (a, b) => math.multiply(a, b);
export const doDivide   = (a, b) => math.divide(a, b);
// math.js
export const add      = (a, b) => a + b;
export const subtract = (a, b) => b - a;
export const multiply = (a, b) => a * b;
export const divide   = (a, b) => b / a;
// app.test.js

import * as app from "./app";
import * as math from "./math";

math.add = jest.fn();
math.subtract = jest.fn();

test("calls math.add", () => {
  app.doAdd(1, 2);
  expect(math.add).toHaveBeenCalledWith(1, 2);
});

test("calls math.subtract", () => {
  app.doSubtract(1, 2);
  expect(math.subtract).toHaveBeenCalledWith(1, 2);
});

  1. jest.mock() mocking
jest.mock('./math.js');

위의 코드로 mocking하는 것은 본질적으로 아래 코드처럼 하는거랑 같다.

export const add      = jest.fn();
export const subtract = jest.fn();
export const multiply = jest.fn();
export const divide   = jest.fn();
// app.test.js

import * as app from "./app";
import * as math from "./math";

// Set all module functions to jest.fn
jest.mock("./math.js");

test("calls math.add", () => {
  app.doAdd(1, 2);
  expect(math.add).toHaveBeenCalledWith(1, 2);
});

test("calls math.subtract", () => {
  app.doSubtract(1, 2);
  expect(math.subtract).toHaveBeenCalledWith(1, 2);
});
반응형

메모이제이션이란 계산된 값을 자료구조에 저장하고 이후 같은 계산을 반복하지 않고 자료구조에서 꺼내 재사용하는 것을 말한다. 메모이제이션의 대표적인 예로는 동적계획법의 탑다운 방식이 있다.

 

1. 기본 개념

  • useMemo와 useCallback는 메모이제이션 기능을 지원하는 리액트의 내장 훅으로, 퍼포먼스 최적화를 위하여 사용된다.

  • useMemo는 메모이제이션된 값을 반환한다.

  • useCallback은 메모이제이션된 콜백을 반환한다.

리액트는 실제로는 상태가 변경되는 컴포넌트와 그 이하의 모든 자식 컴포넌트가 랜더링의 대상이 된다. 문제는 자식 컴포넌트의 상태가 변경되지 않아도(갱신될 필요가 없어도) 불필요한 랜더링이 일어난다는 것이다. 자바스크립트에서 함수도 참조형 데이터이기 때문에 늘 새로운 값으로 취급되어 동일성을 보장받지 못하므로 리액트에서 매번 새로운 렌더링 대상이 된다.

 

 

2. useMemo

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

“생성(create)” 함수와 그것의 의존성 값의 배열을 전달해야 한다.useMemo는 의존성이 변경되었을 때에만 메모이제이션된 값만 다시 계산할 것이다. 이 최적화는 모든 렌더링 시의 고비용 계산을 방지하게 해 준다.

  • 배열이 없는 경우 매 렌더링 때마다 새 값을 계산하게 된다.

  • useMemo로 전달된 함수는 렌더링 중에 실행된다.

  • 무분별하게 useMemo를 사용하면 오히려 성능이 저하될 수 있다. 따라서 최대한 사용하지 않고 useEffect Hook 등을 활용해 비동기적으로 동작하도록 고안하는 것이 좋다. (리액트 공식 문서에서 추천하는 방식)

 

3. useCallback

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

콜백과 그것의 의존성 값을 배열로 만들어 인자로 전달하며, 메모이제이션된 콜백함수를 반환한다. 그 메모이제이션된 버전은 콜백의 의존성이 변경되었을 때에만 변경된다. 이것은, 불필요한 렌더링을 방지하기 위해(예로 shouldComponentUpdate를 사용하여) 참조의 동일성에 의존적인 최적화된 자식 컴포넌트에 콜백을 전달할 때 유용하다.

 

  • useCallback(fn, deps) useMemo(() => fn, deps)와 같다고 한다. 

  • useEffect와 마찬가지로 두 번째 인자로 빈 배열([])을 넣으면 어떤 상태 값에도 반응하지 않으며, 두 번째 인자로 아무것도 넣지 않으면 모든 상태 변화에 반응한다.

반응형

1. Component Create

// 클래스 컴포넌트
class ClassComp extends React.Component {
  render() {
    return (
      <div className="container">
        <h2>class style component</h2>
        <p>Number : 2</p>
      </div>
    )
  }
}
// 함수형 컴포넌트
function FuncComp(props) {
  return (
    <div className="container">
      <h2>function style component</h2>
      <p>Number : 2</p>
    </div>
  );
}

 

 

2. Props

import React, { Component } from 'react'

export default class App extends Component {
  render() {
    return (
      <div className="container">
        <h1>Hello World</h1>
        <FuncComp initNumber={2}></FuncComp>
        <ClassComp initNumber={2}></ClassComp>
      </div>
    )
  }
}

// 함수형
// 인자로 props라는 이름으로 편의상 쓰는 것(다른 이름도 가능)
function FuncComp(props) {
  return (
    <div className="container">
      <h2>function style component</h2>
      <p>Number : {props.initNumber}</p>
    </div>
  );
}

// 클래스형
class ClassComp extends React.Component {
  render() {
    return (
      <div className="container">
        <h2>class style component</h2>
        <p>Number : {this.props.initNumber}</p>
      </div>
    )
  }
}

 

 

 

3. State

 

// 함수형
function FuncComp(props) {
  // state 초기화
  let numberState = useState(props.initNumber); // --> useState는 무조건 배열이 리턴
  let number = numberState[0]; //state 사용, 배열의 첫번째: 상태값
  let setNumber = numberState[1]; // state 변경, 배열의 두번째: 상태를 바꿀수 있는 함수

  // 간단 축약형 사용법
  let [_date, setDate] = useState((new Date()).toString());

  return (
    <div className="container">
      <h2>function style component</h2>
      <p>Number : {number}</p>
      <p>Date : {_date}</p>
      <input type="button" value="random" onClick={
        function () {
          setNumber(Math.random());
        }
      } />
      <input type="button" value="date" onClick={
        function () {
          setDate((new Date()).toString());
        }
      } />
    </div>
  );
}

 

Function 컴포넌트에서 state를 사용하기 위해서는 useState Hooks를 사용한다.

 

useState(초기값) - [state value, func] 배열 return

  • useState는 무조건 2개의 값 요소를 갖는 배열이 리턴된다.

  • 초기값을 줬을 때, 리턴된 배열의 첫 번째 요소가 그 값이 된다.

  • 리턴된 배열의 2번째 값은 함수로, state를 바꿀 수 있는 함수다.

 

 

 

// 클래스형
class ClassComp extends React.Component {
  // state 초기화
  state = {
    number: this.props.initNumber,
    date: (new Date()).toString()
  }
  render() {
    return (
      <div className="container">
        <h2>class style component</h2>
        <p>Number : {this.state.number}</p> {/*state 사용 */}
        <p>Date : {this.state.date}</p>
        <input type="button" value="random" onClick={
          function () {
            this.setState({ number: Math.random() }); {/*state 변경 */ }
          }.bind(this)
        } />
        <input type="button" value="date" onClick={
          function () {
            this.setState({ date: (new Date()).toString() }); {/*state 변경 */ }
          }.bind(this)
        } />
      </div>
    )
  }
}

 

Class 컴포넌트에서 안 좋은 점이 bind를 해줘야 하는 것이다. 바인딩을 안 하고 사용하는 방법들이 존재한다. (ex. 화살표 함수...)

  • state 초기화 : constructor를 사용하거나, 위와 같이 사용해 초기화할 수 있다.

  • state 사용 : this.state를 이용한다. 여기서 render 함수 안에서 바인딩을 해줘야 한다.

  • state 변경 : this.setState를 이용한다.

 

 

 

 

4. Life cycle

 

 

useEffect(<function>, <Array>) 

  • <Array> == 생략 : 첫 렌더링 + state 변화에 따른 모든 렌더링 시에 동작한다.

  • <Array> == [ ] : 첫 렌더링 시에만 동작한다. componentDidMount()와 같다.

  • <Array> == [state] : 첫 렌더링 시 동작하고, 배열 안에 넣어준 값의 상태가 변할 때마다 계속 동작한다. componentDidMount() & componentDidUpdate()

  • Clean up : return으로 함수를 지정해주면 clean up작업을 한다. componentWillUnmount()와 같은 동작이다. 즉, 컴포넌트가 수명을 다하고 사라질 때 어떤 행동을 하는 것을 의미한다.

 

 

반응형

React 엘리먼트에서 이벤트를 처리하는 방식은 DOM 엘리먼트에서 이벤트를 처리하는 방식과 매우 유사하다. 몇 가지 차이는 다음과 같다.

  • React의 이벤트는 소문자 대신 캐멀 케이스(camelCase)를 사용한다.

  • JSX를 사용하여 문자열이 아닌 함수로 이벤트 핸들러를 전달한다.

  • React에서는 false를 반환해도 기본 동작을 방지할 수 없습니다. 반드시 preventDefault를 명시적으로 호출해야한다.

 

ES6 클래스를 사용하여 컴포넌트를 정의할 때, 일반적인 패턴은 이벤트 핸들러를 클래스의 메서드로 만드는 것이다.

class Toggle extends React.Component {
  constructor(props) {
    super(props);
    this.state = {isToggleOn: true};

    // 콜백에서 `this`가 작동하려면 아래와 같이 바인딩 해주어야 합니다.
    this.handleClick = this.handleClick.bind(this);
  }
  
  // 토글 이벤트 메서드
  handleClick() {
    this.setState(state => ({
      isToggleOn: !state.isToggleOn
    }));
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        {this.state.isToggleOn ? 'ON' : 'OFF'}
      </button>
    );
  }
}

ReactDOM.render(
  <Toggle />,
  document.getElementById('root')
);

 

- this의 의미와 binding

JSX 콜백 안에서 this의 의미에 대해 주의해야 합니다. JavaScript에서 클래스 메서드는 기본적으로 바인딩되어 있지 않다. this.handleClick을 바인딩하지 않고 onClick에 전달하였다면, 함수가 실제 호출될 때 this undefined가 된다.

 

이는 React만의 특수한 동작이 아니며, JavaScript에서 함수가 작동하는 방식의 일부다. 일반적으로 onClick={this.handleClick}과 같이 뒤에 ()를 사용하지 않고 메서드를 참조할 경우, 해당 메서드를 바인딩 해야 한다.

 

 

- binding 하지 않는 2가지 방법

class LoggingButton extends React.Component {
  // 이 문법은 `this`가 handleClick 내에서 바인딩되도록 합니다.
  // 주의: 이 문법은 *실험적인* 문법입니다.
  handleClick = () => {
    console.log('this is:', this);
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        Click me
      </button>
    );
  }
}

 

  • 만약 클래스 필드 문법을 사용하고 있지 않다면, 콜백에 화살표 함수를 사용하는 방법도 있다.

class LoggingButton extends React.Component {
  handleClick() {
    console.log('this is:', this);
  }

  render() {
    // 이 문법은 `this`가 handleClick 내에서 바인딩되도록 합니다.
    return (
      <button onClick={() => this.handleClick()}>
        Click me
      </button>
    );
  }
}

 

이 문법의 문제점은 LoggingButton이 렌더링될 때마다 다른 콜백이 생성된다는 것이다. 대부분의 경우 문제가 되지 않으나, 콜백이 하위 컴포넌트에 props로서 전달된다면 그 컴포넌트들은 추가로 다시 렌더링을 수행할 수도 있다. 이러한 종류의 성능 문제를 피하고자, 생성자 안에서 바인딩하거나 클래스 필드 문법을 사용하는 것을 권장합니다.

 

 

 

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

<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>

 

위 두 줄은 동등하며 각각 화살표 함수 Function.prototype.bind를 사용한다.

두 경우 모두 React 이벤트를 나타내는 e 인자가 ID 뒤에 두 번째 인자로 전달된다. 화살표 함수를 사용하면 명시적으로 인자를 전달해야 하지만 bind를 사용할 경우 추가 인자가 자동으로 전달된다.

반응형

1. State

  • 컴포넌트 내부에서 선언하며 내부에서 값을 변경할 수 있다.

  • props와의 차이점이라면, state는 컴포넌트 내부에 존재하고 있기 때문에, 상태 값 변경이 가능하다는 것이다.(즉, 구현하는 쪽에 중점을 둔다.)

  • this.setState() 메소드를 통해서 상태 값을 변경해준다.

  • 상위 컴포넌트의 state는 하위 컴포넌트의 props로 전달된다.

 

import React, { Component } from 'react'
import Subject from './components/Subject';
import Content from './components/Content';
import TOC from './components/TOC';

class App extends Component {
  // 초기 this.state를 지정하는 class constructor
  constructor(props) {
    super(props);
    this.state = {
      subject: { title: "WEB", sub: "World Wide Web!" },
      contents: [
        { id: 1, title: 'HTML', desc: 'HTML is for information' },
        { id: 2, title: 'CSS', desc: 'CSS is for design' },
        { id: 3, title: 'JavaScript', desc: 'JavaScript is for interactive' }
      ]
    }
  }

	// this.state를 이용해 state 값 사용
     // 상위 컴포넌트의 state는 하위 컴포넌트의 props로 전달된다.!!!!!!
  render() {
    return (
      <div className="App">
        <Subject title={this.state.subject.title} sub={this.state.subject.sub} />
        <TOC data={this.state.contents} />
        <Content />
      </div>
    );
  }
}


export default App;

 

클래스 컴포넌트는 항상 props로 기본 constructor를 호출해야 한다.

 

- Constructor(생성자) : JS Class 문법

constructor 메소드는 class 로 생성된 객체를 생성하고 초기화하기 위한 특수한 메서드다.  "constructor"라는 이름을 가진 특수한 메서드는 클래스 안에 한 개만 존재할 수 있다. 만약 클래스에 여러 개의 constructor 메서드가 존재하면 SyntaxError 가 발생할 것이다. constructor는 부모 클래스의 constructor를 호출하기 위해 super 키워드를 사용할 수 있다.

 

State 객체를 사용하고 싶다면 컴포넌트를 생성할 때 가장 윗부분(render() 함수보다 먼저)에 constructor() 함수를 적어준다. 컴포넌트 생성자에서 super를 호출하기 전에는 this를 사용할 수 없기 때문이다. 즉, 컴포넌트의 시작 부분에서 constructor()constructor()라는 함수가 컴포넌트의 초기화를 시켜줘야 StateState에 값을 넣어 사용할 수 있는 것이다.

 

 

- super(props)를 써야 하는 이유

자바스크립트에서 supersuper는 부모 클래스 생성자의 참조다. 그리고 자바스크립트는 언어적 제약사항으로서 생성자에서 super를

 

Hooks를 사용한다면 super  this에 대해 고민하지 않아도 된다!!!

 

 

2. setState

 

- 컴포넌트에서 state를 직접 바꾸면 안 된다.

this.state를 지정할 수 있는 유일한 공간은 바로 constructor입니다.

// Wrong
this.state.comment = 'Hello';

 

다음과 같이 setState() 함수를 사용해서 state를 변경해야 한다.

// Correct
this.setState({comment: 'Hello'});

 

- State 업데이트는 비동기적일 수도 있다.

 

this.props와 this.state가 비동기적으로 업데이트될 수 있기 때문에 다음 state를 계산할 때 해당 값에 의존해서는 안 된다.

// Wrong
this.setState({
  counter: this.state.counter + this.props.increment,
});

 

props와 state의 비동기적 업데이트를 고려해 함수를 인자로 사용하는 다른 형태의 setState()를 사용하면 된다. 그 함수는 이전 state첫 번째 인자로 받아들일 것이고, 업데이트가 적용된 시점의 props두 번째 인자로 받아들일 것이다.

// Correct
this.setState((state, props) => ({
  counter: state.counter + props.increment
}));

// Correct
this.setState(function(state, props) {
  return {
    counter: state.counter + props.increment
  };
});

 

 

- 독립적으로 state들을 업데이트할 수 있다.

 

  constructor(props) {
    super(props);
    this.state = {
      posts: [],
      comments: []
    };
  }
  
  // 독립적으로 각각 업데이트
  componentDidMount() {
    fetchPosts().then(response => {
      this.setState({
        posts: response.posts
      });
    });

    fetchComments().then(response => {
      this.setState({
        comments: response.comments
      });
    });
  }

 

 

 

반응형

1. Function Component & Class Component

 

// 함수형 컴포넌트
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

이 함수는 데이터를 가진 하나의 “props” (props는 속성을 나타내는 데이터) 객체 인자를 받은 후 React 엘리먼트를 반환한다.

 

// 클래스형 컴포넌트
class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

ES6 Class를 사용해 컴포넌트를 정의하면 된다.

 

- 함수형에서 클래스형 컴포넌트로 바꾸기

  1. React.Component를 확장하는 동일한 이름의 ES6 class를 생성합니다.

  2. render()라고 불리는 빈 메서드를 추가합니다.

  3. 함수의 내용을 render() 메서드 안으로 옮깁니다.

  4. render() 내용 안에 있는 props를 this.props로 변경합니다.

 

 

※ 주의

사용자 정의 컴포넌트는 반드시 대문자로 시작해야 한다.

Element가 소문자로 시작하는 경우에는 <div>  <span> 같은 내장 컴포넌트라는 것을 뜻하며 'div'  'span' 같은 문자열 형태로 React.createElement에 전달된다. <Foo />와 같이 대문자로 시작하는 타입들은 React.createElement(Foo)의 형태로 컴파일되며 JavaScript 파일 내에 사용자가 정의했거나 import 한 컴포넌트를 가리킨다고 한다.

 

 

 

2. Props

props는 읽기 전용이다.

함수 컴포넌트나 클래스 컴포넌트 모두 컴포넌트의 자체 props를 수정해서는 안된다.

 

App.js
// APP.js
import React, { Component } from 'react'
import Subject from './components/Subject'; //컴포넌트 import
import Content from './components/Content'; //컴포넌트 import


class App extends Component {
  render() {
    return (
      <div className="App">
        <Subject title="WEB" sub="Wold Wide Web!" /> // props 전달
        리액트 공부하기!
        <Content title="HTML" desc="HTML is ..." /> // props 전달
      </div>
    );
  }
}

export default App;

 

하위 컴포넌트에 전달할 데이터를 props로 전달한다. 여기선 <Subject>, <Content> 각 컴포넌트에 porps를 전달해줬다.

 

 

Subject.js
// Subject.js
import React, { Component } from 'react'

export default class Subject extends Component {
  render() {
    console.log('Subject render!');
    return (
      <header>
        <h1>{this.props.title}</h1> // 전달받은 props를 사용해 렌더링한다.
        {this.props.sub}
      </header>
    )
  }
}

 

Content.js
// Content.js
import React, { Component } from 'react'

export default class Content extends Component {
  render() {
    return (
      <article>
        <h2>{this.props.title}</h2> // 전달받은 props를 사용해 렌더링한다.
        {this.props.desc}
      </article>
    )
  }
}

 

전달받은 props를 사용할 때는 현재 Class를 가리키는 this를 사용한다.

this.props.(전달받은 props 명) 형식으로 중괄호와 함께 사용해주면 받은 데이터를 렌더링 할 수 있게 된다.

 

 

 

- 결과 화면

<결과 화면>

반응형

Hooks는 리액트 v16.8에 새로 도입된 기능으로서, 함수형 컴포넌트에서도 상태 관리를 할 수 있는 useState, 그리고 렌더링 직후 작업을 설정하는 useEffect 등의 기능 등을 제공하여 기존의 함수형 컴포넌트에서 할 수 없었던 다양한 작업을 할 수 있게 해 준다. Hook은 함수형 컴포넌트에서 React의 특징을 갖게 해주는 함수다.

 

- Hook 사용 규칙

  • 최상위(at the top level)에서만 Hook을 호출해야 한다. 반복문, 조건문, 중첩된 함수 내에서 Hook을 실행하지 말아야한다.
  • React 함수 컴포넌트 내에서만 Hook을 호출해야 한다. 일반 JavaScript 함수에서는 Hook을 호출해서는 안 됩니다. (Hook을 호출할 수 있는 곳이 딱 한 군데 더 있다. 바로 직접 작성한 custom Hook 내이다.)

 

1. useState

import React, { useState } from 'react';

function Example() {
  // "count"라는 새 상태 변수를 선언합니다
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

- 형태

const [value, setValue] = useState(0); // [현재 state, 업데이트 함수]와 state 초기값
function ExampleWithManyStates() {
  // 상태 변수를 여러 개 선언했습니다!
  const [age, setAge] = useState(42);
  const [fruit, setFruit] = useState('banana');
  const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
  // ...
}

 

현재의 state값이 값을 업데이트하는 함수배열 할당 형태로 사용한다. 이 함수를 이벤트 핸들러나 다른 곳에서 호출해 사용 가능하다. state 초기값을 첫 번째 렌더링에만 딱 한번 사용된다.

 

 

2. useEffect

// React가 DOM을 업데이트한 뒤에 문서의 타이틀을 바꾸는 컴포넌트
import React, { useState, useEffect } from 'react';

function Example() {
  const [count, setCount] = useState(0);

  // componentDidMount, componentDidUpdate와 비슷합니다
  useEffect(() => {
    // 브라우저 API를 이용해 문서의 타이틀을 업데이트합니다
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

 

useEffect는 리액트 컴포넌트가 렌더링 될 때마다 특정 작업을 수행하도록 설정할 수 있는 Hook이다. React class의 componentDidMount  componentDidUpdate, componentWillUnmount와 같은 목적으로 제공되지만, 하나의 API로 통합된 것이다.

 

 

2-1. useEffect의 두 번째 인수 사용

 

- 특정 값을 넣어주는 경우

 

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // count가 바뀔 때만 effect를 재실행합니다.

2번째 인자로 배열이 주어지는데, 배열 안 값이 리렌더링 시에 변경되지 않는다면 리액트는 effect를 건너뛴다. 즉 안의 값이 변할 때만 리렌더링을 한다. 배열 내에 여러 개의 값이 있다면 그중의 단 하나만 다를지라도 리액트는 effect를 재실행합니다.

 

 

- 빈 배열을 인자로 줄 경우

 

  useEffect(() => {
    console.log('마운트 될 때만 실행됩니다.');
  }, []);

빈 배열([])을 넘기게 되면, effect 안의 prop과 state는 초깃값을 유지하게 된다. 즉, 화면에 가장 처음 렌더링 될 때만 실행되고 업데이트할 경우에는 실행할 필요가 없는 경우에 빈 배열을 설정해준다. effect를 실행하고 이를 정리(clean-up)하는 과정을 (마운트와 마운트 해제 시에) 딱 한 번씩만 실행하고 싶다면, 빈 배열([])을 두 번째 인수로 넘기면 된다. 

 

useEffect(<function>, <Array>) 

  • <Array> == 생략 : 첫 렌더링 + state 변화에 따른 모든 렌더링 시에 동작한다.

  • <Array> == [ ] : 첫 렌더링 시에만 동작한다. componentDidMount()와 같다.

  • <Array> == [state] : 첫 렌더링 시 동작하고, 배열 안에 넣어준 값의 상태가 변할 때마다 계속 동작한다. componentDidMount() & componentDidUpdate()

  • Clean up : return으로 함수를 지정해주면 clean up작업을 한다. componentWillUnMount()와 같은 동작이다. 즉, 컴포넌트가 수명을 다하고 사라질 때 어떤 행동을 하는 것을 의미한다.

 

2-2. 정리(Clean-up)를 이용하는 Effects

 useEffect(() => {
    console.log('effect');
    console.log(name);
    
    // effect 이후에 어떻게 정리(clean-up)할 것인지 표시
    return () => {
      console.log('cleanup');
      console.log(name);
    };
  });

 

 

반응형

+ Recent posts