ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • React Hooks 알아보기
    Java Script/React 2020. 11. 4. 00:20
    반응형

    Hooks

    • React v16.8에 새로 도입된 기능

    • 함수형 컴포넌트에서도 상태 관리를 할 수 있는 useState, 렌더링 직후 작업을 설정하는 useEffect 등의 기능을 제공

    • 기존의 함수형 컴포넌트에서 할 수 없었던 다양한 작업을 할 수 있게 도움

    • 리액트 매뉴얼에 따르면, 기존의 클래스형 컴포넌트는 앞으로도 계속해서 지원될 예정

    • 하지만, 함수형 컴포넌트와 Hooks 사용 권장 ~> 첫번째 옵션으로 염두하기(미션 요구사항과 부합)

    • 종류

      • useState, useEffect, useReducer, useMemo, useCallback, useRef, customHook
      • useState : 컴포넌트에서 가변적인 상태를 핸들링하도록 돕는 Hook
      • useReducer : 다양한 컴포넌트 상황에 따라 다양한 상태를 다른 값으로 업데이트할 때 사용
      • useEffect : 컴포넌트가 렌더링될 때마다 특정 작업을 수행하도록 설정하는 Hook
      • useMemo : 숫자, 문자열, 객체 등 일반 값을 재사용하기 위한 Hook
      • useCallback : 함수를 재사용하기 위한 Hook
      • useRef : 특정 DOM에 접근 or 렌더링과 관련 없는 변수를 이용할 때 사용하는 Hook(Ref를 쉽게 사용)
    • useState

      • 가장 기본적인 Hook

      • 함수형 컴포넌트에서도 가변적인 상태를 지니도록 도움

        • 함수형 컴포넌트로 상태를 관리한다면, Hook을 이용
      •      import React, { useState } from "react";
        
           const Counter = () => {
               const [value, setValue] = useState(0);
        
               const increaseNum = () => {
                   setValue(value+1);
               };
               const decreaseNum = () => {
                   setValue(value -1 <0 ? value : value-1);
               };
               return (
                   <div>
                       <h1>Increasing : {value}</h1>
                       <button onClick={() => increaseNum()}>
                       +1 시키기
                       </button>
                       <button onClick={() => decreaseNum()}>
                       -1 시키기
                       </button>
                   </div>
               );
        
           } 
           export default Counter;
        • useState(0) => value의 기본 값을 0으로 설정하겠다는 의미
        • 이 함수가 호출되면 배열을 반환 ~> 첫 번째 원소 : 상태 값, 두 번째 원소 : 상태 설정 함수
      • useState 함수는 하나의 상태 값만 관리함 ~> 컴포넌트가 여러 상태를 관리 한다면 ~> 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>
                          <div>
                              <input value={name} onChange={onChangeName} />
                              <input value={nickName} onChange={onChangeNickName} />
                          </div>
                          <div>
                              <b>이름:</b> {name}
                          </div>
                          <div>
                              <b>닉네임:</b> {nickName}
                          </div>
                      </div>
                  );
              };
          
              export default Info;
    • useEffect

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

      • useEffect => 클래스 컴포넌트의 componentDidMount + componentDidUpdate (moust와 update)

      •       import React, { useEffect, useState } from 'react';
        
            const Info = () => {
                const [name, setName] = useState('');
                const [nickName, setNickName] = useState('');
        
                useEffect(() => {
                    console.log('렌더링 완료');
                    console.log({name, nickName});
                });
        
                const onChangeName = (e) => {
                    setName(e.target.value);
                }
                const onChangeNickName = (e) => {
                    setNickName(e.target.value);
                }
        
                return (
                    <div>
                        <div>
                            <input value={name} onChange={onChangeName} />
                            <input value={nickName} onChange={onChangeNickName} />
                        </div>
                        <div>
                            <b>이름:</b> {name}
                        </div>
                        <div>
                            <b>닉네임:</b> {nickName}
                        </div>
                    </div>
                );
            };
        
            export default Info;
        • input에 입력이 일어날 때마다 name 값과 nickName값이 바뀌기 때문에, 계속 렌더링 작업이 일어남 ~> console에 계속 찍힌다.
        • image
      • 마운트 될 때만 실행하기

        • 마운트 = 컴포넌트가 맨 처음 렌더링될 때만 실행하고, 업데이트 될 때는 실행하지 않는 것
        • useEffect함수의 두 번째 파라미터에 비어 있는 배열을 넣으면 됨
        •       useEffect(() => {
                  console.log('마운트될 때만 실행됩니다.');
              }, []);
        • image
      • 업데이트될 때만 실행하기

        • 클래스형 컴포넌트
          •       componentDidUpdate(prevProps, prevState) {
                    if(prevProps.value !== this.props.value) {
                        doSomething();
                    }
                }
          • props 안에 있는 value 값이 바뀔 때만 작업을 수행하도록 함
        • 함수형 컴포넌트
          •       useEffect(() => {
                    console.log(name);
                }, [name]);
          • 위와 같이, 두 번째 파라미터의 배열 안에 업데이트를 검사하고 싶은 값을 넣어주면 됨
          • image
      • 뒷정리하기

        • useEffect는 렌더링되고 난 직후마다 실행되고, 두 번째 파라미터 배열의 값에 따라 조건이 달라진다.
        • 컴포넌트가 unMount되기 전이나 update되기 직전에 특정 작업을 수행하고 싶다면, useEffect에서 뒷정리(cleanup) 함수를 반환해주어야 한다.
        •       useEffect(() => {
                  console.log('effect');
                  console.log(name);
                  return () => {
                    console.log('cleanup');
                    console.log(name);
                  };
              });
        • image
        • 최초 렌더링 될 때, Effect가 적용되고 다음 useEffect가 실행되기 전에 cleanup 함수를 실행
        • 더 명확하게 확인하기 위해, 버튼을 누르면 input과 이름 보이고 사라지게 설정해보자
        •       <div>
                  <button onClick={() => {
                          setVisible(!visible);
                      }}>
                      {visible ? '숨기기' : '보이기'}
                  </button>
                  <hr />
                  {visible && <Info />}
              </div>
        • image
        • 최초 보이기 버튼을 누르면 useEffect 실행 ~> effect 호출, 숨기기 버튼 ~> cleanup 실행, 다시 보이기 버튼 누르면 useEffect 실행
      • 언마운트될 때만 뒷정리 함수를 호출해보자

        • 언마운트될 때만 cleanup 함수를 호출하려면, useEffect 함수의 두 번째 파라미터에 빈 배열 전달
        • 마운트 될 때와 파라미터가 같음
        •       useEffect(() => {
                  console.log('effect');
                  console.log(name);
                  return () => {
                      console.log('cleanup');
                      console.log(name);
                  };
              }, []);
    • useReducer

      • useState보다 더 다양한 컴포넌트 상황에 따라 다양한 상태를 다른 값으로 업데이트할 때 사용

      • 리듀서 ~~> 현재 상태, 업데이트에 필요한 정보를 담은 액션(action) 값을 전달받아 새로운 상태 반환

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

      •       import React, { useReducer } from "react";
        
            const reducer = (state, action) => {
                switch(action.type) {
                    case 'INCREASEMENT':
                        return { value:state.value+1 };
                    case 'DECREASEMENT':
                        return { value:state.value-1 };
                    default:
                        return state; // 기존 상태
                }
            }
        
            const Counter = () => {
                const [state, dispatch] = useReducer(reducer, { value:0 });
        
                return (
                    <div>
                        <h1>Increasing : {state.value}</h1>
                        <button onClick={() => dispatch({type: 'INCREASEMENT'})}>
                        +1 시키기
                        </button>
                        <button onClick={() => dispatch({type: 'DECREASEMENT'})}>
                        -1 시키기
                        </button>
                    </div>
                );
            } 
        
            export default Counter;
      • useReducer(리듀서 함수, 리듀서 기본 값)으로 사용

      • 이 함수를 사용하면 state 값과 dispatch 함수를 받아온다.

      • state는 현재 상태, dispatch는 액션을 발생시키는 함수

      • 장점

        • 컴포넌트 업데이트 로직을 컴포난트 밖으로 뺄 수 있음
      • 인풋 상태 관리하기

        • 이전 useState를 여러번 사용했던 예제에 useReducer를 적용해보자.

        •       import React, { useReducer } from 'react';
          
              const 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>
                          <div>
                              <input name ="name" value={name} onChange={onChange} />
                              <input name ="nickName" value={nickName} onChange={onChange} />
                          </div>
                          <div>
                              <b>이름:</b> {name}
                          </div>
                          <div>
                              <b>닉네임:</b> {nickName}
                          </div>
                      </div>
                  );
              };
          
              export default Info;
        • 관리해야하는 상태가 많아질 때, 훨씬 효율적인 코드라고 느낌

    • useMemo

      • 함수형 컴포넌트 내부에서 발생하는 연산 최적화에 사용

      •      import React, { useState, useMemo } from 'react';
        
           const getAverage = numbers => {
             console.log('평균값 계산 중..');
             if (numbers.length === 0) return 0;
             const sum = numbers.reduce((a, b) => a + b);
             return sum / numbers.length;
           };
        
           const Average = () => {
             const [list, setList] = useState([]);
             const [number, setNumber] = useState('');
        
             const onChange = e => {
               setNumber(e.target.value);
             };
             const onInsert = () => {
               const nextList = list.concat(parseInt(number));
               setList(nextList);
               setNumber('');
             };
        
             const avg = useMemo(() => getAverage(list), [list]);
        
             return (
               <div>
                 <input value={number} onChange={onChange} />
                 <button onClick={onInsert}>등록</button>
                 <ul>
                   {list.map((value, index) => (
                     <li key={index}>{value}</li>
                   ))}
                 </ul>
                 <div>
                   <b>평균값:</b> {avg}
                 </div>
               </div>
             );
           };
        
           export default Average;
        • 평균값에서 getAverage()를 계속 호출하면, 렌더링 할때마다 계산이 새롭게돼서 시간이 낭비됨 (값이 많을수록, 그리고 input에 무언가를 계속 입력할수록)
        • useMemo를 이용해서 렌더링하는 과정에서 특정 값이 바뀌었을 때만 연산을 실행하도록 설정
        • 의존성이 변경되었을 때만(배열로 전달한 값이 변경되었을 때만) 메모이제이션된 값만 다시 계산해준다.(반환한다)
        •     const colorMemo = useMemo(() => getColor(color), [color]);
            const movieGenreMemo = useMemo(() => getMovieGenre(movie), [movie]) ;
        • 이 경우는, color값이 변경되면 getColor 함수만, movie값이 변경되면 getMovie함수만 호출된다.
    • useCallback

      • useMemo와 비슷함

      • 렌더링 성능을 최적화 해야하는 상황에서 사용 ~> 이벤트 핸들러 함수를 필요한 상황에서만 생성

      • 컴포넌트의 렌더링이 자주 발생하거나, 렌더링할 컴포넌트 개수가 많아지면 최적화

      •       import React, { useState, useMemo, useCallback } from 'react';
        
            const getAverage = numbers => {
              console.log('평균값 계산 중..');
              if (numbers.length === 0) return 0;
              const sum = numbers.reduce((a, b) => a + b);
              return sum / numbers.length;
            };
        
            const Average = () => {
              const [list, setList] = useState([]);
              const [number, setNumber] = useState('');
        
              const onChange = useCallback(e => {
                setNumber(e.target.value);
              },[]); // 컴포넌트가 처음 렌더링될 때만 생성
        
              const onInsert = useCallback(() => {
                const nextList = list.concat(parseInt(number));
                setList(nextList);
                setNumber('');
              }, [number, list]); // number of list가 바뀔 때만 생성
        
              const avg = useMemo(() => getAverage(list), [list]);
        
              return (
                <div>
                  <input value={number} onChange={onChange} />
                  <button onClick={onInsert}>등록</button>
                  <ul>
                    {list.map((value, index) => (
                      <li key={index}>{value}</li>
                    ))}
                  </ul>
                  <div>
                    <b>평균값:</b> {avg}
                  </div>
                </div>
              );
            };
        
            export default Average;
        • 렌더링이 될 때마다 함수를 생성하는 것 (재선언됨) 은 비효율적이기 때문에, 마운트 될 때만 선언하고 재사용하기 위해 등장
        • useCallback의 첫 번째 파라미터 ~> 생성하고 싶은 함수, 두 번째 파라미터 ~> 배열
        • 배열에 어떤 값이 바뀌었을 때 함수를 생성해야 하는지 명시 (위에서 number, list, 첫 렌더링)
        • 의존성이 변경될 때(두 번째 파라메터인 배열 값이 변경될 때) 메모이제이션된 콜백 함수가 재선언된다.
        • shouldComponentUpdate를 사용하여) 참조의 동일성에 의존적인 최적화된 자식 컴포넌트에 콜백을 전달할 때 유용
        • This is useful when passing callbacks to optimized child components that rely on reference equality to prevent unnecessary renders (e.g. shouldComponentUpdate).
        • useCallback은 useMemo로 함수를 반환하는 상황에서 더 편리하게 이용하는 Hook
        • 숫자, 문자열, 객체 등 일반 값을 재사용 ~> useMemo, 함수 재사용 ~> useCallback
    • useRef

      • 함수형 컴포넌트에서 ref를 쉽게 사용하도록 도움

      • 위에서 작성한 Average 컴포넌트에서 등록 버튼을 눌렀을 때, 포커스가 인풋으로 넘어가게 작성

      •       import React, { useState, useMemo, useCallback, useRef } from 'react';
        
            const getAverage = numbers => {
              console.log('평균값 계산 중..');
              if (numbers.length === 0) return 0;
              const sum = numbers.reduce((a, b) => a + b);
              return sum / numbers.length;
            };
        
            const Average = () => {
              const [list, setList] = useState([]);
              const [number, setNumber] = useState('');
              const inputEl = useRef(null);    // useRef 추가
        
              const onChange = useCallback(e => {
                setNumber(e.target.value);
              },[]); // 컴포넌트가 처음 렌더링될 때만 생성
        
              const onInsert = useCallback(() => {
                const nextList = list.concat(parseInt(number));
                setList(nextList);
                setNumber('');
                inputEl.current.focus();    // input태그를 가리키도록
              }, [number, list]); // number of list가 바뀔 때만 생성
        
              const avg = useMemo(() => getAverage(list), [list]);
        
              return (
                <div>
                  <input value={number} onChange={onChange} ref={inputEl}/>
                  <button onClick={onInsert}>등록</button>
                  <ul>
                    {list.map((value, index) => (
                      <li key={index}>{value}</li>
                    ))}
                  </ul>
                  <div>
                    <b>평균값:</b> {avg}
                  </div>
                </div>
              );
            };
        
            export default Average;
      • 로컬 변수 사용하기

        • 컴포넌트 로컬 변수 사용 시, useRef를 활용

        • 로컬 변수 ~> 렌더링과 상관 없이 바뀔 수 있는 값

        •       import React, { useRef } from 'react';
          
              const RefSample = () => {
                const id = useRef(1);
                const setId = (n) => {
                  id.current = n;
                }
                const printId = () => {
                  console.log(id.current);
                }
                return (
                  <div>
                    refsample
                  </div>
                );
              };
          
              export default RefSample;
          • ref 안의 값이 바뀌어도 컴포넌트가 렌더링되지 않음 (렌더링과 관계 없는 변수)
    • 커스텀 Hooks 만들기

      • 여러 컴포넌트에서 비슷한 기능을 공유할 때, 이를 custom Hook으로 작성해서 재사용하는 목적

      • 앞서 작성했던 예시에서 useReducer로 작성했던 input 로직을 useInputs라는 Hook으로 분리하기

      •       // useInputs.js
            import { useReducer } from "react";
        
            const reducer = (state, action) => {
                return {
                    ...state,
                    [action.name]: action.value
                }
            }
        
            const useInputs = initialForm => {
                const [state, dispatch] = useReducer(reducer, initialForm);
                const onChange = e => {
                    dispatch(e.target);
                }
                return [state, onChange];
            };
        
            export default useInputs;
      •       import React from 'react';
            import useInputs from './useInputs';
        
            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>
                            <b>이름:</b> {name}
                        </div>
                        <div>
                            <b>닉네임:</b> {nickName}
                        </div>
                    </div>
                );
            };
        
            export default Info;
        • input 태그 안에서 state를 관리할 때, useInputs를 활용하면 다른 곳에서 재사용이 가능

    Reference

    리액트를 다루는 기술

    반응형

    'Java Script > React' 카테고리의 다른 글

    React) styled-component  (0) 2020.11.04
    React) useMemo & useCallback  (0) 2020.11.04
    React 컴포넌트 알아보기  (0) 2020.11.04
    JSX란 무엇인가  (0) 2020.11.03

    댓글

Designed by Tistory.