if(재능이 없으면 시간으로 극복하라){return 성공;}

React - Hook이란 무엇인가?

Hook이란 무엇인가?

Hook이라는건 함수 컴포넌트 (Functional Component)에서 생명주기 기능을 연동할 수 있게 해주는 함수이다. Class Component에서 사용할 수 없고 대신 Class없이 React를 사용할 수 있다.

Hook은 지켜야할 규칙이 있다.

  • 최상위(at the top level)에서만 Hook을 호출해야 합니다. 반복문, 조건문, 중첩된 함수 내에서 Hook을 실행하지 않는다.
  • Functional Component(함수형 컴포넌트)에서만 사용해야 한다.

여러가지 Hook에 대해서 알아보자.

  1. useEffect - 컴포넌트가 처음 마운트되거나 리랜더링 되거나 redux sotre 값이 변경될 때 실행할 구문들을 정의해놓은 함수. 구체적인 사용법과 예제를 알아보자.
    import { useEffect } from 'react';
    

    상단에 useEffect를 import하고 사용하면 된다.

    • useEffect를 통해 실행할 내용만 있을때
      useEffect(() => {
      ... // 실행할 내용들
      });
      
    • 최초 마운트 된 시점에 실행할게 있을 때
      useEffect(() => {
      ... // 실행할 내용들
      }, []);
      

[]을 dependency라고 하는데, 이 dependency의 값이 변경될 때 useEffect함수가 실행되도록 할 수 있다.

  • 최초 마운트 된 시점에 실행할게 있을 때
    useEffect(() => {
    ... // 실행할 내용들
    }, [value]);
    

위 처럼 dependency에 변수를 추가해주면 Vue의 watcher처럼 dependency 내의 value값이 변경될 때 함수를 실행할 수 있도록 할 수 있다.

언마운트시에 실행할 수 있도록 하는 방법도 있다.

useEffect(() => {
  ... // 실행할 내용
  
  return () => {
    ... // cleanup
  }
});
  1. useState - 상태의 초기값을 인수로 전달 받아 호출되는 함수 상단에 useState를 import 하고 ```javascript import { useState } from ‘react’;

아래와 같은 방법으로 State를 바꿀 수 있다.
```javascript
const [현재 State, State를 바꾸면서 실하는 함수] = useState(상태 초기 값);

고전적인 예제를 보자.

// src/Counter.jsx
import React from 'react';
import { useState } from 'react';

const Counter = () => {
  const [number, setNumber] = useState(0); /*state인 number에 0으로 숫자를 할당하고, setNumber함수를 통해 값을 바꾼다.*/

  const plusNum = (e) => {
    setNumber(preNum => preNum + 1)
  }
  const minusNum = (e) => {
    setNumber(preNum => preNum - 1)
  }
  
  return (
    <div>
      <button type="button" onClick={plusNum}>+</button>
      <span> {number} </span>
      <button type="button" onClick={minusNum}>-</button>
    </div>
  );
};

export default Counter;
// src/App.jsx
import Counter from './Counter';

function App() {
  return (
    <div>
      <Counter />
    </div>
  );
}

export default App;

다음으로는 여러개 text를 바꾸는 예제를 확인해보자.

import React, { useState } from 'react';

const Input = () => {
  const [input, setInput] = useState({ /*input은 title과 contents의 값을 빈값으로 가지고 있고, setinput 함수를 통해 값을 변경한다.*/
    title: '',
    contents: ''
  });

  const {title, contents} = input;
  const onChange = (e) => {
    // 이벤트를 부른 요소의 value와 name 키의 값 가져오기
    // name은 title or contents
    // value는 그 때의 텍스트
    const {value, name} = e.target;

    setInput({
      ...input, // 기존 input 객체를 복사한 뒤
      [name]: value  // title or contents 키를 가진 값을 value로 설정
    })
  };
  
  return (
    <>
      <input type="text"
        name="title"
        value={title}
        placeholder="제목을 입력해주세요"
        onChange={onChange} />
      <br/>
      <textarea 
        name="contents"
        value={contents}
        cols="30"
        rows="10"
        onChange={onChange}></textarea>
    </>
  );
};

export default Input;
  1. useCallback - useCallback은 함수를 memoization(프로그래밍시에 반복되는 결과를 메모리에 저장해두고 다음에 같은 결과가 나올 때 다시 계산할 필요없이 빠르게 실행하는 기법)을 하기 위해서 사용하는 함수. 첫번째 인자로 넘어온 함수를 두번째 인자로 넘어온 배열 내의 값이 변경될 때까지 저장해놓고 재사용 할수 있도록 하는 함수이다. 함수 안에 함수가 선언되어 있다면 컴포넌트가 랜더링될 때 마다 새로운 함수가 생성된다. useCallback을 사용하면 해당 컴포넌트가 랜더링되더라도 그 함수가 의존하는 값이 바뀌지 않는 한 기존 함수를 계속해서 반환한다.
    const functionOne = function() {
      return 5;
    };
    const functionTwo = function() {
      return 5;
    };
    console.log(functionOne === functionTwo); // false
    

    위와 같은 예제를 보면 결과는 같지만 사실은 다른 함수이다. 예제처럼 한 함수가 고유성을 갖고 있다는 것을 알 수 있다.

한 컴포넌트안에 함수를 선언했다고 가정을 해보자. 그리고 컴포넌트를 여러번 호출하여 랜더링을 했다고 한다면 컴포넌트 내의 함수는 각각 고유성을 갖고 있는 것이다. 하지만 리턴하는 값을 같을 수 있다. 이러한 경우 계속 계산을 하여 가져올 필요 없이 메모리에 저장해두고 다음에 같은 결과가 나오게 된다면 리턴할 수 있도록 하는 것이 useCallback의 사용법이다.

아래 사용법을 참고.

import React, { useCallback } from 'react'

function MyComponent() {
  // handleClick 은 완전히 같은 오브젝트다.
  const handleClick = useCallback(() => {
    console.log('Clicked!')
  }, [])

  // ...
}
  1. useContext A->B->C 의 부모 자식 관계를 가진 컴포넌트가 있다. A에서 C로 props를 옮기려면 B를 무조건 사용해야 했다. 이러한 문제를 해결하기 위해선 Redux를 쓰곤 했는데.. useContext를 이용하여 이러한 경우를 방지할 수 있다. 결국 단계마다 일일히 prop를 넘겨주지 않고도 컴포넌트 트리 전체에 데이터를 공유할 수 있다.

App.js 부모 컴포넌트를 만들고 상단에 import createContext를 해준다. 이후 내보낼 Context를 객체로 만든다. Context에 담을 값을 변수에 담는다. 만든 Context 객체의 Value에 변수를 담고 Provider를 통해 전달된다.

import React, {Children, createContext} from 'react'
import ChildrenComp from './pages/ChildrenComp'

export const Appcontext = createContext();
function App() {
  const info = {
    name: "nuruhee",
    job: "programmer"
  };  

  return(
    <>
    <Appcontext.Provider value={info}>
      <div>
        <ChildrenComp></ChildrenComp>
      </div>
    </Appcontext.Provider>
    </>
  );
}

export default App
}

ChildrenComp App.js에서 넘긴 Appcontext를 import 받고 Appcontext.Consumer를 통해 값을 받는다.

import React from 'react'
import { Appcontext } from '../App'

function ChildrenComp(){
  return(
    <Appcontext.Consumer>
      {(info) => (
      <>
        <span> Appcontext에 존재하는 name 값을 증명해봅시다. {info.name} </span>
        <span> Appcontext에 존재하는 job 값을 증명해봅시다. {info.job} </span>
      </>
      )}
    </Appcontext.Consumer>  
  )
 }

 export default ChildrenComp;

Image

받는 값이 여러가지라면 소스가 더러워 질 수있다. useContext를 사용하면 위와 같은 소스를 똑같이 깔끔하게 사용할 수 있다.

import React, { useContext }  from 'react'
import { Appcontext } from '../App'

function ChildrenComp(){
  const info = useContext(Appcontext);
  return(
    <>
      <span> Appcontext에 존재하는 name 값을 증명해봅시다. {info.name} </span>
      <span> Appcontext에 존재하는 job 값을 증명해봅시다. {info.job} </span>
    </>
  )
 }

 export default ChildrenComp;
  1. useMemo 성능 최적화를 위해, 연산된 값을 useMemo를 통해 재사용하는 함수이다. 위에서 useCallback과 같은 역할인 것 같지만 엄연히 말하면 다르다고 한다. 어떻게 다른지 확인해보자.
    useMemo(() => fn, [dependency])
    

위의 dependency가 변하게 되면 fn 함수를 실행하고, 그 함수의 반환 값을 반환한다. useCallback은 함수를 반환하지만 useMemo는 함수의 반환값을 반환한다는 것이다.

결국 최적화를 위해 사용하는 것은 동일하지만 dependency에 따라 반환값이 다르다는 것 정도만 이해하면 되겠다.

Hook은 더 다양한 종류의 Hook 내장함수를 가지고 있지만 주로 쓰는 다섯가지에 대해 정리해보았다.

필요하면 그때 그때 또 찾아서 사용해보자!