[React] Hooks

vanLan·2022년 11월 22일
0

React

목록 보기
8/11
post-thumbnail


🥇 Hook

  • Hook은 함수 컴포넌트에서도 상태 관리를 할 수 있는 useState, 렌더링 직후 작업을 설정하는 useEffect 등의 기능들을 제공하여 기존의 함수 컴포넌트에서 할 수 없었던 다양한 작업을 할 수 있게 도와준다.


🥈 useState

  • useState는 가장 기본적인 Hook이며, 함수 컴포넌트에서도 가변적인 상태를 지닐 수 있게 도와준다.

  • 함수 컴포넌트에서 상태를 관리해야 한다면 이 Hook을 사용하면 된다.

    🥉 useState 사용법

    • useState를 사용한 간단한 Counter 컴포넌트

      
      // Counter.js
      
      import React, { useState } from 'react';
      
      const Counter = () => {
        const [state, setState] = useState(0);
      
        return (
          <div>
            <p>Count: <p>{state}</p></p>
            <button onClick={() => setState(state+1)}>+1</button>
            <button onClick={() => setState(state-1)}>-1</button>
          </div>
        );
      }
      
      export default Counter;
    • useState를 여러번 사용한 간단한 Info 컴포넌트

      
      // Info.js
      
      // useState를 여러번 사용하기(컴포넌트에서 관리할 상태가 여러 개라면...)
      import React, { useState } from 'react';
      
      const Info = () => {
        const [name, setName] = useState('');
        const [nickname, setNickname] = useState('');
      
        const onChangeName = (e) => {
          setName(e.target.value);
        }
      
        const onChangeNickname = (e) => {
          setNickname(e.target.value);
        }
      
        return (
          <div>
            <input value={name} onChange={onChangeName} />
            <input value={nickname} onChange={onChangeNickname} />
            <p>{name}({nickname})</p>
          </div>
        );
      }
      
      export default Info;

🥈 useEffect

  • useEffect는 컴포넌트가 렌더링 될 때마다 특정 작업을 수행하도록 설정할 수 있는 Hook이다.

  • 클래스형 컴포넌트의 componentDidMount와 ComponentDidUpdate를 합친 형태로 보아도 무방하다.

    🥉 useEffect 사용법

    • 컴포넌트가 렌더링 될때 실행.

      
      useEffect(() => {
        console.log('컴포넌트가 렌더링되면 출력됨.');
      });
    • 마운트될 때만 실행

      
      // useEffect 함수의 두번째 파라미터로 비어있는 배열을 입력.
      useEffect(() => {
        console.log('마운트(처음 렌더링)될 때만 출력됨.');
      }, []);
    • 특정 값이 업데이트될 때만 실행

      
      // useEffect 함수의 두번째 파라미터로 검사하고 싶은 값을 입력.
      useEffect(() => {
        console.log(`${name}이 업데이트될 때만 출력됨.`);
      }, [name]);
    • 컴포넌트가 언마운트될 때 실행

      
      // useEffect 함수에서 뒷정리(cleanup) 함수를 return.
      useEffect(() => {
        
        return () => {
          console.log('컴포넌트가 언마운트 되면 출력됨.');
        };
      }, []);

🥈 useReducer

  • useReducer는 useState 보다 더 다양한 컴포넌트 상황에 따라 다양한 상태를 다른 값으로 업데이트 해주고 싶을 때 사용하는 Hook.

  • 리듀서는 현재 상태, 액션값(업데이트를 위해 필요한 정보)을 전달받아 새로운 상태를 반환하는 함수이다.

    🥉 useReducer 사용법

    • 리듀서 함수에서 새로운 상태를 만들 때는 반드시 불변성을 지켜야 한다.

      function reducer(state, action) {
        return { ... }; // 불변성을 지키면서 업데이트한 새로운 상태를 반환.
      }
    • useReducer의 첫번째 파라미터에는 리듀서 함수를 넣고, 두번째 파라미터에는 해당 리듀서의 기본값을 넣어 줍니다.

      
      const [state, dispatch] = useReducer(reducer, { 기본값 });
      // state: 현재 가리키고 있는 상태.
      // dispatch: 액션을 발생시키는 함수(함수 안에 파라미터로 액션값을 넣어 주면 리듀서 함수가 호출됨.)

    🥉 useReducer의 장점

    • 컴포넌트 업데이트 로직을 컴포넌트 외부로 빼낼 수 있다는 것이 가장 큰 장점이다.
      • useReducer를 이용한 간단한 Info 컴포넌트

        
        // Info.js
        
        import React, { useReducer } from 'react';
        
        function reducer(state, action) {
          return {
            ...state,
            [action.name]: action.value
          };
        }
        
        const Info = () => {
          const [state, dispatch] = useReducer(reducer, {
            name: '',
            nickname: ''
          });
          const { name, nickname } = state;
          const onChange = (e) => {
            dispatch(e.target);
          }
          
          return (
            <div>
              <input name='name' value={name} onChange={onChange} />
              <input name='nickname' value={nickname} onChange={onChange} />
              <p>{name}({nickname})</p>
            </div>
          );
        }
        
        export default Info;

🥈 useMemo

  • useMemo를 사용하면 함수 컴포넌트 내부에서 발생하는 연산을 최적화 할 수 있다.

  • 렌더링 하는 과정에서 특정 값이 바뀌었을 때만 연산을 실행하고, 원하는 값이 바뀌지 않았다면 이전에 연산했던 결과를 다시 사용하는 방식.

    🥉 useMemo 사용법

    • useMemo를 이용한 간단한 Average 컴포넌트

      
      // Average.js
      
      import React, { useState, useMemo } from 'react';
      
      const getAverage = (numbers) => {
        console.log('평균값 계산중...');
        if(numbers.length === 0) return 0;
        const sum = numbers.reduce((arr, cur) => arr + cur);
        
        return sum / numbers.length;
      }
      
      const Average = () => {
        const [list, setList] = useState([]);
        const [number, setNumber] = useState('');
        
        const onChange = (e) => {
          setNumber(e.target.value);
        }
      
        const onClick = () => {
          setList([...list, parseInt(number)]);
          setNumber('');
        }
      
        const avg = useMemo(() => getAverage(list), [list]);
        // list 배열의 내용이 변경 될 때만 getAverage 함수가 호출된다.
      
        return (
          <div>
            <input value={number} onChange={onChange} />
            <button onClick={onClick}>등록</button>
            <ul>
              {list.map((val, idx) => (
                <li key={idx}>{val}</li>
              ))}
            </ul>
            <div>
              <b>평균값:</b> {avg}
            </div>
          </div>
         );
      }  
      
      export default Average;

🥈 useCallback

  • useCallback은 useMemo와 유사한 함수이며, 주로 렌더링 성능을 최적화해야 하는 상황에서 사용한다.

  • 이 Hook을 사용하면 만들어 놨던 함수를 재사용할 수 있다.

    🥉 useCallback 사용법

    • useCallback의 첫번째 파라미터에는 생성하고 싶은 함수를 넣고, 두번째 파라미터에는 배열을 넣는다. (배열에는 어떤 값이 바뀌었을 때 함수를 새로 생성해야 하는지 명시)

    • useMemo를 사용한 Average 컴포넌트를 useCallback을 이용하여 최적화

      
      // Average.js
      
      import React, { useState, useMemo, useCallback } from 'react';
      
      const getAverage = (numbers) => {
        console.log('평균값 계산중...');
        if(numbers.length === 0) return 0;
        const sum = numbers.reduce((arr, cur) => arr + cur);
        
        return sum / numbers.length;
      }
      
      const Average = () => {
        const [list, setList] = useState([]);
        const [number, setNumber] = useState('');
      
        const onChange = useCallback((e) => {
          setNumber(e.target.value);
        }, []); // 컴포넌트가 마운트 되었을 때만 함수 생성.
        
        const onClick = useCallback(() => {
          setList([...list, parseInt(number)]);
          setNumber('');
        }, [number, list]); // number 혹은 list가 변경 되었을때만 함수 생성.
        
        const avg = useMemo(() => getAverage(list), [list]);
        // list 배열의 내용이 변경 될 때만 getAverage 함수가 호출된다.
        
        return (
          <div>
            <input value={number} onChange={onChange} />
            <button onClick={onClick}>등록</button>
            <ul>
              {list.map((val, idx) => (
                <li key={idx}>{val}</li>
              ))}
            </ul>
            <div>
              <b>평균값: </b> {avg}
            </div>
          </div>
        );
      }
      
      export default Average;

🥈 useRef

  • useRef Hook은 함수 컴포넌트에서 ref를 쉽게 사용할 수있도록 도와준다.

  • useRef를 사용하여 ref를 설정하면 useRef를 통해 만든 객체의 current 값이 실제 엘리먼트를 가리킨다.

    🥉 useRef 사용법

    • useRef를 이용한 Average 컴포넌트(버튼 클릭시 input으로 focus이동)

      
      import React, { useState, useMemo, useCallback, useRef } from 'react';
      
      const getAverage = (numbers) => {
        console.log('평균값 계산중...');
        if(numbers.length === 0) return 0;
        const sum = numbers.reduce((arr, cur) => arr + cur);
      
        return sum / numbers.length;
      }
      
      const Average = () => {
        const [list, setList] = useState([]);
        const [number, setNumber] = useState('');
        const inputEl = useRef(null);
      
        const onChange = useCallback((e) => {
          setNumber(e.target.value);
        }, []);
        // 컴포넌트가 마운트 되었을 때만 함수 생성.
      
        const onClick = useCallback(() => {
          setList([...list, parseInt(number)]);
          setNumber('');
          inputEl.current.focus();
          // useRef를 통해 만든 객체 안의 current 값이 실제 엘리먼트를 가리킨다.
        }, [number, list]);
        // number 혹은 list가 변경 되었을때만 함수 생성.
      
        const avg = useMemo(() => getAverage(list), [list]);
        // list 배열의 내용이 변경 될 때만 getAverage 함수가 호출된다.
      
        return (
          <div>
            <input value={number} onChange={onChange} ref={inputEl}/> 
            <button onClick={onClick}>등록</button>
              <ul>
                {list.map((val, idx) => (
                  <li key={idx}>{val}</li>
                ))}
              </ul>
              <div>
                <b>평균값: </b> {avg}
              </div>
          </div>
        );
      }
      
      export default Average;

    🥉 로컬 변수 형태로 useRef 이용

    • ref 안의 값이 바뀌어도 컴포넌트가 렌더링 되지 않는다. 렌더링과 관련되지 않은 값을 관리할 때만 이러한 방식의 코드를 작성).

      
      import React, { useRef } from 'react';
      
      const RefExample = () => {
        const id = useRef(1);
        const setId = (n) => {
          id.current = n;
        };
        const printId = () => {
          console.log(id.current);
        };
        return (
          <div>
            refExample
          </div>
        );
      };

🥈 커스텀 Hooks 만들기

  • 여러 컴포넌트에서 비슷한 기능을 공유할 경우, 이를 나만의 Hook으로 작성하여 로직을 재사용 할 수 있다.

    🥉 커스텀 Hook 사용 예

    • 커스텀 Hook을 사용한 간단한 Info 컴포넌트

      
      // useInput.js
      import { useReducer } from "react";
      
      function reducer(state, action) {
        return {
          ...state,
          [action.name]: action.value
        };
      }
      
      export default function useInputs(initialForm) {
        const [state, dispatch] = useReducer(reducer, initialForm);
        
        const onChange = (e) => {
          dispatch(e.target);
        }
        
        return [state, onChange];
      }
      
      
      // Info.js
      import useInputs from './useInputs.js';
      
      const Info = () => {
        const [state, onChange] = useInputs({
          name: '',
          nickname: ''
        });
        const { name, nickname } = state;
      
        return (
          <div>
            <div>
              <input name='name' value={name} onChange={onChange} />
              <input name='nickname' value={nickname} onChange={onChange} />
            </div>
            <div>
              <p>{name}({nickname})</p>
            </div>
          </div>
        );
      }
      
      export default Info;

🥈 다른 Hooks

  • 커스텀 Hooks를 만들어서 사용했던 것 처럼, 다른 개발자가 만든 Hooks도 라이브러리로 설치하여 사용할수 있다.


📀 정리

  • 리액트에서 Hooks 패턴을 사용하면 클래스형 컴포넌트를 작성하지 않고도 대부분의 기능을 구현할 수 있다.
  • 리액트 메뉴얼에 따르면, 기존 클래스형 컴포넌트는 앞으로도 계속 지원될 예정이기 때문에 기존 구현된 클래스형 컴포넌트를 굳이 함수형 컴포넌트와 Hooks를 사용하는 형태로 전환할 필요는 없다.
  • 다만, 메뉴얼에서는 새로 작성하는 컴포넌트의 경우 함수 컴포넌트와 Hooks를 사용할 것을 권장하고 있다.
  • 앞으로 프로젝트를 개발할 때는 함수 컴포넌트의 사용을 첫 옵션으로 두고, 꼭 필요한 경우에만 클래스형 컴포넌트를 구현하여야 한다.


[참고] 김민준(Velopert)님'리액트를 다루는 기술'을 공부하며 정리한 내용임.

profile
프론트엔드 개발자를 꿈꾸는 이

0개의 댓글