๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

React/Velopert's React

[React] Study # 18 | React.memo

๐Ÿ’กReact.memo?

  • ์ปดํฌ๋„ŒํŠธ์˜ props๊ฐ€ ๋ฐ”๋€Œ์ง€ ์•Š์•˜๋‹ค๋ฉด, ๋ฆฌ๋ Œ๋”๋ง์„ ๋ฐฉ์ง€ํ•˜์—ฌ ์ปดํฌ๋„ŒํŠธ์˜ ๋ฆฌ๋ Œ๋”๋ง ์„ฑ๋Šฅ์„ ์ตœ์ ํ™” ์‹œ์ผœ์ฃผ๋Š” ํ•จ์ˆ˜!
  • ์ด ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด, ์ปดํฌ๋„ŒํŠธ์—์„œ ๋ฆฌ๋ Œ๋”๋ง์ด ํ•„์š”ํ•œ ์ƒํ™ฉ์—์„œ๋งŒ ๋ฆฌ๋ Œ๋”๋ง์„ ํ•˜๋„๋ก ์„ค์ •ํ•ด์ค„ ์ˆ˜ ์žˆ๋‹ค.
  • ์‚ฌ์šฉ๋ฒ•์€ ๊ทธ๋ƒฅ ๊ฐ์‹ธ์ฃผ๋ฉด ๋จ!

CreateUser.js ์— ์ ์šฉ

// ์ปดํฌ๋„ŒํŠธ ๋ฆฌ๋ Œ๋”๋ง ๋ฐฉ์ง€๋ฅผ ์œ„ํ•œ React.memo 
export default React.memo(CreateUser);

์™• ์‰ฌ์›€! 

 

UserList.js ์— ์ ์šฉ

import React, {useEffect} from 'react'

// user๋ฆฌ์ŠคํŠธ๋“ค์ด ๋ณด์—ฌ์ง€๋Š” ๋ถ€๋ถ„
const User = React.memo(function User({user, onRemove, onToggle}) {
    return(
        <div>
            <span
                style={{ 
                    cursor: 'pointer',
                    color: user.active ? 'green':'black',
                }}

                onClick={()=>onToggle(user.id)}
                >{user.username}</span>
            &nbsp;
            <span>{user.email}</span>
            <button onClick={()=>onRemove(user.id)}>์‚ญ์ œ</button>
        </div>
    )
});

function UserList({users, onRemove, onToggle}) {

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

export default React.memo(UserList);

-> ์ด๋ ‡๊ฒŒ ์ ์šฉ์„ ๋‹คํ•˜๋ฉด, input ์ˆ˜์ •์„ ํ•  ๋•Œ ํ•˜๋‹จ์˜ UserList๊ฐ€ ๋ฆฌ๋ Œ๋”๋ง ๋˜์ง€ ์•Š๋Š”๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

 

๐Ÿšซํ•˜์ง€๋งŒ UserList์˜ User์ค‘ ํ•˜๋‚˜๋ผ๋„ ์ˆ˜์ •ํ•˜๋ฉด ๋ชจ๋“  User ๋“ค์ด ๋ฆฌ๋ Œ๋”๋ง ๋˜๊ณ , CreateUser(input๊ณผ ๋“ฑ๋ก ๋ฒ„ํŠผ ๋ถ€๋ถ„)๋„ ๋ฆฌ๋ Œ๋”๋ง์ด ๋œ๋‹ค. -> ์ด์œ ๋Š” users ๋ฐฐ์—ด์ด ๋ฐ”๋€” ๋•Œ ๋งˆ๋‹ค onCreate, onToggle, onRemove ๋ชจ๋‘ ์ƒˆ๋กœ ๋งŒ๋“ค์–ด์ง€๊ธฐ ๋–„๋ฌธ์ด๋‹ค.

const onCreate = useCallback(() => {

    const user = {
      id: nextId.current,
      username,
      email
    };
    setUsers(users.concat(user));

    setInputs({
      username: '',
      email: ''
    })
    nextId.current += 1;
},[users, username, email]);

const onRemove = useCallback(id => {
  setUsers(users.filter(user => user.id !== id))
},[users]);

const onToggle = useCallback(id => {
  setUsers(
    users.map(user => 
      user.id === id ? { ...user, active: !user.active} : user
    )
  )
},[users])

-> ์ด๋ ‡๊ฒŒ 3๊ฐœ์˜ ์ด๋ฒคํŠธ ์•ˆ์˜ deps์— users๊ฐ€ ๋“ค์–ด๊ฐ€ ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, ๋ฐฐ์—ด์ด ๋ฐ”๋€” ๋•Œ๋งˆ๋‹ค ํ•จ์ˆ˜๊ฐ€ ์ƒˆ๋กœ ๋งŒ๋“ค์–ด์ง€๋Š” ๊ฒƒ์€ ๋‹น์—ฐํ•˜๋‹ค.

 

๐Ÿ’ก๊ทธ๋Ÿผ ์ด๊ฑธ ์ตœ์ ํ™” ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ์–ด๋–ป๊ฒŒ ํ•ด์•ผํ• ๊นŒ?

  1. deps์—์„œ users๋ฅผ ์ง€์šด๋‹ค.
  2. ํ•จ์ˆ˜๋“ค์—์„œ ํ˜„์žฌ useState๋กœ ๊ด€๋ฆฌํ•˜๋Š” users๋ฅผ ์ฐธ์กฐํ•˜์ง€ ์•Š๊ฒŒ ํ•œ๋‹ค.
  3. 2๋ฒˆ์€ ์–ด๋–ป๊ฒŒ ํ• ๊นŒ? ๋ฐ”๋กœ ํ•จ์ˆ˜ํ˜• ์—…๋ฐ์ดํŠธ๋กœ!

โžฐํ•จ์ˆ˜ํ˜• ์—…๋ฐ์ดํŠธ

  • ํ•จ์ˆ˜ํ˜• ์—…๋ฐ์ดํŠธ๋Š” useState์— ๊ฐ’์„ ๊ทธ๋Œ€๋กœ ์ „๋‹ฌํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ, ํ•จ์ˆ˜๋ฅผ ์ „๋‹ฌํ•˜๋Š” ๊ฒƒ์ด๋‹ค
  • ํ•จ์ˆ˜ํ˜• ์—…๋ฐ์ดํŠธ๋ฅผ ํ•˜๊ฒŒ ๋˜๋ฉด, setUsers์— ๋“ฑ๋กํ•˜๋Š” ์ฝœ๋ฐฑํ•จ์ˆ˜์˜ ํŒŒ๋ผ๋ฏธํ„ฐ์—์„œ ์ตœ์‹  users๋ฅผ ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— deps ์— users๋ฅผ ๋„ฃ์ง€ ์•Š์•„๋„ ๋œ๋‹ค.

 

๊ธฐ์กด์— deps์— ๋ฐฐ์—ด์ด ์žˆ์œผ๋ฉด ๊ทธ ํ•ด๋‹น ๋ฐฐ์—ด์ด ๋ฐ”๋€” ๋•Œ๋งˆ๋‹ค onToggle๋“ฑ์˜ ํ•จ์ˆ˜๊ฐ€ ์•„์˜ˆ ์ƒˆ๋กœ ๋งŒ๋“ค์–ด์ง€๋Š”๋ฐ, ํ•จ์ˆ˜ํ˜• ์—…๋ฐ์ดํŠธ๋ฅผ ํ†ตํ•ด deps๋ฅผ ๋ฐ›์ง€ ์•Š๊ณ  setUsers์˜ ์ตœ์‹  users๋ฅผ ์ฐธ์กฐํ•˜๋Š” ํ•จ์ˆ˜์˜ ํ˜•ํƒœ๋กœ ๋งŒ๋“ ๋‹ค๋ฉด ํ•จ์ˆ˜๊ฐ€ ๋‹ค์‹œ ๋งŒ๋“ค์–ด์ง€์ง€ ์•Š๋Š”๋‹ค..?!

 

 

๊ทธ๋Ÿผ ๊ฐ ํ•จ์ˆ˜๋“ค์„ ์—…๋ฐ์ดํŠธ ํ•ด๋ณด์ž! (onChange์˜ ๊ฒฝ์šฐ์—” ํ•จ์ˆ˜ํ˜• ์—…๋ฐ์ดํŠธ๋ฅผ ํ•ด๋„ ์˜ํ–ฅ์€ ๊ฐ€์ง€ ์•Š์ง€๋งŒ, ์—ฐ์Šต์‚ผ์•„ ํ•ด๋ณด์ž!)

import React,{useRef, useState, useMemo, useCallback} from 'react'; // useCallback ๋ถˆ๋Ÿฌ์˜ค๊ธฐ
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 = useCallback ( e => {
    const {name, value} = e.target;
    // setInputs์— ๋“ฑ๋กํ•˜๋Š” ์ฝœ๋ฐฑํ•จ์ˆ˜ ํŒŒ๋ผ๋ฏธํ„ฐ์—์„œ 
    // ์ตœ์‹  inputs๋ฅผ ์ฐธ์กฐ ํ•  ์ˆ˜ ์žˆ๋‹ค.
    setInputs(inputs => ({
      ...inputs,
      [name]: value
    }))
    // deps ๋น„์šฐ๊ธฐ
  }, []
  );


  const [users, setUsers] = useState(
    [
      {id: 1, username: 'John', email: 'john@example.com',
      active: true,
    },
      {id: 2, username: 'Car', email: 'car@example.com',
      active: false,
    },
      {id: 3, username: 'lee', email: 'lee@example.com',
      active: false,
    },
    ]
)
    

const nextId = useRef(4);

const onCreate = useCallback(() => {
    const user = {
      id: nextId.current,
      username,
      email
    };
    // setUsers์— ๋“ฑ๋กํ•˜๋Š” ์ฝœ๋ฐฑํ•จ์ˆ˜ ํŒŒ๋ผ๋ฏธํ„ฐ์—์„œ ์ตœ์‹  users๋ฅผ ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ๋‹ค.
    setUsers(users => users.concat(user));

    setInputs({
      username: '',
      email: ''
    })
    nextId.current += 1;

    // deps์—์„œ users ์ง€์šฐ๊ธฐ
},[username, email]);

const onRemove = useCallback(id => {
  // setUsers์— ๋“ฑ๋กํ•˜๋Š” ์ฝœ๋ฐฑํ•จ์ˆ˜ ํŒŒ๋ผ๋ฏธํ„ฐ์—์„œ ์ตœ์‹  users๋ฅผ ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ๋‹ค.
  setUsers(users => users.filter(user => user.id !== id))
  // deps์—์„œ users ์ง€์šฐ๊ธฐ
},[]);

const onToggle = useCallback(id => {
  // setUsers์— ๋“ฑ๋กํ•˜๋Š” ์ฝœ๋ฐฑํ•จ์ˆ˜ ํŒŒ๋ผ๋ฏธํ„ฐ์—์„œ ์ตœ์‹  users๋ฅผ ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ๋‹ค.
  setUsers(users =>
    users.map(user => 
      user.id === id ? { ...user, active: !user.active} : user
    )
  )
  // deps์—์„œ users ์ง€์šฐ๊ธฐ
},[])

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;

-> ์ด๋ ‡๊ฒŒ ํ•ด์ฃผ๋ฉด ํŠน์ • ํ•ญ๋ชฉ์„ ์ˆ˜์ •ํ• ๋•Œ, ํ•ด๋‹น ํ•ญ๋ชฉ๋งŒ ๋ Œ๋”๋ง ๋˜๋ฉฐ ์ตœ์ ํ™”๊ฐ€ ๋๋‚œ ๊ฒƒ์ด๋‹ค!

 


์‹ค์ œ ๋ฆฌ์•กํŠธ ๊ฐœ๋ฐœ์‹œ,

๊ณ„์† ๋ฐฐ์› ๋˜ useCallback(ํ•จ์ˆ˜ ์žฌ์‚ฌ์šฉ), useMemo(๊ฐ’ ์žฌ์‚ฌ์šฉ), React.memo(๋ฆฌ๋ Œ๋”๋ง ๋ฐฉ์ง€)๋Š” ์ปดํฌ๋„ŒํŠธ์˜ ์„ฑ๋Šฅ์„ ์‹ค์ œ๋กœ ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ๋Š” ์ƒํ™ฉ์—์„œ๋งŒ ํ•œ๋‹ค.

ex) User ์ปดํฌ๋„ŒํŠธ์˜ b์™€ button์— onClick์œผ๋กœ ์„ค์ •ํ•ด์ค€ ํ•จ์ˆ˜๋“ค์€ ํ•ด๋‹น ํ•จ์ˆ˜๋“ค์„ useCallback์œผ๋กœ ์žฌ์‚ฌ์šฉํ•œ๋‹คํ•ด์„œ ๋ฆฌ๋ Œ๋”๋ง์„ ๋ง‰์„ ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์€ ์•„๋‹ˆ๋ฏ€๋กœ ๊ตณ์ด ๊ทธ๋ ‡๊ฒŒ ํ•  ํ•„์š”๋Š” ์—†๋‹ค.

 

โž• ๋ Œ๋”๋ง์„ ์ตœ์ ํ™” ํ•˜์ง€ ์•Š์„ ์ปดํฌ๋„ŒํŠธ์— React.memo๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์€ ๋ถˆํ•„์š”ํ•œ props ๋น„๊ต๋งŒ ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

 

โž•React.memo์—์„œ ๋‘๋ฒˆ์งธ ํŒŒ๋ผ๋ฏธํ„ฐ์— propsAreEqual ์ด๋ผ๋Š” ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํŠน์ • ๊ฐ’๋“ค๋งŒ ๋น„๊ต๋ฅผ ํ•˜๋Š” ๊ฒƒ๋„ ๊ฐ€๋Šฅํ•˜๋‹ค.

export default React.memo(
	UserList,
    (prevProps, nextProps) => prevProps.users === nextProps.users
);

-> ํ•˜์ง€๋งŒ ์—ฌ๊ธฐ์„œ ์˜๋„์น˜ ์•Š์€ ๋ฒ„๊ทธ๋“ค์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค. 

์˜ˆ๋ฅผ๋“ค์–ด, ํ•จ์ˆ˜ํ˜• ์—…๋ฐ์ดํŠธ๋กœ ์ „ํ™˜์„ ์•ˆํ–ˆ๋Š”๋ฐ ์ด๋ ‡๊ฒŒ users๋งŒ ๋น„๊ต๋ฅผ ํ•˜๊ฒŒ ๋œ๋‹ค๋ฉด onToggle๊ณผ onRemove์—์„œ ์ตœ์‹  users๋ฐฐ์—ด์„ ์ฐธ์กฐํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ์‹ฌ๊ฐํ•œ ์˜ค๋ฅ˜ ๋ฐœ์ƒ!

 


Reference

https://velog.io/@suyeonme/react-useState์˜-๋น„๋™๊ธฐ์ -์†์„ฑ-ํ•จ์ˆ˜ํ˜•-์—…๋ฐ์ดํŠธ