React Hook을 알아보자
FE BE 개발 메모장/React

React Hook을 알아보자

 리액트 컴포넌트는 클래스 컴포넌트와 함수 컴포넌트로 나뉜다. 기존의 개발 방식은 함수 컴포넌트를 주로 사용하면서 상태를 바꿔줄때나 생명주기(LifeCycle)를 사용해야할 경우만 클래스 컴포넌트롤 사용하는 방식이었다. 하지만 클래스 컴포넌트에선 문제점이 있었는데 다음과 같다.

 

1. 코드가 길고 복잡해진다.

 생명주기, 상태관리를 위해선 constructor, this, bind, componentWillMount 등등... 지켜야할 규칙들이 너무 많기 때문에 코드가 복잡해지고 가독성도 떨어졌다.

 

아래 예시는 React 공식문서에 나온 예시다.

  • 클래스 컴포넌트에서 작성된 Toggle
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')
);

 

  • Hook에서 작성된 함수 컴포넌트
function Toggle() {
  let [isToggle, setIsToggle] = useState(true); //구조 분해할당으로 상태를 지정
  
  const handleClick = () => {
    return setIsToggle(false);
  }

  return (
    <button onClikc={handleClick}> 
     {isToggle ? 'ON' : 'OFF'}
    </button>
  )
}


//이렇게 바꿔줄수도 있을 것이다.

function Toggle() {
  let [isToggle, setIsToggle] = useState(true); //구조 분해할당으로 상태를 지정

  return (
    <button onClikc={() => setIsToogle(false)}> 
     {isToggle ? 'ON' : 'OFF'}
    </button>
  )
}

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

 

훨신 더 깔끔하고 간결해졌다.

 

2. 로직(Logic)의 재사용이 어렵다.

 클래스 컴포넌트에서는 Hight-Order Components(HOC)로 컴포넌트 자체를 재사용 할 수 있지만 부분적인 DOM 관련 처리나 API 사용, 상태(state)를 다루는 등의 로직에 있어서는 경우에 따라서 생명주기 메소드에 중복해서 넣어야 하는 등 재사용에 제약이 따른다.

 반면 Hook을 활용한 함수 컴포넌트에서는 원하는 기능을 함수로 만든 후 필요한 곳에 넣어주면 되서 재사용이 가능하다.

 

3. 성능

React측에서 함수형 컴포넌트의 퍼포먼스를 상승시킬것이라고 예전에 발표한적이 있었다.

https://medium.com/missive-app/45-faster-react-functional-components-now-3509a668e69f

 

45% Faster React Functional Components, Now

Jump to TL;DR at the end to see one easy change you can apply to your React code to get a free speedup. For more details, read on.

medium.com

이러면 굳이 불편한 클래스 컴포넌트를 고집할 필요가 없어졌다.

 

React Hook

 Hook은 공식문서에따르면 React 버전 16.8부터 새로 추가된 요소이다. Hook을 이용하게되면 기존의 Class 방식으로 코드를 작성할 필요 없이 상태 값과 여러 React의 기능을 사용할 수 있다. 기존에서는 생명주기 같이 상태를 관리하기 위해서는 클래스(Class) 컴포넌트에서 작성해주었는데, Hook이 나온 이후로는 함수 컴포넌트에서도 충분히 상태를 관리할 수 있게되었다.

 

생명주기(LifeCycle)

  • React에서의 LifeCycle

  • Hooks의 Life Cycle

 

생명 주기에서조차 훨씬 짧아지고 간결해졌다.

 

componentDidMount, componentDidUpdate, componentWillUnmount 같은 절차들은 UseEffect로 압축되었고, 상태관리는 useState()에게 맡겨주면 된다. 

 

 

이제 다시한번 React 공식문서에 나와있는 예제를 Hook으로 바꿔보았다.

 

React

const scaleNames = {
  c: 'Celsius',
  f: 'Fahrenheit'
};

function toCelsius(fahrenheit) {
  return (fahrenheit - 32) * 5 / 9;
}

function toFahrenheit(celsius) {
  return (celsius * 9 / 5) + 32;
}

function tryConvert(temperature, convert) {
  const input = parseFloat(temperature);
  if (Number.isNaN(input)) {
    return '';
  }
  const output = convert(input);
  const rounded = Math.round(output * 1000) / 1000;
  return rounded.toString();
}

function BoilingVerdict(props) {
  if (props.celsius >= 100) {
    return <p>The water would boil.</p>;
  }
  return <p>The water would not boil.</p>;
}

class TemperatureInput extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
  }

  handleChange(e) {
    this.props.onTemperatureChange(e.target.value);
  }

  render() {
    const temperature = this.props.temperature;
    const scale = this.props.scale;
    return (
      <fieldset>
        <legend>Enter temperature in {scaleNames[scale]}:</legend>
        <input value={temperature}
               onChange={this.handleChange} />
      </fieldset>
    );
  }
}

class Calculator extends React.Component {
  constructor(props) {
    super(props);
    this.handleCelsiusChange = this.handleCelsiusChange.bind(this);
    this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this);
    this.state = {temperature: '', scale: 'c'};
  }

  handleCelsiusChange(temperature) {
    this.setState({scale: 'c', temperature});
  }

  handleFahrenheitChange(temperature) {
    this.setState({scale: 'f', temperature});
  }

  render() {
    const scale = this.state.scale;
    const temperature = this.state.temperature;
    const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature;
    const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature;

    return (
      <div>
        <TemperatureInput
          scale="c"
          temperature={celsius}
          onTemperatureChange={this.handleCelsiusChange} />
        <TemperatureInput
          scale="f"
          temperature={fahrenheit}
          onTemperatureChange={this.handleFahrenheitChange} />
        <BoilingVerdict
          celsius={parseFloat(celsius)} />
      </div>
    );
  }
}

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

React Hook

import { useState } from "react";

function TemperatureInput({ scale, temp, onChange }) {
  const handleChange = (e) => {
    onChange(e.target.value);
  };

  return (
    <fieldset>
      <div>Enter temperature in {scale}:</div>
      <input type="text" value={temp} onChange={handleChange} />
    </fieldset>
  );
}

function BoilingVerdict({ celsius }) {
  if (celsius >= 100) {
    return <p>The water would boil.</p>;
  }
  return <p>The water would not boil.</p>;
}

export default function Calculator() {
  const [scale, setScale] = useState("C");
  const [temp, setTemp] = useState("");

  const handleCelsiusChange = (temp) => {
    setScale("C");
    setTemp(temp);
  };

  const handleFahrenheitChange = (temp) => {
    setScale("F");
    setTemp(temp);
  };

  const toCelsius = (fahrenheit) => {
    return ((fahrenheit - 32) * 5) / 9;
  };

  const toFahrenheit = (celsius) => {
    return (celsius * 9) / 5 + 32;
  };

  const tryConvert = (temperature, convert) => {
    const input = parseFloat(temperature);
    if (Number.isNaN(input)) {
      return "";
    }
    const output = convert(input);
    const rounded = Math.round(output * 1000) / 1000;
    return rounded.toString();
  };

  const celsius = scale === "F" ? tryConvert(temp, toCelsius) : temp;
  const fahrenheit = scale === "C" ? tryConvert(temp, toFahrenheit) : temp;

  return (
    <div>
      <TemperatureInput
        scale="c"
        temp={celsius}
        onChange={handleCelsiusChange}
      />
      <TemperatureInput
        scale="f"
        temp={fahrenheit}
        onChange={handleFahrenheitChange}
      />
      <BoilingVerdict celsius={parseFloat(celsius)} />
    </div>
  );
}


 

React Hook의 특징