React 의 hooks

kirin.log·2021년 7월 8일
0
post-custom-banner

리액트(React)에서 제공하는 여러 hooks.
👉 기본 hook: useState(), useEffect(), useContext()
👉 추가 hook: useReducer(), useMemo(), useCallback(), useRef()

❗ 함수형 컴포넌트에서 사용(not Class)


👆 useState()

  • 함수형 컴포넌트에서도 가변적인 상태를 지닐 수 있게 해준다. 해당 함수의 인자에는 상태의 기본값이 들어간다.

💡 상태 유지 값(state)과 그 값을 갱신하는 함수(setState)를 반환.
최초로 렌더링을 하는 동안 state가 읽히고, state를 갱신할 때 setState함수가 사용된다.

  • 리렌더링(re-rendering) 시에 useState를 통해 반환받은 첫 번째 값은, 항상 갱신된 최신 state가 된다.
  • state값이 바뀔 때마다 재렌더링이 일어난다 (불필요한 재렌더링을 막기위해서는 useMemo를 활용하는 방법이 있다.)
import React from 'react';

const Home = () => {
  let [count, setCount] = useState(0);
 
  return (
    <div>
        <p>You clicked {count} times</p>
        <button onClick={() => {setCount(count+ 1)} >
          Click me
        </button>
      </div>
  );
};

export default Home;

👆 useEffect()

  • 컴포넌트가 렌더링될 때마다 특정 작업을 수행하도록 설정할 수 있음.
  • 기본 형태: useEffect(function, deps) >> 첫 번째 인자는 수행하고자 하는 작업, 두 번째 인자는 배열 형태이며, 배열 안에는 검사하고자 하는 특정 값 또는 빈 배열이 올 수 있다(state값 넣어주기)
  • 컴포넌트가 렌더링 이후에 생성, 업데이트, 종료 되는 등, 컴포넌트의 생명주기를 관리하는 hook.
    (기본적으로 첫번째 렌더링과 이후의 모든 업데이트에서 수행)
    ❗ Class 형의 경우, componentDidMount(), componentDidUpdate(), componentWillUnmount() 와 같은 역할을 한다.
import React from 'react';

const Home = () => {
  let [count, setCount] = useState(0);
 
  useEffect(() => {
  	document.title = `you clicked ${count} times`;
    // 이는 맨 첫 번째 렌더링은 물론 그 이후의 모든 렌더링에 똑같이 적용된다.
  }

  return (
    <div>
        <p>You clicked {count} times</p>
        <button onClick={() => {setCount(count+ 1)} >
          Click me
        </button>
      </div>
  );
};

export default Home;



// 컴포넌트가 종료될 때는 return을 활용한다
import React from 'react';

const Home = () => {

  const [time, setTime] = useState(new Date()); 

  useEffect(() => {
    const timer = setTimeout(setTime(new Date(), 1000));
    return () => { clearTimeout(timer) };   // timer 제거 (return으로)
  }, [time]); 
 
  return (
  	<div>
    	<h2>{time.toLocaleString()}</h2>
    </div>
  );
};
export default Home;

🍎 useEffect(function, deps) 의 두 번째 인자는 컴포넌트가 렌더링 될 때 효율적으로 관리할 수 있다.

  • deps에 특정 값을 넣게 된다면 컴포넌트가 처음 마운트 될 때, 지정한 값이 바뀔 때, 언마운트 될 때, 값이 바뀌기 직전에 모두 호출이 된다.
  • useEffect 안에서 사용하는 상태나 props가 있다면 useEffect의 deps에 넣어주어야 한다. 만약 사용하는 값(state)을 넣어주지 않는다면, useEffect안의 함수가 실행될 때 최신상태 props를 가리키지 않는다.
let [count, setCount] = useState(0);

useEffect(() => {
  console.log('컴포넌트가 마운트 될 때만 실행된다');
})
// deps 파라미터를 생략한다면, 컴포넌트가 리렌더링 될 때마다 useEffect함수가 실행된다

useEffect(() => {
  console.log('컴포넌트가 처음 렌더링 될 때만 실행된다');
}, [])
// 빈 배열 넣어주기

useEffect(() => {
  console.log('컴포넌트가 count state가 실행 될 때만 실행된다');
}, [count])

👆 useContext()

  • state값자식 컴포넌트에서 props 없이 글로벌하게 사용할 수 있도록 도와주는 hook.
    (reducer와 같은 역할을 한다)
  • useContext를 사용하면 하위 컴포넌트에서도 상위 컴포넌트에서 전달하는 값을 공유 받을 수 있다.
    (props를 통해서 전달하지 않고 동일한 Context를 넘겨 받은 인자를 통해서 공유가 가능)
    • context 생성: createContext() >> 부모 컴포넌트에서 생성
    • context 조회: useContext() >> 자식 컴포넌트에서 조회

⭕ 사용방법 ---------------------------------------------------------------

(1) [부모 컴포넌트] createContext()를 실행 및 상단 import 하기

import { createContext, useState } from 'react';   // 상단에 import

const Parent = () => {
 
  // state값 설정 (user 변수 안에 담김)
  const user = {
    nickname: 'danuel',
    isAdmin: true
  }
 
  // createContext선언(countContext변수에 담는다)
  const countContext = createContext(user)  // 전달하려는 state값을 인자에 담는다  
 
  return ();
};
export default Parent;

(2) return 문 안에서 state값을 props로 전달하고 싶은 컴포넌트에 감싸준다(value에 state값 전달하기)

  • createContext()를 담은 "변수명.Provider" 로 감싸주기
import { createContext } from 'react';  

const Home = () => {
 
  const user = {
    nickname: 'danuel',
    isAdmin: true
  }

  const userContext = createContext(user)
 
  return (
    <userContext.Provider value={user}>  // userContext.Provider로 감싸준다 (state값 전달)
    	{Children}
    <userContext.Provider>
  );
};
export default Home;

(3) [자식 컴포넌트] useContext를 이용해서 state값 전달받기

import { createContext } from 'react';  

const Children = () => {
 
  // useContext()함수를 통해 값을 받아온다. 인자에는 createContext가 담긴 변수명을 담는다.
  const count = useContext(countContext)  // count라는변수에 담는다
 
  return (
    {count}
  );
};
export default Children;

👆 useReducer()

  • useState의 대체 함수 이다. useState보다 더 다양한 컴포넌트 상황에 따라 다양한 상태를 다른 값으로 업데이트 해주고 싶을 때 사용함.
  • 주요 상태(state)를 업데이트 할 때 사용할 수 있다. 현재 상태, 업데이트를 위해 필요한 정보를 담은 액션 값을 전달받아 새로운 상태를 반환하는 함수.
    반드시 불변성을 지켜줘야 함.

reducer 란, 현재 상태(state)와 액션 객체(action)를 파라미터로 받아와서 새로운 상태를 반환해주는 함수이다.

function reducer(state, action) {
  // 새로운 상태를 만드는 로직
  // const setState = ...
  return setState;  // << 새로운 상태
}

-----

// 증가/감소 변경 로직을 적용한 예시
function reducer(state, action) {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1;
    case 'DECREMENT':
      return state - 1;
    default:
      return state;
  }
}
  • 첫번째 인자로 reducer 함수를 받고, 두번째 인자로 해당 리듀서의 기본값(initialState)을 넣어준다.
const [state, dispatch] = useReducer(reducer, initialState);

// 여기서 state 는 앞으로 컴포넌트에서 사용 할 수 있는 상태를 가리키게 되고, 
// dispatch 는 액션을 발생시키는 함수

-----

// 위에서 만든 reducer함수를 적용한 예시
import React, { useReducer } from 'react';

function Counter() {
  const [number, dispatch] = useReducer(reducer, 0);  // reducer함수를 넣어준다. 초기값은 0
  // useState가 아니라 useReducer를 통해 state값을 지정하고 reducer(action, dispatch)를 적용할 수 있다

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

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

  return (
    <div>
      <h1>{number}</h1>  // state값
      <button onClick={onIncrease}>+1</button>
      <button onClick={onDecrease}>-1</button>
    </div>
  );
}
  • 이 Hook은 배열을 리턴하고, 첫번째 인자가 상태고, 두번째 인자가 dispatch 함수인 길이가 2인 배열을 리턴한다.

❗❗❗ Memoization

memoization이란 기존에 수행한 연산의 결과값을 어딘가에 저장해두고 동일한 입력이 들어오면 재활용하는 프로그래밍 기법.
memoization을 절적히 적용하면 중복 연산을 피할 수 있기 때문에 메모리를 조금 더 쓰더라도 애플리케이션의 성능을 최적화할 수 있다.
👉 useMemo(), useCallback()

👆 useMemo()

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

  • 함수형 컴포넌트 내부에서 발생하는 연산을 최적화(성능 최적화)할 수 있다.
  • 렌더링하는 과정에서 특정 값이 바뀌었을 때만 연산을 실행하고, 원하는 값이 바뀌지 않았다면 이전에 연산했던 결과를 다시 사용하는 방식이다.
  • useMemo 는 2개의 인자를 받는다.
    첫번째 파라미터에는 어떻게 연산할지 정의하는 함수를 넣어주고, (결과값을 생성해주는 팩토리 함수)
    두번째 파라미터에는 deps 배열을 넣어주면 되는데, 이 배열 안에 넣은 내용이 바뀌면, 첫번째 파라미터에 등록한 함수를 호출해서 값을 연산해주고, 만약에 내용이 바뀌지 않았다면 이전에 연산한 값을 재사용하게 된다. (기존 결과값 재활용 여부의 기준이 되는 입력값 배열)
//App.js

import React, { useState } from "react";
import ShowState from "./ShowState";   // 자식 컴포넌트 import

const App = () => {
   // state 생성
  const [number, setNumber] = useState(0);
  const [text, setText] = useState("");

  const increaseNumber = () => {
    setNumber((prevState) => prevState + 1);
  };

  const decreaseNumber = () => {
    setNumber((prevState) => prevState - 1);
  };

  const onChangeTextHandler=(e)=>{
    setText(e.target.value);
  }

  return (
    <div className="App">
      <div>
        <button onClick={increaseNumber}>+</button>
        <button onClick={decreaseNumber}>-</button>
        <input
          type="text"
          placeholder="Last Name"
          onChange={onChangeTextHandler}
        />
      </div>
      <ShowState number={number} text={text} />  // 자식 컴포넌트에 state값 넘겨주기
    </div>
  );
};

export default App;



// ShowState.js

import React from "react";
import "./styles.css";

const getNumber = (number) => {
  console.log("숫자가 변동되었습니다.");
  return number;
};

const getText = (text) => {
  console.log("글자가 변동되었습니다.");
  return text;
};

const ShowState = ({ number, text }) => {
  const showNumber = getNumber(number);
  const showText = getText(text);

  return (
    <div className="info-wrapper">
      {showNumber} <br />
      {showText}
    </div>
  );
};

export default ShowState;

App.js에서 +,- 버튼을 눌러 number 값을 바꾸거나 input창에서 text를 변경할 때마다 state값의 변경이 일어나고, 그럴 때마다 자식 컴포넌트의 값도 변경된다. (불필요한 렌더링)
이런 불필요한 렌더링을 막기 위해, ShowState 컴포넌트 내에서 useMemo를 사용함으로써 개선할 수 있다.

//ShowState.js

import React,{useMemo} from "react";
import "./styles.css";

const getNumber = (number) => {
  console.log("숫자가 변동되었습니다.");
  return number;
};

const getText = (text) => {
  console.log("글자가 변동되었습니다.");
  return text;
};

const ShowState = ({ number, text }) => {
  const showNumber = useMemo(()=>getNumber(number),[number]);   // useMemo 적용
  const showText = useMemo(()=>getText(text),[text]);    // useMemo 적용

  return (
    <div className="info-wrapper">
      {showNumber} <br />
      {showText}
    </div>
  );
};

export default ShowState;

실제 App.js에서 number값과 text값이 변동될 때만 재렌더링 되고, 그 외 다른 값들의 변경에는 반응하지 않는다.


👆 useCallback()

const memoizedCallback = useCallback( () => { doSomething(a, b); }, [a, b] );

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

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

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

//App.js

import React, { useState } from "react"
import List from "./List"       // 자식 컴포넌트 import

function App() {
  // state 생성
  const [number, setNumber] = useState(1)
  const [dark, setDark] = useState(false)

  const getItems = useCallback(() => {          // useCallback 적용
    return [number, number + 1, number + 2]
  }, [number])

  const theme = {
    backgroundColor: dark ? "#333" : "#fff",
    color: dark ? "#fff" : "#333",
  }

  return (
    <div style={theme}>     
      <input
        type="number"
        value={number}
        onChange={e => setNumber(parseInt(e.target.value))}
      />
      <button onClick={() => setDark(prevDark => !prevDark)}>테마 변경</button>
      <List getItems={getItems} />   // 자식 컴포넌트에 값 전달
    </div>
  )
}

export default App



//List.js

import React, { useEffect, useState } from "react"

export default function List({ getItems }) {

  const [items, setItems] = useState([])

  useEffect(() => {
    setItems(getItems())
    console.log("숫자가 변동되었습니다.")
  }, [getItems])

  return items.map((item, index) => <div key={index}>{item}</div>)

👀 useMemo() 와 useCallback() 의 차이점

useMemo(() => fn, deps) / useCallback(fn, deps)
useMemo는 함수를 반환하지 않고, 함수의 값만 memoization해서 반환.
useCallback함수 자체를 memoization 해서 반환.

💡 자식 컴포넌트에서 특정한 props 값들의 변화를 최적화시키고 싶을때는 useMemo를, 부모 컴포넌트에서 계산량이 많은 props함수를 자식 컴포넌트에게 넘겨줄 때는 useCallback을 사용하는 것이 가장 좋은 방법


👆 useRef()

1) 특정 DOM 을 선택해야 하는 상황에서 useRef()를 설정한다

JavaScript 를 사용 할 때, 특정 DOM 을 선택해야 하는 상황에 getElementById, querySelector 같은 DOM Selector 함수를 사용해서 DOM 을 선택한다.
리액트를 사용하는 프로젝트에서도 가끔씩 DOM 을 직접 선택해야 하는 상황이 발생 할 때도 있다. (ex. 특정 element의 크기를 가져오기, 스크롤바 위치를 가져오기(설정하기), 포커스를 설정하기 등)
그럴 땐, 리액트에서 ref 라는 것을 사용한다.

함수형 컴포넌트에서 ref 를 사용 할 때에는 useRef 라는 Hook 함수를 사용한다.
(클래스형 컴포넌트에서는 콜백 함수를 사용하거나 React.createRef 라는 함수를 사용)

  • useRef 함수는 current 속성을 가지고 있는 객체를 반환.
    (Ref 객체의 current값은 선택하고자 하는 DOM을 가리킨다)
  • useRef를 통해 만든 객체 안의 current 값이 실제 엘리먼트를 가리킨다.
import React, { useState, useRef } from 'react';

function InputSample() {

// input의 기본(초기) state값 설정
  const [inputs, setInputs] = useState({
    name: '',
    nickname: ''
  });

// useRef()객체 설정
  const nameInput = useRef();    // nameInput값을 useRef()객체에 담아준다.
  // nameInput값 = current값

// onReset함수 설정 >> nameInput의 .current에 focus() 설정하는 함수  (초기화 하는 기능)
  const onReset = () => {
    setInputs({    // 변경함수가 실행될 때 값이 모두 초기화 된다
      name: '',
      nickname: ''
    });
    nameInput.current.focus();   // onReset함수가 실행되면 초기화 되면서 nameInput에 포커스가 맞춰짐
  };

  const { name, nickname } = inputs; // 비구조화 할당을 통해 값 추출

  return (
    <div>
      <input
        name="name"
        placeholder="이름"
        onChange={onChange}
        value={name}
        ref={nameInput}   // ref값으로 설정해준다. 
      />

      <button onClick={onReset}>초기화</button>
  );
};
export default InputSample;

2) useRef hook은 DOM선택 용도 외에도, 컴포넌트 안에서 조회/수정 가능한 변수를 관리하는 용도가 있다.
useRef로 변수를 관리하게 될 때, useRef의 current 속성은 값을 변경해도 상태를 변경할 때 처럼 React 컴포넌트가 다시 랜더링되지 않는다.
즉, 그 변수가 업데이트 된다고 해서 컴포넌트가 리렌더링 되지 않는다. (굳이 리렌더링 할 필요가 없는 변수라면 useRef로 관리해주는 것이 효율적이다.

const interval = useRef();  // useRef 객체를 생성 후 interval 변수에 담는다.const로 변수를 설정했다 하더라도, useRef는 객체이기 때문에 값이 바뀔 수 있다.


useEffect(() => {
    interval.current = setInteral(handleChange, 100);  // ref객체의 current에 setInterval함수를 넣어준다.

    return () => {
      clearInterval(interval.current)
    }
    // 컴포넌트가 unmount될 때, clearInterval함수를 활용해서 setInterval함수가 들어있는
    // ref객체를 초기화해준다.
})
profile
boma91@gmail.com
post-custom-banner

0개의 댓글