-
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에 계속 찍힌다.
마운트 될 때만 실행하기
- 마운트 = 컴포넌트가 맨 처음 렌더링될 때만 실행하고, 업데이트 될 때는 실행하지 않는 것
- useEffect함수의 두 번째 파라미터에 비어 있는 배열을 넣으면 됨
useEffect(() => { console.log('마운트될 때만 실행됩니다.'); }, []);
업데이트될 때만 실행하기
- 클래스형 컴포넌트
componentDidUpdate(prevProps, prevState) { if(prevProps.value !== this.props.value) { doSomething(); } }
- props 안에 있는 value 값이 바뀔 때만 작업을 수행하도록 함
- 함수형 컴포넌트
useEffect(() => { console.log(name); }, [name]);
- 위와 같이, 두 번째 파라미터의 배열 안에 업데이트를 검사하고 싶은 값을 넣어주면 됨
- 클래스형 컴포넌트
뒷정리하기
- useEffect는 렌더링되고 난 직후마다 실행되고, 두 번째 파라미터 배열의 값에 따라 조건이 달라진다.
- 컴포넌트가 unMount되기 전이나 update되기 직전에 특정 작업을 수행하고 싶다면, useEffect에서 뒷정리(cleanup) 함수를 반환해주어야 한다.
useEffect(() => { console.log('effect'); console.log(name); return () => { console.log('cleanup'); console.log(name); }; });
- 최초 렌더링 될 때, Effect가 적용되고 다음 useEffect가 실행되기 전에 cleanup 함수를 실행
- 더 명확하게 확인하기 위해, 버튼을 누르면 input과 이름 보이고 사라지게 설정해보자
<div> <button onClick={() => { setVisible(!visible); }}> {visible ? '숨기기' : '보이기'} </button> <hr /> {visible && <Info />} </div>
- 최초 보이기 버튼을 누르면 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를 활용하면 다른 곳에서 재사용이 가능
- 다른 개발자가 만든 Hooks
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