230522 - React(useReducer, useContext)

๋ฐฑ์Šน์—ฐยท2023๋…„ 5์›” 22์ผ
1

๐Ÿšฉ React

useState๋กœ ๋งŒ๋“ค์—ˆ๋˜ ๋‚ด์šฉ์„ useReducer๋กœ ๋ณ€๊ฒฝ(๊ธฐ์กด๋ฐฐ์—ด)



โœ’๏ธ ์ฝ”๋“œ ์ž‘์„ฑ

์ž…๋ ฅ

App.js

// useState์—์„œ useReducer๋กœ ๋ณ€๊ฒฝ

import { useRef, useMemo, useCallback, useReducer } from "react";
import CreateUser from "./CreateUser";
import "./styles.css";
import UserList from "./UserList";

// active๋œ ์•„์ดํ…œ ์ˆซ์ž๋ฅผ ์„ธ ์ฃผ๋Š” ํ•จ์ˆ˜
// array๋ฅผ ์ธ์ž๋กœ ๋ฐ›์•„์˜ด
function countActiveUsers(users) {
  return users.filter((item) => item.active).length;
}
// ํ•„์š” ์—†์„๋•Œ๋„ ๋„ˆ๋ฌด ์ž์ฃผ ์‹คํ–‰๋จ (๋ฐ์ดํ„ฐ ๋‚ญ๋น„, ์„ฑ๋Šฅ ํ•˜๋ฝ) -> useMemo๋ฅผ ์‚ฌ์šฉ

// 1. changeText
const initialState = {
  inputs: { username: "", email: "" },
  users: [
    {
      id: 1,
      username: "Bret",
      email: "Sincere@april.biz",
      active: true
    },
    {
      id: 2,
      username: "Antonette",
      email: "Shanna@melissa.tv",
      active: false
    },
    {
      id: 3,
      username: "Samantha",
      email: "Nathan@yesenia.net",
      active: false
    },
    {
      id: 4,
      username: "Karianne",
      email: "Julianne.OConner@kory.org",
      active: false
    }
  ]
};

// 1. changeText
// 2. Create
// 3. remove
const myReducer = (state, action) => {
  // ์‹คํ–‰ํ•จ์ˆ˜
  switch (action.type) {
    case "CHANGE_INPUT":
      return {
        ...state,
        inputs: { ...state.inputs, [action.name]: action.value }
        // dispatch๋กœ ๋ณด๋‚ด๋ฉด action์œผ๋กœ ๋ฐ›์•„์˜ด
      };
    case "CREATE_USER":
      return {
        users: [...state.users, action.item], // ๋ฐฐ์—ด์— ์•„์ดํ…œ์„ ์ถ”๊ฐ€
        inputs: initialState.inputs // input ์ดˆ๊ธฐํ™”
      };
    case "REMOVE_USER":
      return {
        ...state, // ํ˜„์žฌ ์ƒํƒœ๋Š” input๊ณผ user๊ฐ€ ์žˆ์Œ
        users: state.users.filter((item) => item.id !== action.id) // ๋‚ด๊ฐ€ ๊ณ ๋ฅธ id๊ฐ’์„ ๋นผ๊ณ  ๋‹ค์‹œ ๋ฐฐ์—ด์„ ๋งŒ๋“ฆ
      };
    case "TOGGLE_USER":
      return {
        ...state,
        users: state.users.map((item) =>
          item.id === action.id ? { ...item, active: !item.active } : item
        )
      };
    default:
      return state;
  }
};

export default function App() {
  // 1. changeText
  const [state, dispatch] = useReducer(myReducer, initialState);
  // ๋น„๊ตฌ์กฐํ• ๋‹น
  // const users = state.users;
  // const username = state.inputs.username;
  // const email = state.inputs.email;
  const { users } = state;
  const { username, email } = state.inputs;

  // useCallback() : ๋ญ”๊ฐ€ ๋ณ€ํ™”๊ฐ€ ์žˆ์„ ๋•Œ ์‹คํ–‰ ๋จ
  const changeText = useCallback((event) => {
    const { name, value } = event.target;
    // setInputs({ ...inputs, [name]: value }); // setInput ๋Œ€์‹  dispatch๋กœ ๋ณด๋‚ด์•ผ ํ•จ
    dispatch({
      type: "CHANGE_INPUT",
      name,
      value
    });
  }, []);

  // 2. create
  // create๋ฅผ ๋ˆ„๋ฅผ ๋•Œ ๋งˆ๋‹ค id๊ฐ’์ด 1์”ฉ ์ฆ๊ฐ€ํ•˜๋„๋ก ํ•˜๋Š” ๋ณ€์ˆ˜ ์„ค์ •
  const nextId = useRef(5); // ๋ฐฐ์—ด ์•„์ดํ…œ์— ๋“ค์–ด ๊ฐˆ id
  const focusIn = useRef(); // ํŠน์ •ํ•œ DOM(์—˜๋ฆฌ๋จผํŠธ) ์„ ํƒ์„ ์œ„ํ•œ ์„ค์ •

  const onCreate = useCallback(() => {
    dispatch({
      type: "CREATE_USER",
      item: {
        id: nextId.current, // key: value
        username,
        email
      }
    });
    nextId.current += 1;
    focusIn.current.focus();
  }, [username, email]);
  // setUsers([...users, item]); // ๋ฐฐ์—ด์— ์•„์ดํ…œ์„ ๋„ฃ๋Š” ํ•จ์ˆ˜ ์‹คํ–‰
  // setInputs({ username: "", email: "" }); // input ์ดˆ๊ธฐํ™”

  // 3. ์‚ญ์ œ ํ•จ์ˆ˜ ์ •์˜
  const remove = useCallback((id) => {
    dispatch({
      type: "REMOVE_USER",
      id
    });
  }, []);

  // 4. username ์ƒ‰์„ ๋ฐ”๊ฟ”์ฃผ๋Š” toggleํ•จ์ˆ˜
  const toggleColor = useCallback((id) => {
    dispatch({
      type: "TOGGLE_USER",
      id
    });
    /*
    setUsers(
      users.map(
        (item) =>
          // item์€ {} ํ•œ ๊ฐœ๋ฅผ ์˜๋ฏธ
          // item.active = active์˜ key๊ฐ’์„ ์˜๋ฏธ
          item.id === id ? { ...item, active: !item.active } : item // ๋ช…๋ น์–ด ํ•œ ์ค„ (์ค‘๊ด„ํ˜ธ ํ•„์š”x)
      )
    );*/
  }, []);

  // 5. active ์ ์šฉ๋œ ์ด๋ฆ„ ์ˆซ์ž ์„ธ์ฃผ๋Š” ํ•จ์ˆ˜ ์‹คํ–‰
  // useMemo(ํ•จ์ˆ˜, ๋ณ€ํ™”์š”์†Œ)
  const count = useMemo(() => countActiveUsers(users), [users]);

  return (
    <div>
      <CreateUser
        username={username}
        email={email}
        changeText={changeText}
        onCreate={onCreate}
        focusRef={focusIn}
      />
      <br />
      <br />
      <UserList users={users} remove={remove} toggleColor={toggleColor} />
      <hr />

      <div>Active ์‚ฌ์šฉ์ž ์ˆ˜ : {count}</div>
    </div>
  );
}



CreateUser.jsx

import React from "react";

export default function CreateUser({
  changeText,
  onCreate,
  username,
  email,
  focusRef
}) {
  return (
    <>
      <input
        type="text"
        name="username"
        placeholder="์œ ์ €๋„ค์ž„"
        onChange={changeText}
        value={username}
        ref={focusRef}
      />
      <input
        type="email"
        name="email"
        placeholder="์ด๋ฉ”์ผ"
        onChange={changeText}
        value={email}
      />
      <button onClick={onCreate}>๋“ฑ๋ก</button>
    </>
  );
}



UserList.jsx

import React from "react";

//์ปดํฌ๋„ŒํŠธ ์ถ”๊ฐ€ ์ •์˜
function User({ item, toggleColor, remove }) {
  // "item."์„ ์—†์• ๊ธฐ ์œ„ํ•ด ๋‹ค์‹œ ์ง€์ •
  const { id, active, username, email } = item;
  return (
    <div className={id}>
      <b
        style={{
          color: active ? "orange" : "gray",
          cursor: "pointer",
          marginRight: 5
        }}
        onClick={() => toggleColor(id)}
      >
        {username}
      </b>{" "}
      <span>({email})</span>
      <button
        onClick={() => {
          remove(id); // ๋ช‡ ๋ฒˆ์งธ ๊ฐ’์„ ์„ ํƒํ–ˆ๋Š”์ง€ ์•„์ด๋””๊ฐ’์„ ๋ฐ›์•„์˜ด
        }}
      >
        ์‚ญ์ œ
      </button>
    </div>
  );
}

// onRemove๋Š” key๊ฐ’์ด ๋„˜์–ด๊ฐ
export default function UserList({ users, remove, toggleColor }) {
  return (
    <>
      {users.map((item) => (
        // ์ด rr์ด ์œ„ User ํ•จ์ˆ˜์˜ rr๋กœ ๋„˜์–ด๊ฐ
        <User
          item={item}
          key={item.id}
          remove={remove}
          toggleColor={toggleColor}
        />
      ))}
    </>
  );
}

์ถœ๋ ฅ

  • ์ด๋ฏธ์ง€๋กœ ๋Œ€์ฒด

๐Ÿ”— ์ฐธ๊ณ  ๋งํฌ & ๋„์›€์ด ๋˜๋Š” ๋งํฌ






useState๋กœ ๋งŒ๋“ค์—ˆ๋˜ ๋‚ด์šฉ์„ useReducer๋กœ ๋ณ€๊ฒฝ(๋ฐฐ์—ด๊ฐ์ฒด)



โœ’๏ธ ์ฝ”๋“œ ์ž‘์„ฑ

์ž…๋ ฅ

App.js

import React, { useReducer } from "react";
import personReducer from "./person-reducer"; // ์ค‘๊ด„ํ˜ธ๋กœ ๋ฐ›์•„์˜ฌ๊ฑฐ๋ฉด export ํ•  ๋•Œ default ์•ˆ ์จ๋„ ๋จ
import "./styles.css";

export default function App() {
  const initialPerson = {
    // person์˜ ์ดˆ๊ธฐ๊ฐ’
    name: "์ „์ง€ํ˜„",
    work: "๊ฐœ๋ฐœ์ž",
    mentors: [
      {
        name: "๋งˆ์ดํด",
        work: "์‹œ๋‹ˆ์–ด๊ฐœ๋ฐœ์ž"
      },
      {
        name: "ใ…‡ใ……ใ…‡",
        work: "ํšŒ์žฅ"
      },
      {
        name: "๊ผฌ๋งˆ",
        work: "๊ฐ•์•„์ง€"
      }
    ]
  };

  const [person, dispatch] = useReducer(personReducer, initialPerson);

  // 3. ๋ฉ˜ํ†  ์ด๋ฆ„ ๋ฐ”๊พธ๊ธฐ ํ•จ์ˆ˜ ์ •์˜
  const updateMento = () => {
    const prev = prompt("๋ฐ”๊พธ๊ณ  ์‹ถ์€ ์ด๋ฆ„์€?");
    const current = prompt("์ƒˆ๋กœ ์ง€์ •ํ•  ์ด๋ฆ„์€?");

    dispatch({
      type: "updated",
      prev,
      current
    });

    // setPerson((person) => ({
    //   ...person, // person ์ „์ฒด ๋‚˜์—ด(name, work, mentos)
    //   /*
    //     ์˜ค๋ธŒ์ ํŠธ ๋‹จ์œ„๋กœ map ๋Œ๋ฆผ
    //     person.mentors ๋ผ๋Š” array๋ฅผ map์„ ์‚ฌ์šฉํ•˜์—ฌ item{} ๊ธฐ์ค€์œผ๋กœ ํ•˜๋‚˜์”ฉ ๊ฒ€์‚ฌ
    //   */
    //   mentors: person.mentors.map((item) => {
    //     // prev๊ฐ’์ด ๊ฐ™์€ ๊ฒƒ์ด ์žˆ๋Š”์ง€ ํ™•์ธ. item์˜ ์ด๋ฆ„์ด prev์™€ ๊ฐ™์œผ๋ฉด,
    //     if (item.name === prev) {
    //       // name์„ ๋‚˜์ค‘์— ์ž…๋ ฅํ•œ ๊ฐ’(current)๋กœ ๋ฐ”๊ฟ”์คŒ
    //       return { ...item, name: current };
    //     }
    //     return item;
    //   })
    // }));
  };

  // 2. ๋ฉ˜ํ†  ์‚ญ์ œ ํ•จ์ˆ˜ ์ •์˜
  const deleteMento = () => {
    const del = prompt("์‚ญ์ œํ•˜๊ณ  ์‹ถ์€ ์ด๋ฆ„์€?");
    dispatch({
      type: "deleted",
      del
    });
  };

  // 1. ๋ฉ˜ํ†  ์ถ”๊ฐ€ ํ•จ์ˆ˜ ์ •์˜
  const addMento = () => {
    const name = prompt("์ถ”๊ฐ€ํ•  ๋ฉ˜ํ† ์˜ ์ด๋ฆ„์€?");
    const work = prompt("์ถ”๊ฐ€ํ•  ๋ฉ˜ํ† ์˜ ์ง์—…์€?");

    // setPerson(() => ({
    //   ...person,
    //   mentors: [person.mentors, { name, work }] // ๋’ค์— ์ ์œผ๋ฉด ๋’ค์— ์ถ”๊ฐ€ํ•œ๋‹ค๋Š” ๋œป
    // }));

    dispatch({
      type: "added",
      name,
      work
      // action ์•ˆ์— name๊ณผ work๊ฐ€ ์žˆ์Œ
    });
  };

  return (
    <div className="App">
      <h1>
        {person.name}์€ {person.work}์ž…๋‹ˆ๋‹ค.
      </h1>
      <h4>{person.name}์˜ ๋ฉ˜ํ† ๋Š” : </h4>

      {/* ์„ธ ๋ฒˆ์งธ ๋ฉ˜ํ†  - {person.mentors[2].name} */}
      <ul>
        {person.mentors.map((item, index) => (
          <li key={index}>
            {item.name} ({item.work})
          </li>
        ))}
      </ul>

      <button onClick={updateMento}>๋ฉ˜ํ†  ์ด๋ฆ„ ๋ฐ”๊พธ๊ธฐ</button>
      <button onClick={deleteMento}>๋ฉ˜ํ†  ์‚ญ์ œ</button>
      <button onClick={addMento}>๋ฉ˜ํ†  ์ถ”๊ฐ€</button>
    </div>
  );
}



person-reducer.js

// reducer ํ•จ์ˆ˜ ์„ ์–ธ
export default function personReducer(person, action) {
  switch (action.type) {
    case "updated":
      const { prev, current } = action;
      return {
        ...person,
        // person.mentors ์ด๋ผ๋Š” array
        mentors: person.mentors.map((mentor) => {
          // prev๊ฐ’์ด ๊ฐ™์€ ๊ฒƒ์ด ์žˆ๋Š”์ง€ ํ™•์ธ. item์˜ ์ด๋ฆ„์ด prev์™€ ๊ฐ™์œผ๋ฉด,
          if (mentor.name === prev) {
            // name์„ ๋‚˜์ค‘์— ์ž…๋ ฅํ•œ ๊ฐ’(current)๋กœ ๋ฐ”๊ฟ”์คŒ
            return { ...mentor, name: current };
          }
          return mentor;
        })
      };

    case "added": {
      // const name = action.name;
      // const work = action.work;
      const { name, work } = action;
      return {
        ...person,
        mentors: [...person.mentors, { name, work }]
      };
    }

    case "deleted":
      return {
        ...person,
        mentors: person.mentors.filter((aa) => aa.name !== action.del) // ๊ฐ๊ฐ์˜ ์š”์†Œ
      };

    default:
      return person;
    // throw Error("์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์–ด์š”");
  }
}

์ถœ๋ ฅ

  • ์ด๋ฏธ์ง€๋กœ ๋Œ€์ฒด

์ถ”๊ฐ€/์‚ญ์ œ/๋ณ€๊ฒฝ ๊ฐ€๋Šฅ


๐Ÿ”— ์ฐธ๊ณ  ๋งํฌ & ๋„์›€์ด ๋˜๋Š” ๋งํฌ






useContext

๐Ÿ“ ์„ค๋ช…

  • props๋ฅผ ๊ธ€๋กœ๋ฒŒํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์ฃผ๋Š” react hook


โœ’๏ธ ์‚ฌ์šฉ๋ฒ•

์ž…๋ ฅ

App.js

import Context from "./Context";
import "./styles.css";

export default function App() {
  return (
    <div className="App">
      <Context />
    </div>
  );
}



Context.jsx

import React, { createContext, useContext, useState } from "react";

// createContext() - context ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค์–ด ์คŒ(๋ณ€์ˆ˜), ์ปดํฌ๋„ŒํŠธ์ฒ˜๋Ÿผ ์“ฐ์ž„
// ("๊ธฐ๋ณธ value") - ์ปจํ…์ŠคํŠธ์—์„œ ์‚ฌ์šฉ ํ•  ๊ธฐ๋ณธ๊ฐ’
const MyContext = createContext("๊ธฐ๋ณธ value");

function Child() {
  const text = useContext(MyContext);
  return <div>{text}</div>;
}

function Parent({ text }) {
  return <Child />;
}

function GrandParent() {
  return <Parent />;
}

function Context() {
  const [value, setValue] = useState(true);

  return (
    <MyContext.Provider value={value ? "good" : "no"}>
      <GrandParent />
      <button onClick={() => setValue(!value)}>Click Me</button>
    </MyContext.Provider>
  );
}

export default Context;



์ถœ๋ ฅ

  • ์ด๋ฏธ์ง€๋กœ ๋Œ€์ฒด


๐Ÿ”— ์ฐธ๊ณ  ๋งํฌ & ๋„์›€์ด ๋˜๋Š” ๋งํฌ






profile
๊ณต๋ถ€ํ•˜๋Š” ๋ฒจ๋กœ๊ทธ

0๊ฐœ์˜ ๋Œ“๊ธ€