[React] Hooks

DongDong·2022년 12월 26일
0

React에 대한 이해

목록 보기
1/3

Hooks

Hooks란 리액트 16.8 버전에서부터 새로 도입된 기능으로서
기존 함수형 컴포넌트에서 할 수 없었던 다양한 작업을 할 수 있게 해준다.
과거 클래스형 컴포넌트에서 함수형 컴포넌트로 많이 이동하게된 이유이고,
리액트 공식 홈페이지에서도 함수형 컴포넌트의 사용을 권장하고 있다.

📌 useState

const [state, setState] = useState(initialState);

useState는 가장 기본적인 Hook으로서
함수형 컴포넌트에서도 가변적인 상태를 지니고 있을 수 있게 해준다.
만약 함수형 컴포넌트에서 상태를 관리해야하는 일이 생긴다면 useState를 사용하게 된다.

🔎 useState 사용예시

// useState 를 사용 할 땐 코드의 상단에서 import 구문을 통하여 불러오고, 다음과 같이 사용한다.
import React, { useState } from 'react';

const Counter = () => {

  // useState라는 함수에 인자로 0을 넣고 호출한 결과 값을 구조분해할당하여 value , setValue라는 변수에 담는다.
  // value는 useState 함수에 인자 값을 초기값으로 가지는 변수이며  ex) useState(0) => state = 0
  // 해당 값은 직접적으로 건들지 않는 것을 원칙으로 한다. ex) value = 3 (x) 
  // useState는 가변적인 상태를 지닐 수 있게 도와준다고 했는데, value를 바꿀 수 없다면 어떻게 해야하는 걸까 .. ? 
  // setValue로 value를 바꿔주면 된다. setValue안에 인자를 넣으면 value값이 해당 인자로 바뀌게된다.
  // value를 직접적으로 3으로 할당 (X) <-> setValue(3) (O)
  // useState로 선언한 value의 값을 setValue로 바꾼다면 렌더링이 일어나게 된다.
  
  const [value, setValue] = useState(0);
  return (
    <div>
      <p>
        현재 카운터 값은 <b>{value}</b> 입니다.
      </p>
      <button onClick={() => setValue(value + 1)}>+1</button>
      <button onClick={() => setValue(value - 1)}>-1</button>
    </div>
  );
};

만약 여러개의 useState를 쓰고싶다면 그냥 여러번 사용하면 된다.
하나의 useState는 하나의 상태 값만 관리할 수 있기 때문인데, 예제를 보면 다음과 같다.

const [string,setString] = useState("");
const [array,setArray] = useState([]);

📌 useEffect

useEffect(didUpdate);

useEffect는 컴포넌트가 렌더링될 때 마다 특정 작업을 수행할 수 있도록 하는 Hook이다.
클래스형 컴포넌트에서의
componentDidUpdate와 componentDidMount API를 합친 형태로 봐도 무방하다.

🔎 useEffect 사용예시

import React, { useState,useEffect } from 'react';

const Counter = () => {
  const [value, setValue] = useState(0);
  // 클릭을 하게되면 setValue를 통해서 value가 업데이트 된다. ==> reRendering이 된다.
  
  // 해당 컴포넌트에서 useEffect가 동작하게 되는 순간은 2가지다.
  // 1) componentDidMount => 해당 컴포넌트가 처음으로 마운트 되는 순간.
  // 2) componentDidUpdate => 해당 컴포넌트가 리렌더링이되어 다시 마운트가 되는 순간.
  // 즉 처음 해당 페이지로 진입했을 때 한번 실행이 되고, 클릭할 때마다 실행이 되는 것이다.
  useEffect(() => {
    console.log('렌더링이 완료되었습니다! value : ', value);
  });
  return (
    <div>
      <p>
        현재 카운터 값은 <b>{value}</b> 입니다.
      </p>
      <button onClick={() => setValue(value + 1)}>+1</button>
      <button onClick={() => setValue(value - 1)}>-1</button>
    </div>
  );
};

🙋 근데 리액트로 작업을 하다보면,

"나는 처음 메뉴로 들어올 때만 API를 호출해서 데이터를 바인딩하고싶은데, 렌더링될 때 마다 API를 호출하는건 너무 비효율적이야."

라는 생각이 들 때가 있고, 빈번히 직면하게되는 상황이다.
이럴 땐 의존성배열에 빈 배열을 넣어주어 해결이 가능하다.


import React, { useState,useEffect } from 'react';

const Counter = () => {
  const [value, setValue] = useState(0);

  useEffect(() => {
    console.log(' 의존성배열에 빈 배열을 넣어서 componentDidMount때만 동작하는 useEffect ');
  },[]);
  return (
    <div>
      <p>
        현재 카운터 값은 <b>{value}</b> 입니다.
      </p>
      <button onClick={() => setValue(value + 1)}>+1</button>
      <button onClick={() => setValue(value - 1)}>-1</button>
    </div>
  );
};

🙋 또, 이런 상황이 올 것이다.

나는 A라는 변수가 바뀔 때만 특정 함수를 실행시키고 싶어 .. !
이럴 땐 의존성 배열에 특정 값 A만을 넣어주면 된다.


import React, { useState,useEffect } from 'react';

const Counter = () => {
  const [value, setValue] = useState(0);
  const [A,setA] = useState(0);

  useEffect(() => {
    console.log(' 의존성배열에 A를 넣어서 A가 바뀔 때만 useEffect ');
  },[A]);
  return (
    <div>
      <p>
        현재 카운터 값은 <b>{value}</b> 입니다.
      </p>
      <button onClick={() => setValue(value + 1)}>+1</button>
      <button onClick={() => setValue(value - 1)}>-1</button>

      <br/>

      <p>
        <button onClick={() => setA(value)}> useEffect 실행 </button>
      </p>

    </div>
  );
};

🙋 useEffect는 clean up 기능까지 지원한다.
보통 바닐라JS로 작업을 할 때,

window.addEventListener('click',function (){ ... })

를 통해서 이벤트를 추가해줬으면,

window.removeEventListener('click',function (){ ... })

로 이벤트를 다시 제거(클린업)하여 효율적으로 동작하게 끔 하는데, 이러한 클린업 동작을 useEffect에서 지원한다.
사용방법은 useEffect 내에서 return 문 안에 넣어주면 된다.
useEffect에서 사용하는 return은 unmount될 때 실행된다. 즉 componentWillUnmount의 역할을 수행한다.

...
useEffect(()=>{
  window.addEventListener('click',function(){...})
  return () =>{
    window.removeEventListener('click',function(){...})
  }
},[])

📌 useContext

const value = useContext(MyContext);

해당 Hook을 사용하면 함수형 컴포넌트에서 Context를 보다 쉽게 사용 할 수 있다.
일반적으로 부모컴포넌트에서 자식컴포넌트로 props를 통해 단방향으로 데이터를 전달하게되는데,
부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달할 때,
수 많은 자식들이 있다면 props를 하나 하나 일일이 넘겨주어야 하는 불상사(props drilling)가 있을 수 있다.
이럴 때 useContext를 사용하여 효율적으로 개선이 가능하다.
context를 사용하면 전역적으로 상태를 관리하기 때문에 중간 다리 역할을 하는 중간 컴포넌트들을 모두 건너뛰고 원하는 자식 컴포넌트에
데이터를 전달할 수 있다.

useContext의 사용방법을 알아보기 전에 알고있어야 하는 점부터 알아보자.
1. context의 값은 호출하는 컴포넌트를 기준으로 부모 컴포넌트들 중 가장 가까운 컴포넌트의 Provider value props에 의해 결정된다.
2. useContext를 호출한 컴포넌트는 context의 값이 바뀌면 반드시 리렌더링된다.
3. useContext를 사용하면 컴포넌트의 재활용이 어려워질 수 있기 때문에 props drilling을 피하려는 목적이라면 컴포넌트 합성을 우선적으로 고려해야 한다.

🔎 useContext 사용 예시

최상위 컴포넌트인 App.js에 Child,Age,Name이라는 컴포넌트를 추가하여 useContext를 어떻게 사용하는지 알아보도록 하자.

---- [Age.js] / [Name.js] ----
// 각각 createContext를 import하고 null 값을 담아서 export 해주는 컴포넌트이다.
import { createContext} from react
export const Age = createContext(null); // Age.js
export const Name = createContext(null); // Name.js

// App.js에서 Provider를 통해서 하위 컴포넌트로 값을 넘겨준다.
---- [App.js] ----
import { Age , Name } from "./ContextExample";
import Child from "./Child";

const App = () =>{
  return (
    <div>
      <Age.Provider value={28}>
        <Name.Provider value={"이용재"}>
          <Child/>
        </Name.Provider>
      </Age.Provider>
    </div>
  )
}

---- [Child.js] ----
// 하위 컴포넌트에서는 useContext를 import 해오고 useContext안에 컴포넌트를 넣으면 된다.
import { useContext } from 'react';
import { Age , Name } from "./ContextExample";

const Child = ()=>{
  const age = useContext(Age); // 27
  const name = useContext(Name); // 이용재
  return (
    <div>
      <p>`안녕, 나는 ${name}라고해`</p>
      <p>나이는 {age}이야.</p>
    </div>
  )
}

📌 useReducer

useReducer는 useState의 대체 함수라고 할 수 있다.
리듀서는 현재 상태(state)와 업데이트를 위해 필요한 정보를 담은 액션(action) 값을 전달받아서
새로운 상태를 반환하는 훅인데, 새로운 상태를 만들 때 반드시 불변성을 지켜주어야 한다.

🔎 useReducer 사용 예시

const [state, dispatch] = useReducer(reducer, initialArg, init);

위의 카운터 예시를 useReducer를 사용하여 다시 작성한 코드는 아래와 같다.

import {useReducer} from 'react';
const initialState = {count: 0};

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      throw new Error();
  }
}

function Counter() {
  // useReducer의 첫 번째 인자는 reducer , 두 번째 인자는 리듀서가 사용할 기본 값을 넣어준다.
  // useReducer는 state와 dispatch를 return하고 각각 현재 상태와 액션을 발생시키는 함수를 가르킨다.
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      // dispatch()안에 type을 객체 형태로 넣어서 reducer 함수를 실행시킨다.
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}

useReducer를 쓸 때 가장 큰 이점은 상태를 변경하는 로직을 컴포넌트 밖으로 꺼낼 수 있다는 점이다.

📌 useMemo

useMemo(() => fn, deps)

useMemo는 메모이제이션된 값을 반환하며 보통 최적화를 위해서 사용되는 Hook이다.
메모이제이션(Memoization)은 기존의 수행한 연산의 결과값을 어딘가에 저장해두고,
동일한 입력이 들어오면 재사용을 하는 프로그래밍 기법을 의미한다.

🔎 useMemo 사용 예시

const onChange = useMemo((e) => {
    return working(e);
},[inputValue])

...
<input value={inputValue} onChange={onChange}>

위 함수를 보면 input의 값이 바뀔 때마다 working 함수가 실행될 것을 추측해볼 수 있는데,
동일한 input에 대해서 예전의 작업한 값을 그대로 사용하고 싶을 때 해당 훅을 사용한다.
렌더링 하는 과정에서 특정 값이 바뀌었을 때만 연산을 실행하고 만약에 원하는 값이 바뀐 것이 아니라면
이전에 연산했던 결과를 다시 사용하는 방식인 것이다.

다만 주의할 점은 값을 재활용 하기 위해 메모리를 소비해서 저장하는 것이기에
불필요한 값까지 사용한다면 오히려 성능저하를 일으킬 수 있다.

📌 useCallback

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

useCallback은 useMemo와 굉장히 유사한 Hook이다.

🔎 useCallback 사용 예시

const onChange = useCallback((e) => {
    return working(e);
},[inputValue])

...
<input value={inputValue} onChange={onChange}>

useCallback 의 첫번째 파라미터에는 우리가 생성해주고 싶은 함수를 넣어주고,
두번째 파라미터에는 배열을 넣어주면 되는데 이 배열에는 어떤 값이 바뀌었을 때 함수를 새로 생성해주어야 하는지 명시해주어야 한다.
두번째 파라미터로 빈 배열을 넣어주게 된다면 useEffect와 마찬가지로 컴포넌트가 렌더링될 때 단 한번만 실행이 된다.
위에 onChange함수처럼 inputValue를 넣게되면 inputValue가 바뀔 때만 실행되게 되는 것이다.

useMemo와 useCallback의 차이점

useMemo는 메모이제이션한 '값'을 반환하고,
useCallback은 메모이제이션한 '함수'를 반환한다.
useMemo는 함수를 실행시켜버리는데, useCallback은 함수를 반환하는 것이다.
즉 useCallback(fn, deps)은 useMemo(() => fn, deps)와 동일하다.

useCallback(() => {
  console.log('hello world!');
}, [])

useMemo(() => {
  const fn = () => {
    console.log('hello world!');
  };
  return fn;
}, [])

위 두 함수는 동일한 역할을 하게되는 것이다.
useCallback 은 결국 useMemo 에서 함수를 반환하는 상황에서 더 편하게 사용 할 수 있는 Hook 이므로
숫자,문자열,객체처럼 값을 재사용하기 위해서는 useMemo 를 사용하고,
함수를 재사용 하기 위해서는 useCallback 을 사용하도록 하자.

함수 재활용 => useCallback
값 재활용 => useMemo

📌 useRef

const refContainer = useRef(initialValue);

useRef Hook은 함수형 컴포넌트에서 ref를 쉽게 사용 할 수 있게 도와주는 Hook이다.
일반적인 유스케이스는 아래 소스코드처럼 자식 요소에게 명령적으로 접근할 때이다.

🔎 useRef 사용예시

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

useRef를 사용하여 ref를 설정하면, useRef를 통해 만든 객체 안의 current값이 실제 엘리먼트를 가르키게 된다.
또한 useRef를 사용하여 로컬 변수로 활용할 수 있는데,
useRef에 값을 할당하고, 해당 값을 바꾼다고해도 컴포넌트가 렌더링되지 않기 때문에
만약 렌더링이 되더라도 이전의 값을 가지고있고 싶을 때 활용할 수 있다.

참고한 사이트
: https://velog.io/@velopert/react-hooks#4-usereducer
: https://ko.reactjs.org/docs/hooks-reference.html#usestate

profile
중요한건 꺾이지 않는 마음

0개의 댓글