230519 - React(useCallback, useEffect, useReducer), SASS/SCSS(๋ฐ˜์‘ํ˜•)

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

๐Ÿšฉ React

useCallback

๐Ÿ“ ์„ค๋ช…

  • React Hooks ์ค‘ ํ•˜๋‚˜, useMemo์™€ ๋น„์Šท
  • useMemo๋Š” ํŠน์ • ๊ฒฐ๊ณผ๊ฐ’์„ ์žฌ์‚ฌ์šฉ, useCallbackํ•จ์ˆ˜๋ฅผ ์žฌ์‚ฌ์šฉ
  • ํ•จ์ˆ˜ ์•ˆ์—์„œ ์‚ฌ์šฉํ•˜๋Š” props๊ฐ€ ์žˆ์œผ๋ฉด ๊ผญ deps๋กœ ํฌํ•จ์‹œ์ผœ์•ผ ํ•จ -> ์ตœ์‹ ๊ฐ’์„ ์ธ์‹ํ•˜๊ฒŒ


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

  • useCallback(ํ•จ์ˆ˜์„ ์–ธ๊ตฌ, ๋ณ€ํ™”์š”์†Œ(dependency))

์˜ˆ์ œ ์ฝ”๋“œ ์ž…๋ ฅ

App.js

import { useRef, useState, useMemo, useCallback } 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๋ฅผ ์‚ฌ์šฉ

export default function App() {
  const initialUsers = [
    {
      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
    }
  ];

  const [users, setUsers] = useState(initialUsers); //๋ฐฐ์—ด์ „์ฒด
  const [inputs, setInputs] = useState({ username: "", email: "" }); // input(2๊ฐœ) ์•ˆ์˜ ๋‚ด์šฉ(value)์„ ์ž„์‹œ์ €์žฅํ•  ๋ฐฐ์—ด (username, email์€ ๊ธฐ์กด ๋ฐฐ์—ด์˜ ์ด๋ฆ„๊ณผ ๊ฐ™๊ฒŒ)

  // state์•ˆ์— ์žˆ๋Š” ์ƒํƒœ. ์œ„ ์•„๋ž˜ ๋‘˜ ๋‹ค ๊ฐ™์€ ํ‘œํ˜„
  // const username = inputs.username;
  // const email = inputs.email;
  const { username, email } = inputs;

  const changeText = useCallback(
    (event) => {
      // const name = event.target.name; // username, email ์ค‘ ํ•˜๋‚˜. (์–ด๋–ค input ์ธ์ง€)
      // const value = event.target.value; // input์— ์ž…๋ ฅ ๋œ ๊ฐ’
      const { name, value } = event.target;

      setInputs({ ...inputs, [name]: value });
      // console.log(inputs);
    },
    [inputs]
  );

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

  const onCreate = useCallback(
    (event) => {
      console.log("onCreate ํ•จ์ˆ˜ ์‹คํ–‰");

      const item = {
        id: nextId.current, // key: value
        username,
        email
      };

      setUsers([...users, item]); // ๋ฐฐ์—ด์— ์•„์ดํ…œ์„ ๋„ฃ๋Š” ํ•จ์ˆ˜ ์‹คํ–‰
      setInputs({ username: "", email: "" }); // input ์ดˆ๊ธฐํ™”

      nextId.current += 1;
      focusIn.current.focus();
    },
    [users, username, email]
  );

  // ์‚ญ์ œ ํ•จ์ˆ˜ ์ •์˜
  const remove = (id) => {
    // console.log("์•„์ด๋””๋Š”? : ", id);
    setUsers(users.filter((item) => item.id !== id)); // ๋‚ด๊ฐ€ ๊ณ ๋ฅธ id๊ฐ’์„ ๋นผ๊ณ  ๋‹ค์‹œ ๋ฐฐ์—ด์„ ๋งŒ๋“ฆ
  };

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

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

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

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

      <dl>
        <dt>useCallback</dt>
        <dd>React Hooks ์ค‘ ํ•˜๋‚˜, useMemo์™€ ๋น„์Šท</dd>
        <dd>useMemo๋Š” ํŠน์ • ๊ฒฐ๊ณผ๊ฐ’์„ ์žฌ์‚ฌ์šฉ, useCallbackํ•จ์ˆ˜๋ฅผ ์žฌ์‚ฌ์šฉ </dd>
        <dd>์‚ฌ์šฉ๋ฒ• - useCallback(ํ•จ์ˆ˜์„ ์–ธ๊ตฌ, ๋ณ€ํ™”์š”์†Œ(dependency))</dd>
        <dd>
          ํ•จ์ˆ˜ ์•ˆ์—์„œ ์‚ฌ์šฉํ•˜๋Š” props๊ฐ€ ์žˆ์œผ๋ฉด ๊ผญ deps๋กœ ํฌํ•จ์‹œ์ผœ์•ผ ํ•จ ->
          ์ตœ์‹ ๊ฐ’์„ ์ธ์‹ํ•˜๊ฒŒ{" "}
        </dd>
      </dl>
    </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}
        />
      ))}
    </>
  );
}

์ถœ๋ ฅ

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

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






useEffect

๐Ÿ“ ์„ค๋ช…

  • React Hooks์˜ ์ผ์ข…
  • ๋ผ์ดํ”„์‚ฌ์ดํด hook(์ƒ์• ์ฃผ๊ธฐ) - ๋งˆ์šดํŠธ / ์–ธ๋งˆ์šดํŠธ / ์—…๋ฐ์ดํŠธ (depth์˜ props)
  • depth๋ฅผ ๋น„์–ด์žˆ๋Š” ๋ฐฐ์—ด๋กœ ๋งŒ๋“ค์–ด ๋†“์œผ๋ฉด ์ฒ˜์Œ ํ•œ ๋ฒˆ๋งŒ ๋งˆ์šดํŠธ(๋กœ๋”ฉ) ๋จ
  • useEffect(์ฝœ๋ฐฑํ•จ์ˆ˜, depth
  • ์ฝœ๋ฐฑํ•จ์ˆ˜ ๋งˆ์ง€๋ง‰ ๋ถ€๋ถ„์— return ํ•จ์ˆ˜๋กœ ์–ธ๋งˆ์šดํŠธ ์„ค์ •


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

์ž…๋ ฅ

App.js

import "./App.scss";
import { useState } from "react";
import Products from "./Products";

function App() {
  const [showProducts, setShowProducts] = useState(true); // ์ดˆ๊ธฐ๊ฐ’

  return (
    <div className="App">
      <button onClick={() => setShowProducts((show) => !show)}>
        Toggle Button
      </button>
      

      {showProducts && <Products />}

      <hr />

      <div>
        <dl>
          <dt>useEffect</dt>
          <dd>React Hooks์˜ ์ผ์ข…</dd>
          <dd>๋ผ์ดํ”„์‚ฌ์ดํด hook(์ƒ์• ์ฃผ๊ธฐ) - ๋งˆ์šดํŠธ / ์–ธ๋งˆ์šดํŠธ / ์—…๋ฐ์ดํŠธ(depth์˜ props)</dd>
          <dd>depth๋ฅผ ๋น„์–ด์žˆ๋Š” ๋ฐฐ์—ด๋กœ ๋งŒ๋“ค์–ด ๋†“์œผ๋ฉด ์ฒ˜์Œ ํ•œ ๋ฒˆ๋งŒ ๋งˆ์šดํŠธ(๋กœ๋”ฉ) ๋จ</dd>
          <dd>useEffect(์ฝœ๋ฐฑํ•จ์ˆ˜, depth)</dd>
          <dd>- ์ฝœ๋ฐฑํ•จ์ˆ˜ ๋งˆ์ง€๋ง‰ ๋ถ€๋ถ„์— return ํ•จ์ˆ˜๋กœ ์–ธ๋งˆ์šดํŠธ ์„ค์ •</dd>
        </dl>
      </div>
    </div>
  );
}

export default App;



Products.jsx

import React, { useEffect, useState } from "react";

const Products = () => {
  const [products, setProducts] = useState([]); // ๋ถˆ๋Ÿฌ์˜จ jsonํŒŒ์ผ ์ƒํƒœ(์ „์ฒด์ œํ’ˆ)
  const [checked, setChecked] = useState(false); // ์ฒดํฌ๋ฐ•์Šค ์ƒํƒœ
  
  const change = () => {
    setChecked(!checked);
    console.log("์ฒดํฌ");
  }

  useEffect(() => {
    // fetch(checked ? "data/sale_products.json" : "data/products.json")
    fetch(`data/${checked ? "sale_" : ""}products.json`) // ์œ„์™€ ๊ฐ™์€ ์ฝ”๋“œ
      .then((res) => res.json())
      .then((data) => {
        // console.log("๋ฐ์ดํ„ฐ๋ฅผ ๋„คํŠธ์›Œํฌ์—์„œ ๋ฐ›์•„์™”์Œ๐Ÿ”ฅ : ", data);
        setProducts(data); //setProducts๊ฐ€ ๋ฐ›์•„์˜จ data๋ฅผ ์œ„์˜ products์— ์ง‘์–ด๋„ฃ์Œ
      });
      return () => { // ์–ธ๋งˆ์šดํŠธ ๋  ๋•Œ(ํ™”๋ฉด์—์„œ ์‚ฌ๋ผ์งˆ ๋•Œ) ์ฝœ๋ฐฑํ•˜๋Š” ํ•จ์ˆ˜
        console.log("toggle ๋ฒ„ํŠผ ํด๋ฆญ");
      }
  }, [checked]);
  // depth๋ฅผ []๋ฒˆ ๋ฐฐ์—ด๋กœ ํ•ด๋†“์„ ๋•Œ๋Š” ์ฒ˜์Œ ํ•œ ๋ฒˆ๋งŒ ๋ถˆ๋Ÿฌ์˜จ๋‹ค๋Š” ๋œป
  // depth์— checked๊ฐ€ ๋ณ€๊ฒฝ๋ ๋•Œ๋งˆ๋‹ค useEffect ์•ˆ์˜ ํ•จ์ˆ˜๊ฐ€ ๋ฐœ์ƒ

  return (
    <>
      {/* htmlFor - react jsx์—์„œ for ๋Œ€์‹  ์‚ฌ์šฉ */}
      <label htmlFor="sale">Show only Sale๐Ÿ”ฅ</label>
      <input type="checkbox" name="" id="sale" onChange={change} value={checked}/>
      <ul>
        {products.map(item => (
            <li>
              <article>
                <img src={item.url} alt="" />
                <h3>{item.name}</h3>
                <p>{item.price}</p>
              </article>
            </li>
          )
        )}
      </ul>
    </>
  );
};

export default Products;



์ถœ๋ ฅ

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

toggle

checkbox


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






useReducer

๐Ÿ“ ์„ค๋ช…

  • useState์ฒ˜๋Ÿผ ์ƒํƒœ๊ด€๋ฆฌ ์‹œ ์‚ฌ์šฉ๋จ
  • ์ด Hook ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ปดํฌ๋„ŒํŠธ์˜ ์ƒํƒœ ์—…๋ฐ์ดํŠธ ๋กœ์ง์„ ์ปดํฌ๋„ŒํŠธ์—์„œ ๋ถ„๋ฆฌ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Œ
  • ํ˜„์žฌ ์ƒํƒœ์™€ ์•ก์…˜ ๊ฐ์ฒด๋ฅผ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋ฐ›์•„ ์™€ ์ƒˆ๋กœ์šด ์ƒํƒœ๋ฅผ ๋ฐ˜ํ™˜


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

์ž…๋ ฅ

App.js

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

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



Counter.jsx

import React, { useReducer } from "react";

// reducer ํ•จ์ˆ˜ ์„ ์–ธ
function reducer(state, action) {
  // ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ๋กœ์ง
  switch (action.type) {
    case "INCREMENT":
      return state + 1;
    case "DECREMENT":
      return state - 1;
    default:
      // return state;
      throw new Error("์—๋Ÿฌ ๋ฐœ์ƒ");
  }
}

export default function Counter() {
  // ๋ˆ„๋ฅผ ๋•Œ ๋งˆ๋‹ค ์ˆซ์ž๊ฐ€ ์ฆ๊ฐ€๋˜๊ฒŒ ํ•จ
  const [number, dispatch] = useReducer(reducer, 0);
  // const [ํ˜„์žฌ์ƒํƒœ(state), ์•ก์…˜์„ ๋ฐœ์ƒ์‹œํ‚ค๋Š” ํ•จ์ˆ˜] = useReducer(์—ฐ๊ฒฐ๋˜๋Š” ํ•จ์ˆ˜, state์˜ ์ดˆ๊ธฐ๊ฐ’);
  // 2๊ฐœ์˜ ์ธ์ž๋ฅผ ๊ฐ€์ง

  const increase = () => {
    dispatch({ type: "INCREMENT" }); // dispatch(์•ก์…˜์„ ๋ณด๋‚ด์คŒ), type์„ ๋ณด๋‚ด์คŒ. type์€ ๋Œ€๋ฌธ์ž๋กœ ์ž‘์„ฑ
  };
  const decrease = () => {
    dispatch({ type: "DECREMENT" });
  };

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



์ถœ๋ ฅ

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



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






๐Ÿšฉ SASS/SCSS

๋ฐ˜์‘ํ˜•(๋ฏธ๋””์–ด์ฟผ๋ฆฌ)

๐Ÿ“ ์„ค๋ช…

  • scss์—์„œ ๋ฐ˜์‘ํ˜•์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•


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

์ž…๋ ฅ

html

<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="stylesheet" href="./style/main.css">
  <title>๋ฐ˜์‘ํ˜•</title>
</head>
<body>
  <div></div>
</body>
</html>



scss

$mobile: "(max-width: 767px)";
$tablet: "(min-width: 768px) and (max-width: 1200px)";
$pc: "(min-width: 1201px)";

div {
  width: 200px;
  height: 200px;
  background: pink;
  transition: .3s;

  @media #{$mobile} {
    background: red;
  }
  @media #{$tablet} {
    background: lightblue;
    width: 100px;
    height: 100px;
  }
  @media #{$pc} {
    background: greenyellow;
    width: 70vw;
    height: 70vh;
  }
}



์ถœ๋ ฅ

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

pc

tablet

mobile


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






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

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