React : Hooks + customHook

김가영·2020년 12월 21일
0

Web

목록 보기
9/11
post-thumbnail

react.vlpt.us 참고

useEffect

component가 마운드 됐을 때 (처음 나타났을 때), 언마운트 됐을 때 (사라질 때), 업데이트 될 때 (특정 props가 바뀔 때) 특정 작업을 처리하는 방법

useEffect 심화

마운트 / 언마운트

useEffect(() => {
        console.log('컴포넌트가 화면에 나타남');
        return () => {
            console.log('컴포넌트가 화면에서 사라짐');
        };
}, []);

첫번째 파라미터에는 함수, 두번째 파라미터는 의존 값이 들어있는 배열 (debs)
debs 배열이 비어있으면, 컴포넌트가 처음 나타났을 때에만 useEffect 에 등록한 함수가 호출됩니다.
useEffect에서 함수를 반환할 수 있는데 이를 cleanup 함수라고 부른다.

clean up 함수는 useEffect에 대한 뒷정리르 해준다고 생각하면 된다. 
debs가 비어있는 경우에는 컴포넌트가 사라질 때 cleanup 함수가 호출된다

마운트 시에 할 수 있는 작업들

  • props로 받은 값을 컴포넌트에 로컬 상태로 설정
  • 외부 API 요청
  • 라이브러리 사용 (D3, Video.js 등...)
  • setInterval 을 통한 반복작업 또는 setTimeout을 통한 작업 예약

언마운트 시에 할 수 있는 작업들

  • setInterval, setTimeout 에 등록된 작업들 없애기
  • 라이브러리 인스턴스 제거

debs에 값 넣기

debs 배열에 값을 넣으면, 컴포넌트가 처음 마운트될 때에도 호출이 되고, 지정한 값이 바뀔 때에도 호출이 된다. 언마운트시에도 호출되고, 값이 바뀌기 직전에도 호출.

useEffect(() => {
    console.log('user 값이 설정됨');
    console.log(user)
    return () => {
        console.log('user가 바뀌기 전');
        console.log(user);
    };
}, [user]);

useEffect 안에서 사용하는 상태나 props가 있다면 useEffect의 deps에 넣어줘야 한다. debs 내에서 지정한 값이 변화했을 때에만 effector가 다시 실행되도록 한 것.

useMemo

memoized → 이전에 계산한 값을 재사용한다는 의미이다.

import './App.css';
import React, {useRef, useState, useMemo }from 'react';
import UserList from './UserList';
import CreateUser from './CreateUser';

function counterActiveUsers(users) {
  console.log('counting active users...');
  return users.filter(user => user.active).length;
}

function App() {
  ...
  
  const count = useMemo(() => counterActiveUsers(users), [users]);
  
  return (
    <div className="App">
      <CreateUser
        username={username}
        email={email}
        onChange={onChange}
        onCreate={onCreate}
      />
      < UserList users = {users} onRemove={onRemove} onToggle={onToggle}/>
      <div>number of Active users : {count}</div>
    </div>
  );
}

export default App;

그냥

const count = counterActiveUsers(users); 

를 해버리면 component가 바뀔때마다 countActiveUsers가 실행되지만,
useMemo 를 쓰면 역시 debs 배열 내의 내용이 바뀌지 않았을 땐 이전에 연산한 값을 재사용하게 한다.

useCallback

useMemo 와 비슷
useMemo 가 특정 결과값을 재사용한다면,useCallback은 특정 함수를 재사용하고 싶을 때 사용한다.

그냥 함수를 선언하면 컴포넌트가 리렌더링할때마다 새로 만들어진다.

const onCreate = useCallback(() => {
    const user = {
    id: nextId.current,
    username,
    email
  };
  setUsers([...users, user]);

  setInputs({
    username: '',
    email: ''
  })

  nextId.current += 1;
}, [users, username, email]);

이런 식으로.
함수 안에서 사용하는 state / props는 항상 debs 에 포함시켜야 한다.

React.memo

컴포넌트의 props 가 바뀌지 않았을땐 리렌더링을 방지하는 것

export default React.momo(CreateUser);

를 이용해주면 된다.
또는,

const User = React.memo(function User(){...});

useReducer

컴포넌트의 상태 업데이트 로직을 컴포넌트에서 분리시킬 수 있다.

function reducer(state, action) {
	
    return nextState;
}

현재 상태와 액션 객체를 parameter로 가져와서 새로운 상태를 반환하는 것이 useReducer.

const [state, dispatch] = useReducer(reducer, initialState);

dispatch({ type: 'Increment'}); 라고 사용한다.
useReducer 에 넣는 첫번째 파라미터는 reducer 함수이고, 두번쨰 파라미터는 초기 상태.

import React, { useReducer } from 'react'

function reducer(state, action) {
    switch (action.type) {
        case 'INCREMENT':
            return state +1;
        case 'DECREMENT':
            return state -1;
        default:
            return state;
    }
}

function Counter() {
    
    const [number, dispatch] = useReducer(reducer, 0);

    const onIncrease = () => {
        dispatch({ type : 'INCREMENT'});
    };

    const onDecrease = () => {
        dispatch({ type : 'DECREMENT'});
    };

    return (
        <div>
            <h1>{number}</h1>
            <button onClick={onIncrease}>+1</button>
            <button onClick={onDecrease}>-1</button>
        </div>
    );
}

export default Counter;

useReducer 를 사용하여 모든 code를 변환

import './App.css';
import React, {useRef, useState, useMemo, useCallback, useReducer }from 'react';
import UserList from './UserList';
import CreateUser from './CreateUser';

function counterActiveUsers(users) {
  console.log('counting active users...');
  return users.filter(user => user.active).length;
}

function reducer(state, action) {
  switch (action.type) {
    case 'CHANGE_INPUT':
      return {
        ...state,
        inputs: {
          ...state.inputs,
          [action.name]: action.value
        }
      }
    case 'CREATE_USER':
      return {
        inputs: initialState.inputs,
        users: state.users.concat(action.user)
      }
    case 'REMOVE_USER':
      return {
        ...state,
        users : state.users.filter(user => user.id != action.userId)
      }
    case 'TOGGLE_USER':
      return {
        ...state,
        users : state.users.map(user => user.id === action.userId? {...user, active : ! user.active } : user)
      }
    default:
      return state;
  }
}

const initialState = {
  inputs : {
    username: '',
    email: ''
  },
  users : [
    {
      id: 1,
      username: 'velopert',
      email: 'public.velopert@gmail.com',
      active : true,      
  },
  {
      id: 2,
      username: 'tester',
      email: 'tester@gmail.com',
      active : false,
  },
  {
      id: 3,
      username: 'liza',
      email: 'liz@gmail.com',
      active : false,
  },
  ]
}

function App() {

  const [state, dispatch] = useReducer(reducer, initialState);
  const nextId = useRef(4);

  const { users } =state;
  const { username, email } = state.inputs;

  const onChange = useCallback(e => {
    const { name, value } = e.target;
    dispatch({
      type: 'CHANGE_INPUT',
      name,
      value
    });
  }, [])

  const onCreate = useCallback(e => {
    dispatch({
      type : 'CREATE_USER', 
      user : {
        id: nextId.current,
        username,
        email
      }
    })
    nextId.current +=1;
  }, [username, email]);

  const onRemove = useCallback(e => {
    dispatch({
      type : 'REMOVE_USER',
      userId : e,
    })
  }, [])

  const onToggle = useCallback(e => {
    dispatch({
      type: 'TOGGLE_USER',
      userId : e,
    })
  },[])

  const count = useMemo(() => counterActiveUsers(users), [users]);
  
  return (
    <div className="App">
      <CreateUser username={username} email={email} onChange={onChange} onCreate={onCreate}/>
      < UserList users = {users} onRemove={onRemove} onToggle={onToggle}/>
  <div>number of Active users : {count}</div>
    </div>
  );
}

export default App;

커스텀 hook

  • hooks/useInput.js
import { useState, useCallback } from 'react'

function useInputs(initialForm) {
    const [form, setForm] = useState(initialForm);

    const onChange = useCallback(e => {
        const { name, value } = e.target;
        setForm(form => ({...form, [name] : value}));
    }, []);
    const reset = useCallback(e => setForm(initialForm), [initialForm]);

    return [form, onChange, reset];
}

export default useInputs;
  • App.js
const [{username, email}, onChange, reset] = useInputs({
    username : '',
    email: ''
  });

useInput 을 useReducer 로 다시 구현한 것

import {useReducer, useCallback} from 'react'

function reducer(state, action) {
    switch (action.type) {
        case 'CHANGE':
            
            return {
                ...state,
                [action.name] : action.value
            };
        case 'RESET':
            return {
                ...action.initialState
            };
        default : 
            return state;
    }
}

function useNewInputs(initialState) {
    const [state, dispatch] = useReducer(reducer, initialState);
    
    const onChange = useCallback(e => {
        const { name, value } = e.target;
        dispatch({
            type : 'CHANGE',
            name,
            value,
        })
    }, []);

    const reset = useCallback(e => {
        dispatch({
            type : 'RESET',
            initialState,
        })
    },[initialState]);

    return [state, onChange, reset]
}


export default useNewInputs
profile
개발블로그

0개의 댓글