본문 바로가기

웹 프레임워크/Javascript - React.js

[Javascript] React Hook Guide

728x90

useState

  • 가장 기본적인 함수형 컴포넌트의 Hook API
  • 데이터를 담거나 setter를 이용하여 업데이트
  • 정의 시, 값을 가져올 변수와 설정할 변수(setter)를 deps에 적재
    • 주의할 점은 값을 가져올 변수와 설정할 변수의 명명규칙
    • 설정할 변수를 set+가져올 변수에 카멜케이스로 명명
  • useState()내에는 String이면 '', Integer이면 0, Object면 {} 등. 사용할 타입에 따라 default값을 입력
  • 함수 내에서 값을 업데이트 시 setName를 사용
// index.js

import React, { useState } from 'react';

const Index = () => {
  const [name, setName] = useState(''); // or React.useState('');

  const handleInputName = (e) => setName(e.target.value);

  return (
    <div>
      <input type="text" value={name} onChange={handleInputName} />
      {name}
    </div>
  );
};
  • 객체타입 데이터를 업데이트 시 주의할 점은 데이터의 현재 상태에서 반영의 여부를 결정하는 것이 중요
  • 현재 상태를 반영 시에 현재 useState상태값을 적재
// index.js

import React, { useState } from 'react';

const Index = () => {
  const [product, setProduct] = useState({
    name: '',
    price: 0,
    isSale: false,
  });

  // product를 setProduct에 넣어주면서 변경할 데이터의 key: value를 적재
  const handleInputProduct = (e) => setProduct({ ...product, [e.target.name]: e.target.value});

  return (
    <div>
      <input type="text" name="name" value={product.name} onChange={handleInputProduct} />
      <input type="number" name="price" value={product.price} onChange={handleInputProduct} />
      <input type="radio" name="isSale" value={true} checked={product.isSale} onChange={handleInputProduct} />
      <input type="radio" name="isSale" value={false} checked={!product.isSale} onChange={handleInputProduct} />
      {name}
    </div>
  );
};

useEffect

  • 가장 기본적인 함수형 컴포넌트의 Hook API
  • 주로 컴포넌트 상태를 업데이트를 반영할때 사용
  • 클래스형 컴포넌트의 라이프 사이클에선 componentDidMount 와 기능이 동일
  • dependency array(이하 deps)에 state variable를 적재하면 값이 변경될 때마다 실행되며 Re-render 발생
    • deps를 정의하지 않을 시, 컴포넌트의 모든 업데이트마다 Re-render
    • deps를 빈배열로 정의 시, 상태를 업데이트 하지 않음
    • 조건부로, deps에 특정 상태를 적재시 deps내의 상태가 업데이트될 때 Re-render가 발생
  • state variable이 primitive types(이하 Boolean, Number, String, Null, Undefined)인경우, 값이 변경될 때마다 useEffect을 실행하게 되며,
    Object(Object, Function, Array)인 경우 rendering마다 실행
  • componentDidUnmount 구현은 useEffect에 return 을 정의
// index.js
import React, { useState, useEffect } from 'react';

const Index = () => {
  const [name, setName] = useState('');
  const [product, setProduct] = useState({
    productName: '',
    productPrice: 0,
  });

  // deps 정의 x
  useEffect(() => {
    console.log(name);
    console.log(product);
  });

  // deps 정의 o
  useEffect(() => {
    // 이 경우 로그에 찍히지 않음
    console.log(name);
    console.log(product);
  }, []);

  // deps 정의 o, 조건부
  useEffect(() => {
    // deps에 적재한 name만 업데이트 된 값을 볼 수가 있음
    console.log(name);
    console.log(product);

    // 언마운트시 콘솔에 찍힌다.
    return () => {
      console.log('unMount, ', name);
    };
  }, [name]);

  const handleInputName = (e) => setName(e.target.value);

  const handleInputProduct = (e) => setProduct({ ...product, [e.target.name]: e.target.value });

  return (
    <div>
      <input type="text" name="name" value={name} onChange={handleInputName} />
      <input type="text" name="productName" value={product.name} onChange={handleInputProduct} />
      <input type="number" name="productPrice" value={product.price} onChange={handleInputProduct} />
    </div>
  );
};

성능 최적화를 위한 Hook

useMemo

  • 상위 컴포넌트로부터 props를 내릴 때, 하위 컴포넌트는 서로 다른 함수로 각각의 값을 연산하여 변경되면 렌더링.
    그 과정에서 변경되지 않은 값까지 함수를 다시 호출하여 재계산 하는 낭비가 발생
    useMemo는 그런 값들을 다시 호출하여 재계산하지 않고 반환
// index.js
import React, { useState, useCallback } from 'react';

import Info from './info';

const Index = () => {
  const [color, setColor] = useState('');
  const [movie, setMovie] = useState('');

  const handleChangeState = useCallback((e) => {
    if (e.target.id === 'color') setColor(e.target.value);
    else setMovie(e.target.value);
  }, []);

  return (
    <div>
      <label htmlFor="color">
        What is your favorite color of rainbow?
        <input type="text" id="color" value={color} onChange={handleChangeState} />
      </label>
      <div>
        What is your favorite movie among these?
        <label htmlFor="movie">
          <input type="radio" name="movie" value="Marriage Story" onChange={handleChangeState} />
          Marriage Story
        </label>
        <label htmlFor="movie">
          <input type="radio" name="movie" value="The Fast And The Furious" onChange={handleChangeState} />
          The Fast And The Furious
        </label>
      </div>
      <Info color={color} movie={movie} />
    </div>
  );
};

export default Index;
// Info.js
import React, { useMemo } from 'react';

const getColorKor = color => {
    console.log("getColorKor");
    switch (color) {
      case 'red':
        return '빨강';
      case 'orange':
        return '주황';
      case 'yellow':
        return '노랑';
      case 'green':
        return '초록';
      case 'blue':
        return '파랑';
      case 'navy':
        return '남';
      case 'purple':
        return '보라';
      default:
        return '레인보우';
    }
  };

  const getMovieGenreKor = movie => {
    console.log('getMovieGenreKor');
    switch (movie) {
      case 'Marriage Story':
        return '드라마';
      case 'The Fast And The Furious':
        return '액션';
      default:
        return '아직 잘 모름';
    }
  };

const Info = ({ color, movie }) => {
  const colorKor = useMemo(() => getColorKor(color), [color]);
  const movieGenreKor = useMemo(() => getMovieGenreKor(movie), [movie]);

  return (
    <div className="info-wrapper">
      제가 가장 좋아하는 색은 {colorKor} 이고, <br />
      즐겨보는 영화 장르는 {movieGenreKor} 입니다.
    </div>
  );
};

export default Info;
  • useMemo 내 deps에 적재한 값이 변경되었을 때만 메모리제이션된 값을 다시 계산

useCallback

  • 컴포넌트가 렌더링 될 때마다 내부에 선언된 것들도 매번 다시 선언

    • 함수 또는 변수들

      이와 같은 낭비를 막기 위한 useCallback 함수는 메모리제이션 된 함수를 반환
      useCallback을 사용 시에 React.memo로 컴포넌트를 래핑하여 사용

  • 함수의 생성 자체가 오래 걸리는 경우 사용

  • 변겅되는 값이 있을 때 실행되는 hook API

  • 자식컴포넌트에 함수를 props로 내릴 때는 useCallback을 반드시 사용(자식의 Re-rendering 방지)

  • useMemo에 useCallback에 대한 예제도 포함 참고.

memo

  • 동일한 props로 자주 Component rendering이 일어나는 경우
    컴포넌트가 동일한 props를 지속적으로 받아 rendering이 일어나는 경우

  • 클래스형 컴포넌트 라이프사이클인 shouldComponentUpdate와 PureComponent의 역할을 대체

  • 컴포넌트를 React.memo()로 래핑하면 리액트는 컴포넌트를 Rendering하고 결과를 메모이징한다

    • 이후 다음 렌더링 시 props가 달라지지 않았다면, 메모이징 된 값 재사용
    • 이 이점이 클래스컴포넌트의 PureComponent와 동일
  • props 또는 props객체를 비교하여 메모이징의 여부를 반환

    • 이 이점이 클래스컴포넌트의 shouldComponentUpdate와 동일

    • 다만 shouldComponentUpdate와 반대로 props를 비교할때 이전 props와 같다면 true를 반환, 다를 땐 false를 반환

      import React from 'react';
      
      const Example = (props) => {
      const { count } = props;
      };
      
      const areEqual = (prevProps, nextProps) => {
      return  prevProps.count === nextProps.count;
      };
      
      export default React.memo(Example, areEqual);
  • 동일한 props로 Re-rendering이 자주 일어난다면 적용하는 것이 좋지만, props가 자주 변하는 컴포넌트에선 React.memo()의 이점을 얻기 어려움
    즉, 성능의 최적화가 되지 않는다면 이러한 HOC또는 성능최적화를 위한 HookAPI사용을 피하는 것이 좋음
    오히려 성능 악화의 결과로 이어짐

  • 콜백함수를 props로 사용하는 컴포넌트를 메모이징 할 때, useCallback과 함께 사용하는 것이 중요

728x90