Web/React

React Hooks 정리

daeunnniii 2023. 1. 24. 16:02
728x90
반응형

React Hook이란?

  • React Hook이란, React 버전 16.8부터 React 요소로 새로 추가되었음.
  • Hook이 나오기 전까지는 State 값 접근, lifecycle 구현을 사용하기 위해 Class 컴포넌트 선언을 해줘야했음.
  • 또한 Function 컴포넌트는 한번 호출되고 메모리상에서 사라져 State 값 접근과 lifecycle 구현이 불가능.

기존의 class component를 사용하는 React 방식

import "./style";
import { Component, render } from "preact";
 
export default class App extends Component {
  state = {
    count: 0
  };
 
  modify = (n) => {
    this.setState({
      count: n
    });
  };
  render() {
    const { count } = this.state;
    return (
      <div>
        <div>{count}</div>
        <button onClick={() => this.modify(count + 1)}> Increment</button>
      </div>
    );
  }
}
 
if (typeof window !== "undefined") {
  render(<App />, document.getElementById("root"));
}

 

Hook

  • Hook을 사용하면 기존 class 바탕의 코드를 작성할 필요 없이 함수 컴포넌트에서도 state 값에 접근이 가능하고  함수형 프로그래밍으로 다음과 같이 작성 가능해짐.
import React, { Component, useState } from "react";
import "./styles.css";
 
function App() {
  const [count, setCount] = useState(0);
  return (
    <>
      {count}
      <p></p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </>
  );
}
 
export default App;

 

 

Hook의 종류

1. useState(): 함수형 컴포넌트에서도 가변적인 상태를 지닐 수 있게 해줌. 해당 함수의 인자에는 상태의 기본값이 들어감.

2. useEffect(): 컴포넌트가 렌더링될 때마다 특정 작업을 수행하도록 설정할 수 있음.

3. useReducer()

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

- 현재 state와 업데이트를 위해 필요한 정보를 담은 action 값을 전달받아 새로운 상태를 반환하는 함수. 반드시 불변성을 지켜줘야 함.

- 첫번째 인자로 reducer 함수를 받고, 두번째 인자로 해당 리듀서의 기본값을 넣어줌.

- 이 Hook은 배열을 리턴하고, 첫번째 인자가 상태고, 두번째 인자가 dispatch 함수인 길이가 2인 배열을 리턴함.

 

useReducer 예시

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;

 

4. useMemo()

- 함수형 컴포넌트 내부에서 발생하는 연산을 최적화할 수 있음.

- 렌더링하는 과정에서 특정 값이 바뀌었을 때만 연산을 실행하고, 원하는 값이 바뀌지 않았다면 이전에 연산했던 결과를 다시 사용하는 방식.

- 예를 들어 아래 예제에서 users에 변화가 있을 때만 countActiveUsers() 함수가 실행되야하는데, input 값이 바뀔 때에도 컴포넌트가 리렌더링 되므로 이렇게 불필요할때에도 호출하여 자원이 낭비됨.

- 이때 useMemo() Hook 함수를 통해 성능을 최적화할 수 있음.

 

useMemo 코드 예시

import React, { useRef, useState, useMemo } from 'react';
import UserList from './UserList';
import CreateUser from './CreateUser';
 
function countActiveUsers(users) {
  console.log('활성 사용자 수를 세는중...');
  return users.filter(user => user.active).length;
}
 
function App() {
  const [inputs, setInputs] = useState({
    username: '',
    email: ''
  });
  const { username, email } = inputs;
  const onChange = e => {
    const { name, value } = e.target;
    setInputs({
      ...inputs,
      [name]: value
    });
  };
  const [users, setUsers] = useState([
    {
      id: 1,
      username: 'velopert',
      email: 'public.velopert@gmail.com',
      active: true
    },
    {
      id: 2,
      username: 'tester',
      email: 'tester@example.com',
      active: false
    },
    {
      id: 3,
      username: 'liz',
      email: 'liz@example.com',
      active: false
    }
  ]);
 
  const nextId = useRef(4);
  const onCreate = () => {
    const user = {
      id: nextId.current,
      username,
      email
    };
    setUsers(users.concat(user));
 
    setInputs({
      username: '',
      email: ''
    });
    nextId.current += 1;
  };
 
  const onRemove = id => {
    // user.id 가 파라미터로 일치하지 않는 원소만 추출해서 새로운 배열을 만듬
    // = user.id 가 id 인 것을 제거함
    setUsers(users.filter(user => user.id !== id));
  };
  const onToggle = id => {
    setUsers(
      users.map(user =>
        user.id === id ? { ...user, active: !user.active } : user
      )
    );
  };
  const count = useMemo(() => countActiveUsers(users), [users]);
  return (
    <>
      <CreateUser
        username={username}
        email={email}
        onChange={onChange}
        onCreate={onCreate}
      />
      <UserList users={users} onRemove={onRemove} onToggle={onToggle} />
      <div>활성사용자 수 : {count}</div>
    </>
  );
}
 
export default App;

 

5. useCallback()

- useMemo()와 비슷하며, useMemo()를 기반으로 만들어짐. useMemo() 는 특정 결과값을 재사용 할 때 사용하는 반면, useCallback 은 특정 함수를 새로 만들지 않고 재사용하고 싶을때 사용. 

- 주로 렌더링 성능을 최적화해야 하는 상황에서 사용. 이벤트 핸들러 함수를 필요할 때만 생성할 수 있음.

- 첫번째 인자에는 생성하고 싶은 함수를 넣고, 두번째 인자에는 배열을 넣는다. 이 배열에는 어떤 값이 바뀌었을 때 함수를 새로 생성해야 하는지 명시해야함.

- 함수 내부에서 상태 값에 의존해야 할 때는 그 값을 반드시 두 번째 파라미터 안에 포함시켜 주어야 함.

 

useCallback 사용 예시

const onChange = useCallback(e => {
  const { name, value } = e.target;
  setInputs(inputs => ({
    ...inputs,
    [name]: value
  }));
}, []);
 
const onRemove = useCallback(id => {
  setUsers(users => users.filter(user => user.id !== id));
}, []);

 

6. useRef()

- 함수형 컴포넌트에서 ref를 쉽게 사용할 수 있도록 한다.

- useRef를 통해 만든 객체 안의 current 값이 실제 엘리먼트를 가리킨다.

 

useRef 사용 예시

import React, { useState, useRef } from 'react';
 
function InputTest() {
  const [text, setText] = useState('');
  const nameInput = useRef();
 
  const onChange = e => {
    setText(e.target.value)
  };
 
  const onReset = () => {
    setText('');
    nameInput.current.focus();
  };
 
  return (
    <div>
      <input
        name="name"
        onChange={onChange}
        value={text}
        ref={nameInput}
      />
 
      <button onClick={onReset}>초기화</button>
      <div>
        <b>내용: </b>
        {text}
      </div>
    </div>
  );
}
 
export default InputTest;

 

 

Hook 규칙

1. Top Level에서만 Hook을 호출해야 한다. Hook을 호출하는 순서는 항상 같아야 한다.

  • 반복문, 조건문, 중첩된 함수 내에서 Hook을 사용하면 안된다.
  • 왜 훅의 호출 순서가 같아야 하는 걸까?
    • 리액트가 상태값을 구분할 수 있는 유일한 정보는 Hook이 사용된 순서이기 때문이다. 리액트가 Hook이 호출된 순서에 의존한다는 것이다.
    • 예시로 반복문 안에서 Hook을 호출했을 때 반복문이 true라면 괜찮겠지만 값이 false라면 건너뛰게 된다. 이렇게 하면 실행순서가 바뀔 수 있어 오류를 일으킨다.
  • 조건문 혹은 반복문을 사용하고 싶을때는 useEffect안에 넣어 사용하면 된다.
    • useEffect 내 조건문에서 사용하는 예시
export function RequireLogin(){
    const navigate = useNavigate();
    let auth = GetStore("user", "user");
    useEffect(() => {
        console.log("auth::", auth);
        if(auth==="123123"){
            navigate('/login');
        }
    }, []);
}

 

2. React 함수형 컴포넌트 또는 Custom Hook 내부에서만 Hook을 호출해야 한다.

  • 일반적인 Javascript 함수에서는 호출하면 안된다.
  • 직접 작성한 custom hook에서는 사용이 가능하다.

 

다음과 같이 useEffect에서 호출하는 비동기 함수 내에서 Hook을 호출하는 것도 가능.

export function RequireLogin(){
    const navigate = useNavigate();     // HOOK
    let auth = GetStore("user", "user");
 
    const checkAuth = async () => {
        let url = 'http://localhost:8000/api_v1/public/login';
        await Axios.post(url, {id:"", passwd:""}, {withCredentials: true})
        .then((res) => {
            if(res){
                console.log(res);
                navigate('/login');     // HOOK 호출
            }
        }).catch((e) => {
            navigate('/login');
            console.log(e);
        })
    }
 
    useEffect(() => {
        console.log("auth::", auth);
        checkAuth();
    }, []);
}

 

블록 스코프 안에서는 Hook 사용 불가능!

export default function App(){
  return {
    <div>
      // 불가능
      <div>{const [value, setvalue] = useState()}</div>
    </div>
  }
}

 

비동기 함수(async 키워드가 붙은 함수)는 콜백함수로 사용할 수 없다.

export default function App(){
  // useEffect Hook 내부에, 비동기 함수가 들어가므로 에러 발생
  useEffect(async () => {
    await Promise.resolve(1)
  }, [])
   
  return {
    <div>
      <div>Test</div>
    </div>
  }
}

 

→ 위와 같은 규칙으로 useNavigate와 같은 Hook을 직접적으로 호출하는 것이 아닌 아래와 같이 변후로 선언해서 함수를 호출

import { useNavigate } from "react-router-dom";
const Login = () => {
  const navigate = useNavigate();
  return(
    <div className="login">
      <input placeholder="전화번호, 사용자 이름 또는 이메일"/>
      <input placeholder="비밀번호/>
      <button onClick={() => {navigate("/main");}}>로그인</button>                  
    </div>
  )
}

 

Custom Hook 만들기

  • input을 관리하는 코드와 같이 반복되는 로직을 처리해야할 때 Custom Hook을 사용
  • 반복되는 로직을 쉽게 재사용이 가능함.

 

  • Custom Hook을 만들기 위해선 use라는 키워드로 시작하는 함수를 만들어야함.
  • 해당 함수 내에서 useState, useEffect, useReducer, useCallback 등 Hooks를 사용하여 원하는 기능을 구현하여 함수형 컴포넌트에서 사용할 수 있음.
    • 함수형 컴포넌트의 함수 이름은 대문자로 시작해야함.
export const apiGet = async () => {
    const navigate = useNavigate();             // 함수형 컴포넌트가 아니므로 호출 불가능
}
 
export const ApiGet = async () => {
    const navigate = useNavigate();             //  함수형 컴포넌트이므로 호출 가능 
}

 

 

 

728x90
반응형

'Web > React' 카테고리의 다른 글

[React] redux-persist를 통한 Store 유지하기  (0) 2023.01.24
Redux-toolkit으로 상태관리하기  (0) 2023.01.24