[TIL, React] React Hooks 복습하기

25979 단어 ReactReact

리액트를 다루는 기술 8장을 복습하며 글을 쓴다.

1. UseState, SetState

상태 값 관리

Counter.js

import React, { useState } from "react";
const Counter = () => {
  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로 state값의 초기값을 설정한다.
    useState는 반드시 객체가 아니어도 상관 없다.
    useState는 여러번 사용될 수 있다.
    함수를 호출하면 배열이 반환되는데, 배열의 첫 번째 원소는 현재 상태이고, 두 번째 상태는 원소의 상태를 바꿔주는 함수이다.

  • state의 값은 세터함수, setState를 사용해 바꿔준다.
    이 때, 불변성을 지키기 위해 배열이나 객체 값을 업데이트 할 대는,
    객체의 사본
    을 만들고 그 사본에 값을 업데이트 한 후, 그 사본의 상태를 setState또는 useState를 통해 전달받은 세터함수를 통해 업데이트 한다.

  • 객체에 대한 사본을 만들 때는 spread 연산자 [...] 를 이용해 처리한다.

  • 배열에 대한 사본을 만들 때는 concat 같은 배열의 내장함수를 이용해 처리한다.

  • state는 부모 -> 자식으로 단 방향적 흐름을 갖는데, 자식컴포넌트에서 부모의 state 값을 바꾸고 싶은 경우에는 props에 setState를 내려줘서 처리한다.

2. UseEffect

컴포넌트가 랜더링 될 때 특정 작업 실행

const Info = () => {
  const [name, setName] = useState("");
  const [nickname, setNickname] = useState("");
  useEffect(() => {
    console.log("랜더링 완료.");
    console.log("name, nickname -->", 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} />
      </div>
      <div>
        <div>
          <b>이름 :</b> {name}
        </div>
        <div>
          <b>별명 :</b> {nickname}
        </div>
      </div>
    </>
  );
};

핵심 개념

  • useEffect는 리액트 컴포넌트가 랜더링 될 때마다 특정 작업을 수행시키는 hook.

  • 실행 시점은 랜더링되고 난 직후 마다, 의존값으로 설정한 값이 바뀌고 난 직후 마다 싱행된다.

  • 컴포넌트가 마운트 될 때(최초 랜더링 시)만 실행하고 싶을 때는, 함수의 두 번째 파라미터로 빈 배열을 넣으면 된다.

 useEffect(() => {
    console.log("랜더링 완료.");
    console.log("name, nickname -->", name, nickname);
  },[]);
  • 특정 값이 업데이트 될 때에만 실행하고 싶을 때는, 함수의 두 번째 파라미터로 전달되는 배열안에 검사하고 싶은 값을 넣어주면 된다.

  • 뒷정리하기, 컴포넌트가 언마운트 되기 전이나 의존 값이 업데이트 되기 직전에 어떤 작업을 수행하고 싶다면 useEffect에서 뒷정리(clean up) 함수를 반환해 주어야 한다.

 useEffect(() => {
    console.log("랜더링 완료.");
    console.log("name, nickname -->", name, nickname);
    return () =>{
    console.log('clean up');
    console.log(name);
    };
  },[name]);
  • 오직 언마운트 될 때만 뒷정리 함수를 호출하고 싶다면 useEffect 함수의 두 번째 파라미터에 빈 배열을 넣어주면 된다.
    그리고, 뒷정리 함수는 함수가 호출 될 때 업데이트 되기 직전의 값을 보여준다.
 useEffect(() => {
    console.log("effect");
    return () =>{
    console.log('unmount');
    };
  },[]);

* 프로젝트에 useEffect를 사용해보니,

원하는 dependency를 설정해서 useEffect를 사용하는것 보다,
필요한 곳에 해당 로직을 넣는게 나은 것 같다.

useEffect를 사용하면 코드리뷰를 할 때
사용한 의도가 무엇인지, 어떻게 작동하게 되는지, 구구절절 설명해야 했다.
해당 컴포넌트에 들어간 모든 로직을 이해해야 해당 useEffect를 이해할 수 있기 때문에
협업할 때 지장이 생겼다.

생각보다 dependency 설정도 까다로웠고,
랜더링때마다 호출되다 보니 sideEffect가 필연적 발생하게 되는 것 같다.

그래서 앞으로는 useEffect를 최대한 지양해서 쓰려고 한다.
단, 렌더링 할 때마다 로그를 찍는 디버깅용으로는 자주 사용할 것 같다.

3. useReducer

const reducer = (state, action) => {
  // action.type에 따라 다른 작업을 수행함.
  switch (action.type) {
    case "INCRESEMENT":
      return { value: state.value + 1 };
    case "DECRESEMENT":
      return { value: state.value - 1 };
    default:
      // 아무것도 해당되지 않을 때 기존 상태 반환
      return state;
  }
};
const Counter = () => {
  const [state, dispatch] = useReducer(reducer, { value: 0 });
  return (
    <div>
      <p>
        현재 카운터 값은 <b>{state.value}</b>입니다.
      </p>
      <button onClick={() => dispatch({ type: "INCRESEMENT" })}> +1 </button>
      <button onClick={() => dispatch({ type: "DECRESEMENT" })}> -1 </button>
    </div>
  );
};

핵심개념

  • useReducer는 useState보다 더 다양한 컴포넌트 상황에 따라 다양한 상태를 다른 값으로 업데이트 해주고 싶을 때 사용하는 Hook.
    개인적으로 state가 지역 변수라면, reducer는 전역 변수 제어라고 이해했다.

  • 리듀서는 현재 상태, 그리고 업데이트를 위해 필요한 정보를 담은 액션(action) 값을 전달받아 새로운 상태를 반환하는 함수다. 새로운 상태를 만들 때는 반드시 불변성을 지켜주어야 한다.

  • useReducer의 첫 번째 파라미터에는 리듀서 함수를 넣고, 두 번째 파라미터에는 해당 리듀서의 기본값을 넣어준다.

  • 이 hook을 사용하면 state값과 dispatch함수를 받아온다. state는 현재 가리키고 있는 상태고, dispatch는 액션을 발생시키는 함수다.

  • dispatch(action) 과 같은 형태로, 함수 안에 파라미터로 액션 값을 넣어주면 리듀서 함수가 호출되는 구조다.

  • useReducer를 사용했을 때의 가장 큰 장점은 컴포넌트 업데이트 로직을 컴포넌트 바깥으로 뺄 수 있다는 것이다.

리듀서로 input 상태 관리하기

const 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>
        <input name="name" value={name} onChange={onChange} />
        <input name="nickname" value={nickname} onChange={onChange} />
      </div>
      <div>
        <div>
          <b>이름 :</b> {name}
        </div>
        <div>
          <b>별명 :</b> {nickname}
        </div>
      </div>
    </>
  );
};
  • useReducer에서의 액션은 그 어떤 값도 사용 가능하다. 그래서 이번에는 이벤트 객체가 지니고 있는 e.target의 값 자체를 액션값으로 사용했다.

4. useMemo

import React, { useMemo, useState } 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 = (e) => {
    setNumber(e.target.value);
  };

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

  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;

핵심요약

  • useMemo는 렌더링 하는 과정에서 특정 값이 바뀌었을 때만 실행하도록 연산 실행 시점에 대해 조건을 준다. 만약 dependency 로 설정한 값이 바뀌지 않았다면 이전에 연산했던 결과를 다시 사용하는 방식이다.

5. useCallback

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(
    (e) => {
      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>평균값 : </b> {avg}
      </div>
    </div>
  );
};
export default Average;

핵심요약

  • useCallback은 useMemo와 상당히 비슷한 함수로, 주로 렌더링 성능을 최적화해야 하는 상황에서 사용한다. 이 Hook을 사용하면 만들어 놨던 함수를 재사용 할 수 있다.

  • 컴포넌트의 렌더링이 자주 발생하거나 렌더링해야 할 컴포넌트의 개수가 많아지면 최적화 해주는 것이 좋다.

  • useCallback의 첫 번째 파라미터에는 생성하고 싶은 함수를 넣고, 두 번째 파라미터에는 배열을 넣으면 된다.

  • 이 배열에는 어떤 값이 바뀌었을 때 함수를 새로 생성해야 하는지 명시해야 한다.

  • onChange 처럼 비어있는 배열을 넣게 되면 컴포넌트가 렌더링될 때 만들었던 함수를 계속해서 재사용하게 된다. dependency를 설정하게 되면 해당 dependency의 변경에 따라 새로 만들어진 함수를 사용하게 된다.

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

  • useCallback 은 결국 useMemo 에서 함수를 반환하는 상황에서 더 편하게 사용 할 수 있는 Hook 이다. 숫자, 문자열, 객체 처럼 일반 값을 재사용하기 위해서는 useMemo 를, 그리고 함수를 재사용 하기 위해서는 useCallback 을 사용할 것.

6. useRef


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 inputEl = useRef(null);

  const onChange = useCallback((e) => {
    setNumber(e.target.value);
  }); // 컴포넌트가 처음 렌더링 될 때만 함수 생성

  const onInsert = useCallback(
    (e) => {
      const nextList = list.concat(parseInt(number));
      setList(nextList);
      setNumber("");
      inputEl.current.focus();
    },
    [number, list]
  ); // number 혹은 list 가 바뀌었을 때만 함수 생성

  const avg = useMemo(() => getAverage(list), [list]);

  return (
    <div>
      <input value={number} onChange={onChange} ref={inputEl} />
      <button onClick={onInsert}>등록</button>
      <ul>
        {list.map((value, index) => (
          <li key={index}>{value}</li>
        ))}
      </ul>
      <div>
        <b>평균값 : </b> {avg}
      </div>
    </div>
  );
};

export default Average;

핵심요약

  • useRef Hook은 함수형 컴포넌트에서 ref를 쉽게 사용할 수 있게 한다.

  • useRef를 사용하여 ref를 설정하면 useRef를 통해 만든 객체 안의 current 값이 실제 엘리먼트를 가리키게 된다.

useRef 로컬 변수 사용하기

import React, { useRef } from "react";

const RefSample = () => {
  const id = useRef(1);
  const setId = (n) => {
    id.current = n;
  };
  const printId = () => {
    console.log(id.current);
  };
  return <div>RefSample</div>;
};

export default RefSample;

컴포넌트 로컬 변수를 사용해야 할 때도 useRef를 활용할 수 있다.

여기서 로컬 변수란 렌더링과 상관없이 바뀔 수 있는 값을 의미한다.

이렇게 ref안의 값이 바뀌어도 컴포넌트가 렌더링 되지 않는다는 점은 주의해야 한다.

렌더링과 관련되지 않은 값을 관리할 때만 이러한 방식으로 코드를 작성한다.

좋은 웹페이지 즐겨찾기