[리액트를 다루는 기술] 8장 Hooks

fzerome·2022년 11월 28일
0

Hooks

  • 클래스형 컴포넌트를 작성하지 않고도 대부분의 기능을 구현할 수 있다.
  • 함수 컴포넌트와 Hooks 사용을 권장한다.

프로젝트 설치

yarn create react-app hooks-tutorial

useState

함수 컴포넌트에서 상태 관리

import React, {useState} from 'react'

const [value, setValue] = useState(0)

useEffect

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

useEffect(() => {
	console.log({name, nickname})
})

마운트될 때만 실행하는 법

useEffect(() => {
	console.log('마운트될때 실행')
}, [])

특정 값이 업드이트될 때만 실행하고 싶을 때

배열 안에 useState를 통해 관리하고 있는 상태를 넣어도 되고, props로 전달받은 값을 넣어도 된다

useEffect(() => {
	console.log(name)
}, [name])

cleanup

컴포넌트가 언마운트되기 전이나 업데이트되기 직전에 어떠한 작업을 수행하고 싶다면 cleanup 함수를 반환해줘야 한다.

useEffect(() => {
	console.log(name)
    return () => {
    	console.log('cleanup')
    }
}, [name])

오직 언마운트될 때만 cleanup함수를 호출하고 싶을 때

빈 배열을 넣는다.

useEffect(() => {
	console.log(name)
    return () => {
    	console.log('cleanup')
    }
}, [])

useReducer

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

  • reducer: 현재 상태, 업데이트를 위해 필요한 정보를 담은 액션값을 받아 새로운 상태를 반환하는 함수
  • 반드시 불변성을 지켜야 한다
function reducer(state, action) {
	return { ... }
}

// 액션값
{ type: 'INCREMENT', ... }
  • useReducer(reducer 함수, 해당 reducer의 기본값)
  • 가장 큰 장점: 컴포넌트 업데이트 로직을 컴포넌트 바깥으로 뺄 수 있다는 것

input 상태 관리하기

e.target 값을 액션값으로 사용하면 input 개수가 많아져도 코드를 깔끔하게 유지할 수 있다.

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

    // e.target 값 자체를 액션 값으로 사용
    const onChange = e => {
        dispatch(e.target)
    }

    return (
        <div>
            <div>
                <input name="name" value={name} onChange={onChange}></input>
                <input name="nickName" value={nickname} onChange={onChange}></input>
            </div>
            <div>
                <div>이름: {name}</div>
                <div>닉네임: {nickname}</div>
            </div>
        </div>
    )
}

export default Info

useMemo

  • 함수 컴포넌트 내부에서 발생하는 연산을 최적화할 수 있다.
  • 렌더링하는 과정에서 특정 값이 바뀌었을 때만 연산을 실행하고, 원하는 값이 바뀌지 않았다면 이전에 연산했던 결과를 다시 사용하는 방식이다.
import {useState, useMemo} from 'react'

const getAverage = numbers => {
    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('')
    }

    // list 배열의 내용이 바뀔때만 getAverage 함수 호출된다
    const avg = useMemo(() => getAverage(list), [list])

    return (
        <div>
            <input value={number} onChange={onChange}></input>
            <button onClick={onInsert}>등록</button>
            <ul>
                {list.map((value, index) => (
                    <li key={index}>{value}</li>
                ))}
            </ul>
            <div>평균값 {avg}</div>
        </div>
    )
}

export default Average

useCallback

  • 만들어놨던 함수를 재사용할 수 있다. (useMemo와 비슷하다)
  • 리렌더링 시 새로 만들어진 함수를 사용하게 되는데, 컴포넌트의 렌더링지 자주 발생하거나, 렌더링해야할 컴포넌트의 개수가 많아지는 경우
  • 렌더링 성능 최적화 시 사용한다.
  • useCallback(생성하고 싶은 함수, [해당 값이 바뀌었을 때 함수를 새로 생성])
import {useState, useMemo, useCallback} from 'react'

const getAverage = numbers => {
    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 또는 list가 바뀌었을 때만 함수 생성

    // list 배열의 내용이 바뀔때만 getAverage 함수 호출된다
    const avg = useMemo(() => getAverage(list), [list])

    return (
        <div>
            <input value={number} onChange={onChange}></input>
            <button onClick={onInsert}>등록</button>
            <ul>
                {list.map((value, index) => (
                    <li key={index}>{value}</li>
                ))}
            </ul>
            <div>평균값 {avg}</div>
        </div>
    )
}

export default Average

useRef

ref를 설정하면 객체 안의 current 값이 실제 엘리먼트를 가리킨다.

import {useState, useMemo, useCallback, useRef} from 'react'

const getAverage = numbers => {
    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)

    const onChange = useCallback(e => {
        setNumber(e.target.value)
    }, []) // 컴포넌트가 처음 렌더링될 때만 함수 생성

    const onInsert = useCallback(() => {
        const nextList = list.concat(parseInt(number))
        setList(nextList)
        setNumber('')
        inputEl.current.focus()
    }, [number, list]) // number 또는 list가 바뀌었을 때만 함수 생성

    // list 배열의 내용이 바뀔때만 getAverage 함수 호출된다
    const avg = useMemo(() => getAverage(list), [list])

    return (
        <div>
            <input value={number} onChange={onChange} ref={inputEl}></input>
            <button onClick={onInsert}>등록</button>
            <ul>
                {list.map((value, index) => (
                    <li key={index}>{value}</li>
                ))}
            </ul>
            <div>평균값 {avg}</div>
        </div>
    )
}

export default Average

로컬 변수 사용하기

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

커스텀 Hooks 만들기

  • 분리하여 깔끔하게 코드 작성할 수 있다.
  • 다른 개발자가 만든 다양한 Hooks도 라이브러리로 설치하여 사용가능하다

useInputs.js

import React, {useReducer} from 'react';

function reducer(state, action) {
    return {
        ...state,
        [action.name]: action.value
    }
}

export default function useInputs(initialForm) {
    const [state, dispatch] = useReducer(reducer, initialForm)

    // e.target 값 자체를 액션 값으로 사용
    const onChange = e => {
        dispatch(e.target)
    }

    return [state, onChange]
}

Info.js

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>
                <input name="nickName" value={nickname} onChange={onChange}></input>
            </div>
            <div>
                <div>이름: {name}</div>
                <div>닉네임: {nickname}</div>
            </div>
        </div>
    )
}

export default Info
profile
프론트엔드 제롬

0개의 댓글