본문 바로가기

Computer Programming/[리액트를 다루는 기술]

[리액트를 다루는 기술] 8. Hooks | Custom Hooks

1. useEffect cleanup ?

useEffect 에서는 함수를 반환할 수 있는데, 이를 cleanup 함수라고 한다. 

렌더링이 다시 일어날때 (첫 렌더링 또는 deps 변화) useEffect 의 return 함수가 실행된다. 

이 함수의 실행 시점은 업데이트 직전이다. 따라서 업데이트 직전 clean up 하고싶거나 원하는 상태로 되돌리고싶을때, 또는 중단하고 싶을 때 사용하면 된다.

 

useEffect(() => {
    console.log("렌더링이 되었습니다!")
    console.log(name)
    return () => {
        console.log("cleanup")
        console.log(name)
    }
},[name])

 

*주의사항

의존성 배열에 함수를 추가하면 렌더링때 마다 갱신됨 (렌더링때마다 함수가 새로 생성되서)

그러니 useCallback을 사용해서 함수가 필요할 때만 갱신되도록 하거나 useMemo로 동일한 객체가 전달되도록 해야한다.

 

또한 effect 내에서 쓰이는 값 전부를 의존성 배열에 알려줘야한다.

 

const fetchUserFnc = useCallback(async () => {
  const res = fetchUser(id);
  ...
}, [id]);

useEffect(() => {
  fetchUserFnc();
}, [fetchUserFnc]);

 

 

 

 

2. useReducer

import { useReducer } from "react";

function reducer(state, action) {
    switch (action.type) {
        case 'INCREASE':
            return { value: state.value + 1 }
        case 'DECREASE':
            return { value: state.value - 1 }
        default:
        	return state
    }
}

const Counter = () => {
    const [state, dispatch] = useReducer(reducer, { value : 0 })// useReducer의 파라미터1: reducer, 
    return (
        <div>
            {state.value}
            <div>
                <button onClick={() => dispatch({ type : 'INCREASE'})}>+</button>
                <button onClick={() => dispatch({ type : 'DECREASE'})}>-</button>
            </div>
        </div>
    )
}
export default Counter;

 

useReducer의 첫 번째 파라미터에는 리듀서 함수를 넣고, 두 번째 파라미터에는 해당 리듀서의 기본값을 넣어준다. 여기서는 { value: 0 }객체이며 reducer 함수를 통해 현재 state 를 가져오고, state.value에 접근해 액션의 타입에 따라 +1을 하거나 -1을 해 객체를 반환한다.

 

 

또한 dispatch 함수를 호출하면 리액트가 useReducer 훅 호출 시 첫번째 파라미터로 전달한 reducer이 실행된다.

 

 

action으로는 일반적으로 '객체'를 전달하지만 문자열이나 숫자 모두 전달 가능하다. 일반적으로 action 객체는 type 이라는 프로퍼티에 동작을 지정하여 상태 변경을 '여러 상황'으로 분기하여 업데이트 하도록 도와준다! 

 

import { useReducer } from "react";

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

const Info = () => {
    const [state, dispatch] = useReducer(reducer, {
        name: '',
        nickname:''
    })// useReducer의 파라미터1: reducer, 
    const {name, nickname} = state;
    const onChange = (e) => {
        dispatch(e.target)
    }
    return (
        <div>
            {state.value}
            <div>
                <input name="name" value={name} onChange={onChange}/>
                <input name="nickname" value={nickname} onChange={onChange}/>
                <br />
                <b>이름: </b> {name}
                <b>닉네임: </b> {nickname}
            </div>
        </div>
    )
}
export default Info;

 

 

3. useMemo

숫자를 추가하여 평균값을 보여주는 간단한 코드

import { useState, useMemo } from "react";

const getAverage = (list) => {
    if (list.length === 0) return 0
    console.log(list)
    const sum = list.reduce((a,b) => a+b)
    return sum/list.length
}

const Average = () => {
    const [num, setNum] = useState('')
    const [list, setList] = useState([])

    const onChange = (e) => {
        setNum(e.target.value)
    }

    const onClick = () => {
    	//정수로 만들기, 안 만들면 문자열로 더해서 수가 매우 커짐
        const newList = list.concat(parseInt(num))
        //새로운 리스트로 변경 (불변성 유지)
        setList(newList)
        setNum('')
    }

    const avg = useMemo(() => getAverage(list), [list])

    console.log("rendering")
    return (
        <div>
            <input value={num} onChange={onChange}/>
            <button onClick={onClick}>추가</button>
            <ul>
                {list && list.map((item) => <li>{item}</li>)}
            </ul>
            <d>평균값: </d> {avg}
        </div>
    )
}

export default Average;

 

 

useMemo를 통해 연산 작업 최적화하기 

 

    const avg = useMemo(() => getAverage(list), [list])

 

매번 렌더링 될 때마다 이 함수를 실행시키는 것이 아니라, list 의 값이 변화할 때만 연산을 실행하도록 해준다.

 

 

4. useCallback

함수 재사용 => 렌더링 최적화

렌더링이 자주 발생하거나 렌더링 할 컴포넌트 개수가 많아지면 최적화하는 것이 좋음

 

const onChange = useCallback((e) => {
    setNum(e.target.value)
    console.log("onchange")
}, []) //처음 렌더링 시에만 함수 생성

//num || list가 바뀌었을 때만 함수 생성
const onClick = useCallback(() => {
    const newList = list.concat(parseInt(num))
    //새로운 리스트로 변경 (불변성 유지)
    setList(newList)
    setNum('')
    console.log("onclick")
},[num,list])

 

렌더링 시 매번 함수가 다시 생성되는데, 원하는 때만 (첫 렌더링 시 또는 state 가 바뀌었을 경우)에만 함수를 생성할 수 있다. 빈 배열을 넣으면 첫 렌더링 시 만든 함수를 계속해서 재사용하게 된다.

 

 

 

5. useRef

 

id처럼 사용할 수 있다.

 

const Average = () => {
    const [num, setNum] = useState('')
    const [list, setList] = useState([])
    const inputEl = useRef(null)

    const onChange = useCallback((e) => {
        setNum(e.target.value)
        console.log("onchange")
    }, [])

    const onClick = useCallback(() => {
        const newList = list.concat(parseInt(num))
        //새로운 리스트로 변경 (불변성 유지)
        setList(newList)
        setNum('')
        inputEl.current.focus();
        console.log("onclick")
    },[num,list])

    const avg = useMemo(() => getAverage(list), [list])

    console.log("rendering")
    return (
        <div>
            <input value={num} onChange={onChange} ref={inputEl}/>
            <button onClick={onClick}>추가</button>
            <ul>
                {list && list.map((item) => <li>{item}</li>)}
            </ul>
            <d>평균값: </d> {avg}
        </div>
    )
}

렌더링과 관련되지 않는 값에도 사용한다. 

 

 

5. Custom Hooks

import { useReducer } from "react";

const reducer = (state, action) => {
    return {
        ...state,
        [action.name] : action.value
    }
}

const useInputs = (initialState) => {
    const [state, dispatch] = useReducer(reducer,initialState)

    const onChange = (e) => {
        dispatch(e.target)
    }

    return [state, onChange]
}

export default useInputs;

사용할 것을 리턴해야되는데 배열로 state와 onChange를 반환하는 부분이 새로웠다

 

위의 커스텀 훅을 만들고

 

아까 전 useReducer 할때 작성했던 인풋 2개를 받는 코드를 가져와서 다시 작업했다.

 

import useInputs from "./useInputs";

const Info = () => {
    const [state, onChange] = useInputs({ //initial object를 넘겨주고 반환된 state와 onChange를 가져다 씀
        name: '',
        nickname:''
    })
    const {name, nickname} = state;

    return (
        <div>
            {state.value}
            <div>
                <input name="name" value={name} onChange={onChange}/>
                <input name="nickname" value={nickname} onChange={onChange}/>
                <br />
                <b>이름: </b> {name}
                <b>닉네임: </b> {nickname}
            </div>
        </div>
    )
}
export default Info;

코드 재사용 👍

 

 

educative.io