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

โœ๏ธ/React

React ๊ณต๋ถ€ํ•˜๊ธฐ(1) - Hooks ์ข…๋ฅ˜ํŽธ

๐ŸŒน ๋ฒจ๋กœํผํŠธ์™€ ํ•จ๊ป˜ํ•˜๋Š” ๋ชจ๋˜ ๋ฆฌ์•กํŠธ

 

~ for ์„ฑ๋Šฅ ์ตœ์ ํ™” ~

useMemo : ์—ฐ์‚ฐํ•œ ๊ฐ’ ์žฌ์‚ฌ์šฉ
useCallback : ํ•จ์ˆ˜ ์žฌ์‚ฌ์šฉ
React.memo : ์ปดํฌ๋„ŒํŠธ ๋ฆฌ๋ Œ๋”๋ง ๋ฐฉ์ง€

 

7. useState๋ฅผ ํ†ตํ•ด ์ปดํฌ๋„ŒํŠธ์—์„œ ๋ฐ”๋€Œ๋Š” ๊ฐ’ ๊ด€๋ฆฌํ•˜๊ธฐ

 

import React, { useState } from 'react';
const [number, setNumber] = useState(0);

์ƒํƒœ์˜ ๊ธฐ๋ณธ๊ฐ’์„ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋„ฃ์–ด์„œ ํ˜ธ์ถœ

ํ˜ธ์ถœํ•˜๋ฉด ๋ฐฐ์—ด์ด ๋ฐ˜ํ™˜, ์ฒซ๋ฒˆ์งธ ์›์†Œ๋Š” ํ˜„์žฌ ์ƒํƒœ, ๋‘๋ฒˆ์งธ ์›์†Œ๋Š” Setter ํ•จ์ˆ˜

 

11. useRef๋กœ ํŠน์ • DOM ์„ ํƒํ•˜๊ธฐ

 

import React, { useRef } from 'react';
const nameInput = useRef();
...
nameInput.current.focus();
...
<input
	name="name"
    placeholder="์ด๋ฆ„"
    onChange={onChange}
    value={name}
    ref={nameInput}
/>

 

12. useRef๋กœ ์ปดํฌ๋„ŒํŠธ ์•ˆ์˜ ๋ณ€์ˆ˜ ๋งŒ๋“ค๊ธฐ

 

const nextId = useRef(4);
...
nextId.current += 1;

.currnet์˜ ๊ธฐ๋ณธ๊ฐ’์„ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋„ฃ์–ด์„œ ํ˜ธ์ถœ

์ด ๊ฐ’์„ ์ˆ˜์ •ํ•  ๋•Œ์—๋Š” .currnet ๊ฐ’์„ ์ˆ˜์ •ํ•˜๋ฉด ๋˜๊ณ , ์กฐํšŒํ•  ๋•Œ์—๋Š” .current๋ฅผ ์กฐํšŒํ•˜๋ฉด ๋œ๋‹ค.

 

13. useEffect๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋งˆ์šดํŠธ(์ฒ˜์Œ ๋‚˜ํƒ€๋‚ฌ์„ ๋•Œ)/์–ธ๋งˆ์šดํŠธ(์‚ฌ๋ผ์งˆ ๋•Œ)/์—…๋ฐ์ดํŠธ(ํŠน์ • props๊ฐ€ ๋ฐ”๋€” ๋•Œ)์‹œ ํ•  ์ž‘์—… ์„ค์ •ํ•˜๊ธฐ

 

import React, { useEffect } from 'react';

[๋งˆ์šดํŠธ / ์–ธ๋งˆ์šดํŠธ]

<UserList.js>

import React, { useEffect } from 'react';

function User({ user, onRemove, onToggle }) {
  useEffect(() => {
    console.log('์ปดํฌ๋„ŒํŠธ๊ฐ€ ํ™”๋ฉด์— ๋‚˜ํƒ€๋‚จ');
    return () => {
      console.log('์ปดํฌ๋„ŒํŠธ๊ฐ€ ํ™”๋ฉด์—์„œ ์‚ฌ๋ผ์ง');
    };
  }, []);
  return (
    <div>
      <b
        style={{
          cursor: 'pointer',
          color: user.active ? 'green' : 'black'
        }}
        onClick={() => onToggle(user.id)}
      >
        {user.username}
      </b>
      &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 UserList;

useEffect ์ฒซ๋ฒˆ์งธ ํŒŒ๋ผ๋ฏธํ„ฐ์—๋Š” ํ•จ์ˆ˜, ๋‘๋ฒˆ์งธ ํŒŒ๋ผ๋ฏธํ„ฐ์—๋Š” ์˜์กด๊ฐ’์ด ๋“ค์–ด์žˆ๋Š” ๋ฐฐ์—ด(deps)๋ฅผ ๋„ฃ๋Š”๋‹ค.

๋งŒ์•ฝ deps ๋ฐฐ์—ด์„ ๋น„์›Œ๊ฒŒ ๋œ๋‹ค๋ฉด, ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ฒ˜์Œ ๋‚˜ํƒ€๋‚ ๋•Œ์—๋งŒ useEffect์— ๋“ฑ๋กํ•œ ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋œ๋‹ค.

 

useEffect์—์„œ๋Š” ํ•จ์ˆ˜๋ฅผ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ, ์ด๋ฅผ cleanup ํ•จ์ˆ˜๋ผ๊ณ  ํ•œ๋‹ค.

cleanup ํ•จ์ˆ˜๋Š” useEffect์— ๋Œ€ํ•œ ๋’ท์ •๋ฆฌ๋ฅผ ํ•ด์ค€๋‹ค๊ณ  ๋ณด๋ฉด๋˜๋Š”๋ฐ,

deps๊ฐ€ ๋น„์–ด์žˆ๋Š” ๊ฒฝ์šฐ์—๋Š” ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์‚ฌ๋ผ์งˆ ๋•Œ cleanup ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋œ๋‹ค.

 

[deps์— ํŠน์ • ๊ฐ’ ๋„ฃ๊ธฐ]

deps์— ํŠน์ • ๊ฐ’์„ ๋„ฃ๊ฒŒ ๋˜๋ฉด, ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ฒ˜์Œ ๋งˆ์šดํŠธ ๋  ๋•Œ, ์ง€์ •ํ•œ ๊ฐ’์ด ๋ฐ”๋€” ๋•Œ, ์–ธ๋งˆ์šดํŠธ ๋  ๋•Œ, ๊ฐ’์ด ๋ฐ”๋€Œ๊ธฐ ์ง์ „์—๋„ ํ˜ธ์ถœ์ด ๋œ๋‹ค.

<UserList.js>

import React, { useEffect } from 'react';

function 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>
      &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 UserList;

useEffect์—์„œ ์‚ฌ์šฉํ•˜๋Š” ์ƒํƒœ๋‚˜ props๊ฐ€ ์žˆ๋‹ค๋ฉด, useEffect์˜ deps์— ๋„ฃ์–ด์ฃผ์–ด์•ผ ํ•œ๋‹ค.

๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด useEffect์— ๋“ฑ๋กํ•œ ํ•จ์ˆ˜๊ฐ€ ์ตœ์‹  ์ƒํƒœ๋‚˜ porps๋ฅผ ๊ฐ€๋ฅดํ‚ค์ง€ ์•Š๊ฒŒ ๋œ๋‹ค.

 

[deps ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์ƒ๋žตํ•˜๊ธฐ]

deps ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์ƒ๋žตํ•˜๋ฉด, ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ฆฌ๋ Œ๋”๋ง ๋  ๋•Œ๋งˆ๋‹ค ํ˜ธ์ถœ๋œ๋‹ค.

<UserList.js>

import React, { useEffect } from 'react';

function User({ user, onRemove, onToggle }) {
  useEffect(() => {
    console.log(user);
  });
  return (
    <div>
      <b
        style={{
          cursor: 'pointer',
          color: user.active ? 'green' : 'black'
        }}
        onClick={() => onToggle(user.id)}
      >
        {user.username}
      </b>
      &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 UserList;

 

17. useMemo๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์—ฐ์‚ฐํ•œ ๊ฐ’ ์žฌ์‚ฌ์šฉํ•˜๊ธฐ for ์„ฑ๋Šฅ ์ตœ์ ํ™”

 

[App.js]

import React, { useRef, useState, useMemo } from 'react';
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 = e => {
    const { name, value } = e.target;
    setInputs({
      ...inputs,
      [name]: value
    });
  };
  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 onCreate = () => {
    const user = {
      id: nextId.current,
      username,
      email
    };
    setUsers(users.concat(user));

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

  const onRemove = id => {
    // user.id ๊ฐ€ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ผ์น˜ํ•˜์ง€ ์•Š๋Š” ์›์†Œ๋งŒ ์ถ”์ถœํ•ด์„œ ์ƒˆ๋กœ์šด ๋ฐฐ์—ด์„ ๋งŒ๋“ฌ
    // = user.id ๊ฐ€ id ์ธ ๊ฒƒ์„ ์ œ๊ฑฐํ•จ
    setUsers(users.filter(user => user.id !== id));
  };
  const onToggle = id => {
    setUsers(
      users.map(user =>
        user.id === id ? { ...user, active: !user.active } : user
      )
    );
  };
  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;

useMemo์˜ ์ฒซ๋ฒˆ์งธ ํŒŒ๋ผ๋ฏธํ„ฐ์—๋Š” ์–ด๋–ป๊ฒŒ ์—ฐ์‚ฐํ• ์ง€ ์ •์˜ํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ๋„ฃ์–ด์ฃผ๊ณ , ๋‘๋ฒˆ์งธ ํŒŒ๋ผ๋ฏธํ„ฐ์—๋Š” deps ๋ฐฐ์—ด์„ ๋„ฃ์–ด์ค€๋‹ค.

์ด ๋ฐฐ์—ด ์•ˆ์— ๋„ฃ์€ ๋‚ด์šฉ์ด ๋ฐ”๋€Œ๋ฉด, ๋“ฑ๋กํ•œ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•ด์„œ ๊ฐ’์„ ์—ฐ์‚ฐํ•ด์ฃผ๊ณ , ๋ฐ”๋€Œ์ง€ ์•Š์œผ๋ฉด ์ด์ „์— ์—ฐ์‚ฐํ•œ ๊ฐ’์„ ์žฌ์‚ฌ์šฉํ•œ๋‹ค.

 

18. useCallback์„ ์‚ฌ์šฉํ•˜์—ฌ ํ•จ์ˆ˜ ์žฌ์‚ฌ์šฉํ•˜๊ธฐ for ์„ฑ๋Šฅ ์ตœ์ ํ™”

 

<App.js>

import React, { useCallback } from 'react';
import React, { useRef, useState, useMemo, useCallback } from 'react';
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,
        [name]: value
      });
    },
    [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 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 => {
      // user.id ๊ฐ€ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ผ์น˜ํ•˜์ง€ ์•Š๋Š” ์›์†Œ๋งŒ ์ถ”์ถœํ•ด์„œ ์ƒˆ๋กœ์šด ๋ฐฐ์—ด์„ ๋งŒ๋“ฌ
      // = user.id ๊ฐ€ 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]
  );
  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 ํ•จ์ˆ˜ ์•ˆ์—์„œ๋Š” ์‚ฌ์šฉํ•˜๋Š” ์ƒํƒœ ํ˜น์€ props๊ฐ€ ์žˆ๋‹ค๋ฉด ๊ผญ deps ๋ฐฐ์—ด์•ˆ์— ํฌํ•จ์‹œ์ผœ์•ผ ํ•œ๋‹ค.

๋งŒ์•ฝ์— ๋„ฃ์ง€ ์•Š์œผ๋ฉด, ํ•จ์ˆ˜ ๋‚ด์—์„œ ํ•ด๋‹น ๊ฐ’๋“ค์„ ์ฐธ์กฐํ•  ๋•Œ ๊ฐ€์žฅ ์ตœ์‹  ๊ฐ’์„ ์ฐธ์กฐํ•  ๊ฒƒ์ด๋ผ๊ณ  ๋ณด์žฅํ•  ์ˆ˜ ์—†๋‹ค.

 

19. React.memo๋ฅผ ์‚ฌ์šฉํ•œ ์ปดํฌ๋„ŒํŠธ ๋ฆฌ๋ Œ๋”๋ง ๋ฐฉ์ง€ for ์„ฑ๋Šฅ ์ตœ์ ํ™”

 

<CreateUser.js>

import React from 'react';

const CreateUser = ({ username, email, onChange, onCreate }) => {
  return (
    <div>
      <input
        name="username"
        placeholder="๊ณ„์ •๋ช…"
        onChange={onChange}
        value={username}
      />
      <input
        name="email"
        placeholder="์ด๋ฉ”์ผ"
        onChange={onChange}
        value={email}
      />
      <button onClick={onCreate}>๋“ฑ๋ก</button>
    </div>
  );
};

export default React.memo(CreateUser);

<UesrList.js>

import React from 'react';

const User = React.memo(function User({ user, onRemove, onToggle }) {
  return (
    <div>
      <b
        style={{
          cursor: 'pointer',
          color: user.active ? 'green' : 'black'
        }}
        onClick={() => onToggle(user.id)}
      >
        {user.username}
      </b>
      &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);

โญ ๋ฐฐ์—ด์ด ๋ฐ”๋€” ๋•Œ๋งˆ๋‹ค ํ•จ์ˆ˜๊ฐ€ ์ƒˆ๋กœ ๋งŒ๋“ค์–ด์ง„๋‹ค...

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

deps์— users(๋ฐฐ์—ด)์„ ์ง€์šฐ๊ณ , ํ•จ์ˆ˜๋“ค์—์„œ ํ˜„์žฌ useState๋กœ ๊ด€๋ฆฌํ•˜๋Š” users๋ฅผ ์ฐธ์กฐํ•˜์ง€ ์•Š๊ฒŒ ํ•œ๋‹ค.

=> ํ•จ์ˆ˜ํ˜• ์—…๋ฐ์ดํŠธ !!!!!!!

[App.js]

import React, { useRef, useState, useMemo, useCallback } from 'react';
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 => ({
      ...inputs,
      [name]: value
    }));
  }, []);
  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 onCreate = useCallback(() => {
    const user = {
      id: nextId.current,
      username,
      email
    };
    setUsers(users => users.concat(user));

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

  const onRemove = useCallback(id => {
    // user.id ๊ฐ€ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ผ์น˜ํ•˜์ง€ ์•Š๋Š” ์›์†Œ๋งŒ ์ถ”์ถœํ•ด์„œ ์ƒˆ๋กœ์šด ๋ฐฐ์—ด์„ ๋งŒ๋“ฌ
    // = user.id ๊ฐ€ id ์ธ ๊ฒƒ์„ ์ œ๊ฑฐํ•จ
    setUsers(users => users.filter(user => user.id !== id));
  }, []);
  const onToggle = useCallback(id => {
    setUsers(users =>
      users.map(user =>
        user.id === id ? { ...user, active: !user.active } : user
      )
    );
  }, []);
  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;

 

20. useReducer๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ƒํƒœ ์—…๋ฐ์ดํŠธ ๋กœ์ง ๋ถ„๋ฆฌํ•˜๊ธฐ

 

reducer : ํ˜„์žฌ ์ƒํƒœ์™€ ์•ก์…˜ ๊ฐ์ฒด๋ฅผ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋ฐ›์•„์™€์„œ ์ƒˆ๋กœ์šด ์ƒํƒœ๋ฅผ ๋ฐ˜ํ™˜ํ•ด์ฃผ๋Š” ํ•จ์ˆ˜

action : ์—…๋ฐ์ดํŠธ๋ฅผ ์œ„ํ•œ ์ •๋ณด

import React, { useReducer } from 'react';

<reducer ๋ž€>

function reducer(state, action) {
  // ์ƒˆ๋กœ์šด ์ƒํƒœ๋ฅผ ๋งŒ๋“œ๋Š” ๋กœ์ง
  // const nextState = ...
  return nextState;
}

<action ์˜ˆ์‹œ>

// ์นด์šดํ„ฐ์— 1์„ ๋”ํ•˜๋Š” ์•ก์…˜
{
  type: 'INCREMENT'
}
// ์นด์šดํ„ฐ์— 1์„ ๋นผ๋Š” ์•ก์…˜
{
  type: 'DECREMENT'
}
// input ๊ฐ’์„ ๋ฐ”๊พธ๋Š” ์•ก์…˜
{
  type: 'CHANGE_INPUT',
  key: 'email',
  value: 'tester@react.com'
}
// ์ƒˆ ํ•  ์ผ์„ ๋“ฑ๋กํ•˜๋Š” ์•ก์…˜
{
  type: 'ADD_TODO',
  todo: {
    id: 1,
    text: 'useReducer ๋ฐฐ์šฐ๊ธฐ',
    done: false,
  }
}

<useReducer ์‚ฌ์šฉ๋ฒ•>

const [state, dispatch] = useReducer(reducer, initialState);

state : ์ปดํฌ๋„ŒํŠธ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ƒํƒœ

dispatch : ์•ก์…˜์„ ๋ฐœ์ƒ์‹œํ‚ค๋Š” ํ•จ์ˆ˜ dispatch({type: 'INCREMENT'});

useReducer์— ๋„ฃ๋Š” ์ฒซ๋ฒˆ์งธ ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” reducerํ•จ์ˆ˜, ๋‘๋ฒˆ์งธ ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” ์ดˆ๊ธฐ์ƒํƒœ

<Counter.js>

import React, { useReducer } from 'react';

function reducer(state, action) {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1;
    case 'DECREMENT':
      return state - 1;
    default:
      return state;
  }
}

function Counter() {
  const [number, dispatch] = useReducer(reducer, 0);

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

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

  return (
    <div>
      <h1>{number}</h1>
      <button onClick={onIncrease}>+1</button>
      <button onClick={onDecrease}>-1</button>
    </div>
  );
}

export default Counter;

 

<App.js>

import React, { useRef, useReducer, useMemo, useCallback } from 'react';
import UserList from './UserList';
import CreateUser from './CreateUser';

function countActiveUsers(users) {
  console.log('ํ™œ์„ฑ ์‚ฌ์šฉ์ž ์ˆ˜๋ฅผ ์„ธ๋Š”์ค‘...');
  return users.filter(user => user.active).length;
}

const initialState = {
  inputs: {
    username: '',
    email: ''
  },
  users: [
    {
      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
    }
  ]
};

function reducer(state, action) {
  switch (action.type) {
    case 'CHANGE_INPUT':
      return {
        ...state,
        inputs: {
          ...state.inputs,
          [action.name]: action.value
        }
      };
    case 'CREATE_USER':
      return {
        inputs: initialState.inputs,
        users: state.users.concat(action.user)
      };
    case 'TOGGLE_USER':
      return {
        ...state,
        users: state.users.map(user =>
          user.id === action.id ? { ...user, active: !user.active } : user
        )
      };
    case 'REMOVE_USER':
      return {
        ...state,
        users: state.users.filter(user => user.id !== action.id)
      };
    default:
      return state;
  }
}

function App() {
  const [state, dispatch] = useReducer(reducer, initialState);
  const nextId = useRef(4);

  const { users } = state;
  const { username, email } = state.inputs;

  const onChange = useCallback(e => {
    const { name, value } = e.target;
    dispatch({
      type: 'CHANGE_INPUT',
      name,
      value
    });
  }, []);

  const onCreate = useCallback(() => {
    dispatch({
      type: 'CREATE_USER',
      user: {
        id: nextId.current,
        username,
        email
      }
    });
    nextId.current += 1;
  }, [username, email]);

  const onToggle = useCallback(id => {
    dispatch({
      type: 'TOGGLE_USER',
      id
    });
  }, []);

  const onRemove = useCallback(id => {
    dispatch({
      type: 'REMOVE_USER',
      id
    });
  }, []);

  const count = useMemo(() => countActiveUsers(users), [users]);
  return (
    <>
      <CreateUser
        username={username}
        email={email}
        onChange={onChange}
        onCreate={onCreate}
      />
      <UserList users={users} onToggle={onToggle} onRemove={onRemove} />
      <div>ํ™œ์„ฑ์‚ฌ์šฉ์ž ์ˆ˜ : {count}</div>
    </>
  );
}

export default App;
setter ํ•จ์ˆ˜๊ฐ€ ํ•œ๊ฐœ๋ฉด useState
setter ํ•จ์ˆ˜๊ฐ€ ๋‘๊ฐœ ์ด์ƒ์ด๋ฉด useReducer ๊ฐ€ ์ข‹๋‹ค~ 

 

21. ์ปค์Šคํ…€ Hooks ๋งŒ๋“ค๊ธฐ => ์ปดํฌ๋„ŒํŠธ ๋กœ์ง ๋ถ„๋ฆฌ!

 

<useInput.js>

import { useState, useCallback } from 'react';

function useInputs(initialForm) {
  const [form, setForm] = useState(initialForm);
  // change
  const onChange = useCallback(e => {
    const { name, value } = e.target;
    setForm(form => ({ ...form, [name]: value }));
  }, []);
  const reset = useCallback(() => setForm(initialForm), [initialForm]);
  return [form, onChange, reset];
}

export default useInputs;

<App.js>

import React, { useRef, useReducer, useMemo, useCallback } from 'react';
import UserList from './UserList';
import CreateUser from './CreateUser';
import useInputs from './hooks/useInputs';

function countActiveUsers(users) {
  console.log('ํ™œ์„ฑ ์‚ฌ์šฉ์ž ์ˆ˜๋ฅผ ์„ธ๋Š”์ค‘...');
  return users.filter(user => user.active).length;
}

const initialState = {
  users: [
    {
      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
    }
  ]
};

function reducer(state, action) {
  switch (action.type) {
    case 'CREATE_USER':
      return {
        users: state.users.concat(action.user)
      };
    case 'TOGGLE_USER':
      return {
        users: state.users.map(user =>
          user.id === action.id ? { ...user, active: !user.active } : user
        )
      };
    case 'REMOVE_USER':
      return {
        users: state.users.filter(user => user.id !== action.id)
      };
    default:
      return state;
  }
}

function App() {
  const [{ username, email }, onChange, reset] = useInputs({
    username: '',
    email: ''
  });
  const [state, dispatch] = useReducer(reducer, initialState);
  const nextId = useRef(4);

  const { users } = state;

  const onCreate = useCallback(() => {
    dispatch({
      type: 'CREATE_USER',
      user: {
        id: nextId.current,
        username,
        email
      }
    });
    reset();
    nextId.current += 1;
  }, [username, email, reset]);

  const onToggle = useCallback(id => {
    dispatch({
      type: 'TOGGLE_USER',
      id
    });
  }, []);

  const onRemove = useCallback(id => {
    dispatch({
      type: 'REMOVE_USER',
      id
    });
  }, []);

  const count = useMemo(() => countActiveUsers(users), [users]);
  return (
    <>
      <CreateUser
        username={username}
        email={email}
        onChange={onChange}
        onCreate={onCreate}
      />
      <UserList users={users} onToggle={onToggle} onRemove={onRemove} />
      <div>ํ™œ์„ฑ์‚ฌ์šฉ์ž ์ˆ˜ : {count}</div>
    </>
  );
}

export default App;

 

22. Context API๋ฅผ ์‚ฌ์šฉํ•œ ์ „์—ญ๊ฐ’ ๊ด€๋ฆฌ

 

const UserDispatch = React.createContext(null);

createContext์˜ ํŒŒ๋ผ๋ฏธํ„ฐ์—๋Š” Context์˜ ๊ธฐ๋ณธ๊ฐ’

<UserDispatch.Provider value={dispatch}>...</UserDispatch.Provider>

Context ์•ˆ์— Provider๋ผ๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ†ตํ•˜์—ฌ Context์˜ ๊ฐ’์„ (value๋กœ) ์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

=> Provider์— ์˜ํ•˜์—ฌ ๊ฐ์‹ธ์ง„ ์ปดํฌ๋„ŒํŠธ ์ค‘ ์–ด๋””์„œ๋“ ์ง€ Context์˜ ๊ฐ’์„ ๋‹ค๋ฅธ ๊ณณ์—์„œ ๋ฐ”๋กœ ์กฐํšŒํ•ด์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

 

<App.js>

import React, { useRef, useReducer, useMemo, useCallback } from 'react';
import UserList from './UserList';
import CreateUser from './CreateUser';
import useInputs from './hooks/useInputs';

function countActiveUsers(users) {
  console.log('ํ™œ์„ฑ ์‚ฌ์šฉ์ž ์ˆ˜๋ฅผ ์„ธ๋Š”์ค‘...');
  return users.filter(user => user.active).length;
}

const initialState = {
  users: [
    {
      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
    }
  ]
};

function reducer(state, action) {
  switch (action.type) {
    case 'CREATE_USER':
      return {
        users: state.users.concat(action.user)
      };
    case 'TOGGLE_USER':
      return {
        ...state,
        users: state.users.map(user =>
          user.id === action.id ? { ...user, active: !user.active } : user
        )
      };
    case 'REMOVE_USER':
      return {
        ...state,
        users: state.users.filter(user => user.id !== action.id)
      };
    default:
      return state;
  }
}

// UserDispatch ๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ ๋‚ด๋ณด๋‚ด์ค๋‹ˆ๋‹ค.
export const UserDispatch = React.createContext(null);

function App() {
  const [{ username, email }, onChange, onReset] = useInputs({
    username: '',
    email: ''
  });
  const [state, dispatch] = useReducer(reducer, initialState);
  const nextId = useRef(4);

  const { users } = state;

  const onCreate = useCallback(() => {
    dispatch({
      type: 'CREATE_USER',
      user: {
        id: nextId.current,
        username,
        email
      }
    });
    onReset();
    nextId.current += 1;
  }, [username, email, onReset]);

  const count = useMemo(() => countActiveUsers(users), [users]);
  return (
    <UserDispatch.Provider value={dispatch}>
      <CreateUser
        username={username}
        email={email}
        onChange={onChange}
        onCreate={onCreate}
      />
      <UserList users={users} />
      <div>ํ™œ์„ฑ์‚ฌ์šฉ์ž ์ˆ˜ : {count}</div>
    </UserDispatch.Provider>
  );
}

export default App;

<UserList.js> => onToggle/onRemove ๊ด€๋ จ ์ฝ”๋“œ ์‚ญ์ œ

import React, { useContext } from 'react';
import { UserDispatch } from './App';

const User = React.memo(function User({ user }) {
  const dispatch = useContext(UserDispatch);

  return (
    <div>
      <b
        style={{
          cursor: 'pointer',
          color: user.active ? 'green' : 'black'
        }}
        onClick={() => {
          dispatch({ type: 'TOGGLE_USER', id: user.id });
        }}
      >
        {user.username}
      </b>
      &nbsp;
      <span>({user.email})</span>
      <button
        onClick={() => {
          dispatch({ type: 'REMOVE_USER', id: user.id });
        }}
      >
        ์‚ญ์ œ
      </button>
    </div>
  );
});

function UserList({ users }) {
  return (
    <div>
      {users.map(user => (
        <User user={user} key={user.id} />
      ))}
    </div>
  );
}

export default React.memo(UserList);

 

23. Immer๋ฅผ ์‚ฌ์šฉํ•œ ๋” ์‰ฌ์šด ๋ถˆ๋ณ€์„ฑ ๊ด€๋ฆฌ => ๋ฐฐ์—ด ๊ฐ์ฒด๊ฐ€ ๊นŠ์€ ๊ณณ(2deps ์ด์ƒ)์— ์œ„์น˜ํ•  ๋•Œ ์‚ฌ์šฉํ•˜์ž

 

์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธํ•  ๋•Œ, ๋ถˆ๋ณ€์„ฑ์„ ์‹ ๊ฒฝ์“ฐ์ง€ ์•Š์œผ๋ฉด์„œ ์—…๋ฐ์ดํŠธ๋ฅผ ํ•ด์ฃผ๋ฉด Immer๊ฐ€ ๋ถˆ๋ณ€์„ฑ ๊ด€๋ฆฌ๋ฅผ ๋Œ€์‹  ํ•ด์ค€๋‹ค.

 

$ yarn add immer
import produce from 'immer';

produce ํ•จ์ˆ˜์— ์ฒซ๋ฒˆ์งธ ํŒŒ๋ผ๋ฏธํ„ฐ์—๋Š” ์ˆ˜์ •ํ•˜๊ณ  ์‹ถ์€ ์ƒํƒœ, ๋‘๋ฒˆ์งธ ํŒŒ๋ผ๋ฏธํ„ฐ์—๋Š” ์–ด๋–ป๊ฒŒ ์—…๋ฐ์ดํŠธํ•˜๊ณ  ์‹ถ์€์ง€ ์ •์˜ํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ๋„ฃ์–ด์ค€๋‹ค. (๋ถˆ๋ณ€์„ฑ ์‹ ๊ฒฝ X)

<์˜ˆ์‹œ>

const state = {
  number: 1,
  dontChangeMe: 2
};

const nextState = produce(state, draft => {
  draft.number += 1;
});

console.log(nextState);
// { number: 2, dontChangeMe: 2 }

<App.js>

import React, { useReducer, useMemo } from 'react';
import UserList from './UserList';
import CreateUser from './CreateUser';
import produce from 'immer';

function countActiveUsers(users) {
  console.log('ํ™œ์„ฑ ์‚ฌ์šฉ์ž ์ˆ˜๋ฅผ ์„ธ๋Š”์ค‘...');
  return users.filter(user => user.active).length;
}

const initialState = {
  users: [
    {
      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
    }
  ]
};

function reducer(state, action) {
  switch (action.type) {
    case 'CREATE_USER':
      return produce(state, draft => {
        draft.users.push(action.user);
      });
    case 'TOGGLE_USER':
      return produce(state, draft => {
        const user = draft.users.find(user => user.id === action.id);
        user.active = !user.active;
      });
    case 'REMOVE_USER':
      return produce(state, draft => {
        const index = draft.users.findIndex(user => user.id === action.id);
        draft.users.splice(index, 1);
      });
    default:
      return state;
  }
}

// UserDispatch ๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ ๋‚ด๋ณด๋‚ด์ค๋‹ˆ๋‹ค.
export const UserDispatch = React.createContext(null);

function App() {
  const [state, dispatch] = useReducer(reducer, initialState);

  const { users } = state;

  const count = useMemo(() => countActiveUsers(users), [users]);
  return (
    <UserDispatch.Provider value={dispatch}>
      <CreateUser />
      <UserList users={users} />
      <div>ํ™œ์„ฑ์‚ฌ์šฉ์ž ์ˆ˜ : {count}</div>
    </UserDispatch.Provider>
  );
}

export default App;