Hooks

Haechan Kim·2022년 1월 11일
0

React

목록 보기
7/13

  • Hooks
    Hooks는 리액트 v16.8에 새로 도입된 기능으로 함수형 컴포넌트에서 할 수 없었던 작업들 할 수 있게 해줌
    ㄴ useState, useEffect 등

  • useState
    useState는 가장 기본적인 Hook이며 함수형 컴포넌트에서도 가변적 상태 지닐 수 있게 해줌
    함수형 컴포넌트에서 상태(state) 관리해야 한다면 useState 사용하면 됨.

useState를 이용한 숫자 카운터 구현

import React, {useState} from 'react';
// Counter.js
const Counter = () => {
    // useState함수의 파라미터에는 상태의 기본값
    // 카운터의 기본값을 0으로 설정
    // useState 호출되면 배열 반환하는데
    // 베열을 첫번째 원소는 상태값, 두번째는 상태값을 설정하는 함수 
    const [value, setValue] = useState(0); // 그 배열

    return (
        <div>
            <p>
                현재 카운터 값은 <b>{value}</b>입니다.
            </p>
            <button onClick = {() => setValue(value+1)}>+1</button>
            <button onClick = {() => setValue(value-1)}>-1</button>
        </div>
    )
}

export default Counter;

하나의 useState함수는 하나의 상태값만 관리
관리해야 할 상태 여러개면 여러번 사용

  • useEffect
    useEffect: 리액트 컴포넌트가 렌더링 될 때마다 특정 작업 수행 설정할 수 있음
    클래스형의 componentDidMount와 componentDidUpdate 합친 형태
import React, {useState, useEffect} from 'react';
// Info.js
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>
            <input value = {name} onChange={onChangeName} />
            <input value = {nickname} onChange={onChangeNickname} />
            <p>이름은 {name}</p>
            <p>닉네임은 {nickname}</p>
        </div>
    )
}

export default Info;

마운트 될때만 실행하고 싶다면 두번째 파라미터로 빈 배열

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

특정 값이 업데이트될 때만 실행하고 싶다면
클래스형 에서는

componentDidUpdate(prevProps, prevState) {
    if (prevProps.value !== this.props.value) {
        doSomething();
    }
}

이 코드는 props안의 value가 바뀔때만 작업 수행
이 작업은 useEffect에서는 이전의 두번째 빈 배열에 검사하고 싶은 값 넣으면 됨.

useEffect(() => {
    console.log('이름 변할때만 실행됨!! ' + name);
  }, [name]);

useEffect는 기본적으로 렌더링 직후마다 실행
언마운트 전이나 업데이트 직전에 어떤 작업 수행하고 싶다면 useEffect에서 뒷정리(cleanup) 함수 반환해 줘야 함

App 컴포넌트에서 Info 컴포넌트 가시성 바꾸기

import React, {useState} from 'react';
import Info from './Info'

const App = () => {
  const [visible, setVisible] = useState(false);
  return (
    <div>
      <button 
      onClick = {() => {
        setVisible(!visible);
      }}
      >
        {visible ? '숨기기' : '보이기'}
        </button>
        <hr/>
        {visible && <Info />}
    </div>
  )
};

export default App;
  • useReducer
    useState보다 더 다양한 컴포넌트 상황에 따라 가양한 상태 다른 값으로 업데이트 해야 할 때
    리듀서는 현재 상태, 업데이트에 필요한 정보를 담은 액션(action) 값을 전달받아 새로운 상태 반환하는 함수

Counter를 useReducer 사용해 다시 구현

import React, {useReducer} from 'react';

function reducer(state, action) {
    // action.tyle에 따라 다른 작업 수행
    switch(action.type) {
        case 'INCREMENT' :
            return {value: state.value + 1};
        case 'DECREMENT' :
            return {value: state.value - 1};
        default:
            return state;
    }
}

const Counter = () => {
    // useReducer의 첫 파라미터에 리듀서 함수, 두번째에 해당 리듀서의 기본 값 넣기
    // 이 Hook 사용하면 state값과 dispatch함수 받아 엄
    // state는 현재 가리키는 상태이고
    // dispatch는 액션 발생시키는 함수
    // dispatch(action) 형태로 함수안에 파라미터로 액션 값 넣어주면 리듀서 함수가 호출됨
    const [state, dispatch] = useReducer(reducer, {value : 0});

    return (
        <div>
            <p>
                현재 카운터 값은 <b>{state.value}</b>입니다!
            </p>
            <button onClick = {() => dispatch({type: 'INCREMENT'})}>+1</button>
            <button onClick = {() => dispatch({type: 'DECREMENT'})}>-1</button>
        </div>
    )
}

export default Counter;

useReducer의 가장 큰 장점은 컴포넌트의 업데이트 로직을 컴포넌트 바깥으로 빼낼 수 있다는 것

  • useReducer 사용해 인풋 상태 관리
    Info 컴포넌트의 인풋 상태를 관리해 보자
    기존에는 인풋이 여러개여서 useState 여러번 사용
    useReducer 사용하면 클래스형에서 input태그에 name값 할당하고 e.target.name 참조해 setState 해준 것과 유사한 방식으로 처리
// Info.js
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>
            <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>
    )
}

useReducer에서의 액션은 어떤 값이든 사용 가능
위에서 이벤트 객체가 지니고 있는 e.target자체를 액션값으로 사용
인풋 개수 많아져도 코드 짧고 깔끔하게 유지 가능

  • useMemo
import React, {useState, useMemo} from 'react';
// Average.js
const getAverage = numbers => {
    console.log('평균 값 계산 중...');
    if (numbers.length === 0) return 0;
    const sum = numbers.reduce((acc, val) => acc + val);
    return sum / numbers.length;
}; // 숫자 등록할때만 아니라 인풋 내용 수정될 때로 getAverage 함수 호출됨
// 인풋 내용 바뀔 때는 평균값 다시 계산할 필요 업음
// 렌더링 마다 계산하는 것은 낭비
// useMemo 사용하면 작업 최적화 가능
// 렌더링 과정에서 특정 값 바뀌었을때만 연산 실행하고
// 바뀌지 않았다면 잊너 연산 결과 다시 사용


const Average = () => {
    const [list, setList] = useState([]);
    const [number, setNumber] = useState('');

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

    const onInsert = e => {
        const nextList = list.concat(parseInt(number));
        setList(nextList);
        setNumber('');
    };

    // list 배열 내용 바뀔 때만 getAverage 함수 호출 됨
    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
    useMemo와 비슷함
    ㄴ useMemo 는 특정 결과값 재사용 할 때 사용,
    useCallback은 특정 함수 새로 만들지 않고 재사용 할 때 사용

렌더링 성능 최적화해야 하는 경우 사용
이 함수 사용 시 이벤트 핸들러 함수 필요할 때만 생성할 수 있음
컴포넌트의 렌더링이 자주 발생하거나 렌더링해야 할 컴포넌트가 많아지면 최적화 하는것이 좋음

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 혹은 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>평균값: {avg}</b>
            </div>
        </div>
    )
}

export default Average;
  • useRef

  • 커스텀 Hooks

  • 정리
    Hooks 사용하면 클래스형 컴포넌트 작성하지 않고도 대부분 기능 구현 가능
    함수형 컴포넌트와 Hooks 사용을 권고
    꼭 필요할 때만 클래스 컴포넌트 구현

0개의 댓글