[TIL] 23.05.08

Minkyu Shin·2023년 5월 8일
0

TIL

목록 보기
24/44
post-thumbnail

오늘의 나는 무엇을 잘했을까?

오랜만에 제 시간에 맞춰 학습을 시작한 것 같다. 항상 공부하기 전 준비 시간이 필요했고, 어쩌다 보면 한시간이 훅 가 있었는데 앞으로도 미리 학습할 준비를 하고 정해진 시간이 되면 바로 공부를 진행할 수 있도록 해서 시간을 효율적으로 사용하도록 노력해야겠다.

오늘의 나는 무엇을 배웠을까?

useEffect 정리하기

useEffect 는 컴포넌트의 마운트, 업데이트, 언마운트라는 생명주기(Life Cycle)를 제어하기 위한 리액트 Hook이다. 생명주기의 각 단계에 특정 작업을 처리해 주기 위해 사용한다.
코드 예시를 통해 알아보자

마운트와 언마운트 관리

import { useEffect } from "react";

const User = ({ user, onRemove, onToggle }) => {
  useEffect (() => {
    console.log("컴포넌트가 화면에 나타남");
    return (() => {
      console.log("컴포넌트가 화면에서 사라짐");
    };
}, []);
  
  return (
    <div>
      <b onClick={() => onToggle(user.id)}>{user.username}</b>
      &nbsp;
      <span>({user.email})</span>
      <button onClick={() => onRemove(user.id)}>삭제</button>
    </div>
  );
}

const UserList = ({ users, onRemove, onToggle }) =>  {
  return (
    <div>
      {users.map(user => (
        <User
          user={user}
          key={user.id}
          onRemove={onRemove}
          onToggle={onToggle}
        />
      ))}
    </div>
  );
}

export default UserList;

useEffect 는 첫번째 매개변수로 함수, 두번째 파라미터로 의존값이 들어있는 배열(dependencies, 이하 deps)을 받는다. deps가 빈 배열이라면 컴포넌트가 처음 나타낼 때만 등록된 함수가 호출된다. 따라서, 위 코드의 함수는 컴포넌트가 처음 렌더링 되었을 때만 호출된다.
useEffect 가 반환하는 함수는 cleanup 함수라고 불리며, useEffect 함수에 대한 뒷정리를 해준다. 만약 deps가 비어 있다면, 컴포넌트가 언마운트 될 때 cleanup 함수가 호출된다.
cleanup 함수의 작동순서는 다음과 같다.

re-rendering => 이전 useEffect의 cleanup => useEffect

위 코드를 통해 화면을 렌더링시키고 하나의 User 컴포넌트를 삭제한 뒤 콘솔 창을 확인해 보면,

위와 같은 결과가 나온다. 세번씩 출력이 되는 것은 User 컴포넌트가 총 세번 사용되기 때문이라 무시해도 좋다. 첫 렌더링 시 useEffect 가 실행되고, User 컴포넌트 하나가 삭제되어 state가 변경되므로 리렌더링된 이후 cleanup 함수가 실행됨을 알 수 있다.

마운트 시 하는 작업들

마운트 시에 주로 하는 작업들에는,

  • props로 받은 값을 컴포넌트의 로컬 상태로 설정
  • 외부 API 요청
  • 라이브러리의 사용
  • setInterval 또는 setTimeout 의 사용

이 있으며

언마운트 시 하는 작업들

언마운트 시에 주로 하는 작업들에는,

  • setInterval 또는 setTimeout 으로 등록한 작업들을 지워주기 ( clearInterval , clearTimeout )
  • 라이브러리 인스턴스 제거

등이 있다.

업데이트 관리

지금까지는, deps가 빈 배열일 경우를 살펴 보았는데 만약 특정 값이 요소로 들어 있으면 어떨까?
deps에 특정 값이 존재한다면 useEffect 는 마운트 될 때와 특정 값이 변화할 때도 호출이 된다. cleanup 함수는 언마운트 될 때와 특정 값이 바뀌기 직전에 호출이 된다.

위의 코드를 아래와 같이 수정하고 결과를 살펴보면,

import { useEffect } from "react";

const User = ({ user, onRemove, onToggle }) => {
  useEffect(() => {
    console.log("user 값이 설정됨");
    console.log(user);
    return () => {
      console.log("user가 바뀌기 전");
      console.log(user);
    };
  }, [user]);
  return (
    <div>
      <b style={{ cursor: "pointer", color: user.active ? "green" : "black" }} onClick={() => onToggle(user.id)}>
        {user.username}
      </b>{" "}
      <span>({user.email})</span>
      <button onClick={() => onRemove(user.id)}>삭제</button>
    </div>
  );
};

const UserList = ({ users, onRemove, onToggle }) => {
  return (
    <div>
      {users.map((user) => (
        <User key={user.id} user={user} onRemove={onRemove} onToggle={onToggle} />
      ))}
    </div>
  );
};

export default UserList;

다음과 같은 결과가 나온다.

또, <b> 태그를 클릭해 active 상태를 변화 시키면

값이 바뀌기 직전 cleanup 함수가 실행되고, 값이 바뀐 후 useEffect 가 실행된 것을 볼 수 있다.
useEffect 내부에서 사용하는 state나 props가 있다면, deps에 추가해 줘야 한다. 만약 추가하지 않았을 경우, useEffect 에 등록한 함수가 실행될 때 최신의 state 또는 props를 가리키지 않게 되기 때문이다.

마지막으로, 만약 deps 매개변수 자체를 생략한다면 어떤 결과가 나올까?
이 때는 컴포넌트가 리렌더링 될 때마다 useEffect 함수가 호출된다. 리액트에서는 state나 props의 변화 또는 부모컴포넌트의 리렌더링이 일어나면 컴포넌트가 리렌더링 되는데 이때마다 계속 호출이 되는 것이다.

useMemo

useMemo 는 성능 최적화를 위해 연산된 값을 재사용할 수 있도록 해주는 Hook이다.
가령 아래와 같이 활성 사용자 수를 계산해 화면에 출력해 주는 함수가 있다고 해보자.

import { useRef, useState } from "react";
import UserList from "./UserList";
import CreateUser from "./CreateUser";

const countActiveUsers = (users) => {
  console.log("활성 사용자를 세는 중..."); // 함수가 호출될 때마다 콘솔에 출력됨 
  return users.filter((user) => user.active).length;
};

function App() {
  const [inputs, setInputs] = useState({
    username: "",
    email: "",
  });
  const { username, email } = inputs;

  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 onChange = (e) => {
    const { name, value } = e.target;
    setInputs({
      ...inputs,
      [name]: value,
    });
  };

  const count = countActiveUsers(users);

  return (
    <>
// ~
      <div>활성사용자 수 : {count}</div>
    </>
  );
}

export default App;

화면이 처음 렌더링 되면, 콘솔에는 "활성 사용자를 세는중" 이 출력되고 계산된 수가 화면에 잘 출력된다. 언뜻 보면 정상적으로 작동되는 것처럼 보이지만 큰 문제가 하나 있다. input 값이 바뀔 때 state가 변경되며 리렌더링이 일어나는데, 불필요한 함수 호출이 일어나 콘솔에 "활성 사용자를 세는중"이 지속적으로 출력 되는 것이다.
이를 useMemo Hook을 통해 최적화 할 수 있다. 여기서 Memo는 Memoized를 말하며 이전에 계산한 값을 재사용한다는 의미를 가지고 있다.

import { useMemo, useRef, useState } from "react";
import UserList from "./UserList";
import CreateUser from "./CreateUser";

const countActiveUsers = (users) => {
  console.log("활성 사용자를 세는 중...");
  return users.filter((user) => user.active).length;
};

function App() {
  const [inputs, setInputs] = useState({
    username: "",
    email: "",
  });
  const { username, email } = inputs;

  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 onChange = (e) => {
    const { name, value } = e.target;
    setInputs({
      ...inputs,
      [name]: value,
    });
  };

// ~
  
  const count = useMemo(() => countActiveUsers(users), [users]);

  return (
    <>
//~
      <div>활성사용자 수 : {count}</div>
    </>
  );
}

export default App;

이전 코드를 위와 같이 수정했다. useMemo 의 첫번째 매개변수로 연산을 정의하는 함수를 넣어주고, 두번째 매개변수로 deps 배열을 넣어주면 된다. deps 배열의 요소가 변경되면 등록한 함수가 호출되어 값을 연산하고, 변경되지 않으면 기존 연산값을 재사용하게 된다. 이제 input 값이 바뀌어도 users의 값은 변경되지 않으므로 함수가 호출되지 않게 된다.

오늘의 나는 어떤 어려움이 있었을까?

여전히 내가 애매모호하게 이해가 되는 부분을 그냥 넘어가는 것이 쉽지 않다. 앞선 WIL에서도 작성했듯이 기능과 그 기능을 활용하면 어떤 결과가 나오는지 정도만 알고 넘어가도 좋을 것 같은데 생각과 다르게 자꾸 깊게 파고 자료를 더 찾아보고 하다보니 진도가 안 나가는 것 같다. 앞으로 공부를 하는 방식을 바꾸기 위해 부단한 노력을 해야겠다고 생각했다.

내일의 나는 무엇을 해야할까?

  • 쿠키, 세션, 로컬 스토리지 이해하기 학습
  • CSS 더 알아보기 학습
  • React Hook 학습
profile
개발자를 지망하는 경영학도

0개의 댓글