본문 바로가기

React

Hooks : useState, useEffect, useReducer, useRef, useMemo, useCallback

1. Hooks란 무엇인가?
Hooks는 리엑트 v16.8에 새로 도입된 기능으로 기존의 함수
컴포넌트에서 할 수 없었던 다양한 작업을 할 수 있게 도와줍니다.

2. useState


함수 컴포넌트에서 상태 관리를 위한 훅입니다
하나의 상태 값만 관리할 수 있으므로 관리해야 할 상태가
여러개라면 useState를 여러번 사용하면 됩니다.

import {useState} from 'react'

const [value, setValue] = useState(0)
// value: 값
// setValue: 값의 상태를 변경하고 리렌더링됩니다.
// useState(): 괄호 안에 기본 값을 넣어줍니다.


3. useEffect
리엑트 컴포넌트가 렌더링될 때마다 작업을 생명 주기(Lifecycle)와
의존성 배열에 따라서 수행하도록 설정할 수 있는 훅입니다.

import {useEffect} from 'react'

const useEffect(( ) => { 
    //Mounting
    return(
    //unMounting
    )
}, [
    //Dependency array
])


4. useReducer
useState보다 더 다양한 컴포넌트 상황에 따라
값을 업데이트해 주고 싶을 때 사용하는 훅입니다.

import {useReducer} from 'react'

function reducer(state, action) {
    return {
        // 불변성을 유지해줘야 합니다
        ...state,
        [action.name]: action.value
    }
}

// state와 dispatch 함수를 받아옵니다.
const [state, dispatch] = useReducer(reducer, {value: 0});

// action에 type 필요없이 어떤 값도 들어올 수 있습니다.
const onChange = e => {
    dispatch(e.target)
}
// state: 현재 상태
// dispatch: 액션을 발생시키는 함수
// reducer: 리듀서 함수
// {value: 0}: 기본 값



5. useMemo
함수 컴포넌트 내부에서 발생하는 연산을 최적화할 수 있습니다.
렌더링 과정에서 특정 값이 바뀌었을 때만 연산을 실행하고,
원하는 값이 바뀌지 않았다면 이전에 연산했던 결과를 다시 사용 합니다.

import {useMemo} from 'react'

const getDiaryAnalysis = useMemo(() => {
    console.log('useMemo 연산 시작함');
    const sum = x + y;
    return { sum };
  }, [x, y]);
// {}: callback Function
// []: Dependency array(의존성 배열)


6. useCallback
useMemo와 유사한 함수입니다.
주로 렌더링 성능을 최적화해야 하는 상황에서 사용합니다.
만들어 놨던 함수를 재사용할 수 있습니다.

import {useCallback} from 'react'

const getDiaryAnalysis = useCallback(() => {
    const nextList = list.concat(parseint(number));
    setList(nextList)
    setNumber('')
  }, [number, list]);
{}: callback Function
[]: Dependency array(의존성 배열)


7. useRef
함수 컴포넌트에서 ref를 쉽게 사용할 수 있도록 해 줍니다.

useRef 사용하여 포커싱 주기

import { useEffect, useRef } from "react";
import "./App.css";

function App() {
  const idRef = useRef("");

  // 렌더링이 될 때
  useEffect(() => {
    idRef.current.focus();
  }, []);

  return (
    <>
      <div>
        아이디 : <input type="text" ref={idRef} />
      </div>
      <div>
        비밀번호 : <input type="password" />
      </div>
    </>
  );
}

export default App;

// 아이디가 10자리 입력되면 자동으로 비밀번호 필드로 이동하도록.
// `id.length ≥ 10` 이 로직을 useEffect 안에 넣었는지 곰곰히 생각!
(힌트 : 배치 업데이트)

import { useEffect, useRef, useState } from "react";
import "./App.css";

function App() {
  const idRef = useRef("");
  const pwRef = useRef("");

  const [id, setId] = useState("");

  const onIdChangeHandler = (event) => {
    setId(event.target.value);
  };

  // 렌더링이 될 때
  useEffect(() => {
    idRef.current.focus();
  }, []);

  // 왜 useEffect 안에 놓았을까요?
  useEffect(() => {
    if (id.length >= 10) {
      pwRef.current.focus();
    }
  }, [id]);

  return (
    <>
      <div>
        아이디 :
        <input
          type="text"
          ref={idRef}
          value={id}
          onChange={onIdChangeHandler}
        />
      </div>
      <div>
        비밀번호 : <input type="password" ref={pwRef} />
      </div>
    </>
  );
}

export default App;
import {useRef, useEffect} from 'react'

// 1. ref는 값을 저장하다가 리렌더링 되면 한꺼번에 보여준다.
const countRef = useRef(0)
const countHandler = () => countRef.current++

// 2. DOM요소를 선택할 수 있다.
const countRef2 = useRef("")
const useEffect(() => {
    countRef2.current.focus()
}, [])

return (
    <>
    <button onClick = {countHandler}> ref 증가</button>
    <input type = "text" ref = {countRef2}/>
    </>
)


8. useContext

// #1. context > FamilyContext.js 생성

import { createContext } from "react";

// 여기서 null이 의미하는 것은 무엇일까요?
export const FamilyContext = createContext(null);
//#2. GrandFather.jsx 수정

import React from "react";
import Father from "./Father";
import { FamilyContext } from "../context/FamilyContext";

function GrandFather() {
  const houseName = "스파르타";
  const pocketMoney = 10000;

  return (
    <FamilyContext.Provider value={{ houseName, pocketMoney }}>
      <Father />
    </FamilyContext.Provider>
  );
}

export default GrandFather;
#3. Father.jsx 수정(props를 제거해요!)
import React from "react";
import Child from "./Child";

function Father() {
  return <Child />;
}

export default Father;
#4. Child.jsx 수정
import React, { useContext } from "react";
import { FamilyContext } from "../context/FamilyContext";

function Child({ houseName, pocketMoney }) {
  const stressedWord = {
    color: "red",
    fontWeight: "900",
  };

  const data = useContext(FamilyContext);
  console.log("data", data);

  return (
    <div>
      나는 이 집안의 막내에요.
      <br />
      할아버지가 우리 집 이름은 <span style={stressedWord}>{data.houseName}</span>
      라고 하셨어요.
      <br />
      게다가 용돈도 <span style={stressedWord}>{data.pocketMoney}</span>원만큼이나
      주셨답니다.
    </div>
  );
}

export default Child;
// GrandFather → Context(중앙 관리소) → Child 순서로 전달!
object를 이용.

<span style={stressedWord}>{data.houseName}</span>
<span style={stressedWord}>{data.pocketMoney}</span>

## 주의해야 할 사항

- 렌더링 문제
    
    useContext를 사용할 때, Provider에서 제공한 value가 달라진다면 useContext를 사용하고 있는 모든 컴포넌트가 리렌더링 됩니다. 따라서 value 부분을 항상 신경써줘야 해요!
    
    이후에 배우게 될 메모이제이션이 그 키가 되겠네요 🙂

9. React Hooks - 최적화(React.memo, useCallback, useMemo)

// App.jsx

import React, { useState } from "react";
import Box1 from "./components/Box1";
import Box2 from "./components/Box2";
import Box3 from "./components/Box3";

const boxesStyle = {
  display: "flex",
  marginTop: "10px",
};

function App() {
  console.log("App 컴포넌트가 렌더링되었습니다!");

  const [count, setCount] = useState(0);

  // 1을 증가시키는 함수
  const onPlusButtonClickHandler = () => {
    setCount(count + 1);
  };

  // 1을 감소시키는 함수
  const onMinusButtonClickHandler = () => {
    setCount(count - 1);
  };

  return (
    <>
      <h3>카운트 예제입니다!</h3>
      <p>현재 카운트 : {count}</p>
      <button onClick={onPlusButtonClickHandler}>+</button>
      <button onClick={onMinusButtonClickHandler}>-</button>
      <div style={boxesStyle}>
        <Box1 />
        <Box2 />
        <Box3 />
      </div>
    </>
  );
}

export default App;
Box1.jsx
import React from "react";

const boxStyle = {
  width: "100px",
  height: "100px",
  backgroundColor: "#91c49f",
  color: "white",

  // 가운데 정렬 3종세트
  display: "flex",
  justifyContent: "center",
  alignItems: "center",
};

function Box1() {
  console.log("Box1이 렌더링되었습니다.");
  return <div style={boxStyle}>Box1</div>;
}

export default Box1;
 memo를 통해 해결해보기
우리는 정말 간단히 React.memo를 이용해서 컴포넌트를 메모리에 저장해두고 필요할 때 갖다 쓰게 됩니다. 이렇게 하면 부모 컴포넌트의 state의 변경으로 인해 props가 변경이 일어나지 않는 한 컴포넌트는 리렌더링 되지 않아요. 이것을 컴포넌트 memoization 이라고 합니다.
한번 해볼게요!

Box1.jsx, Box2.jsx, Box3.jsx 모두 동일
export default React.memo(Box1);
export default React.memo(Box2);
export default React.memo(Box3);

10. useCallback

App.jsx
...

// count를 초기화해주는 함수
const initCount = useCallback(() => {
  console.log(`[COUNT 변경] ${count}에서 0으로 변경되었습니다.`);
  setCount(0);
}, [count]);

...

11. useMemo

App.jsx
import "./App.css";
import HeavyComponent from "./components/HeavyComponent";

function App() {
  const navStyleObj = {
    backgroundColor: "yellow",
    marginBottom: "30px",
  };

  const footerStyleObj = {
    backgroundColor: "green",
    marginTop: "30px",
  };

  return (
    <>
      <nav style={navStyleObj}>네비게이션 바</nav>
      <HeavyComponent />
      <footer style={footerStyleObj}>푸터 영역이에요</footer>
    </>
  );
}

export default App;
components > HeavyComponent.jsx
import React, { useState, useMemo } from "react";

function HeavyButton() {
  const [count, setCount] = useState(0);

  const heavyWork = () => {
    for (let i = 0; i < 1000000000; i++) {}
    return 100;
  };

	// CASE 1 : useMemo를 사용하지 않았을 때
  const value = heavyWork();

	// CASE 2 : useMemo를 사용했을 때
  // const value = useMemo(() => heavyWork(), []);

  return (
    <>
      <p>나는 {value}을 가져오는 엄청 무거운 작업을 하는 컴포넌트야!</p>
      <button
        onClick={() => {
          setCount(count + 1);
        }}
      >
        누르면 아래 count가 올라가요!
      </button>
      <br />
      {count}
    </>
  );
}

export default HeavyButton;
 useMemo를 활용할 수 있어요!
const me = useMemo(() => {
  return {
    name: "Ted Chang",
    age: 21,
    isAlive: isAlive ? "생존" : "사망",
  };
}, [isAlive]);

- **주의해야 할 사항**
    
    useMemo를 남발하게 되면 별도의 메모리 확보를 너무나 많이 하게 되기 때문에 오히려 성능이 악화될 수 있습니다. 필요할 때만 쓰기로 합시다 🙂


✅커스텀 Hook 만들기
비슷한 기능을 공유할 경우, 커스텀 Hook으로 작성하여
컴포넌트를 깔끔하게 분리해서 재사용할 수 있습니다.

 

[출처] https://w00sik.github.io/posts/study02/