[React] (사진 수정 필요) 7. Context

ㅎㅎ·2023년 8월 1일
0

React

목록 보기
9/11

Context란?

두 컴포넌트의 상태값을 공유해야 한다면?

usestate는 해당 컴포넌트에서 재사용되지 다수의 컴포넌트를 할수는 없음

⇒ 이상적 해결 방법은 가장 근접한 부모 컴포넌트로 state를 옮기고 아래 컴포넌트에 props로 전달 하는 방법

모든 컴포넌트마다 props로 전달하면 비효율적

Context API

모든 컴포넌트들이 필요하다면, 어플리케이션 전반적으로 필요한 경우 사용할 수 있음. 변경되면 해당 상태가 있는 모든 컴포넌트들이 모두 리렌더링 됨

ex) 언어, 테마(다크모드), 로그인

빈번히 업데이트 되는 상태는 context api를 사용하지 않음 → 대신 umbrella 사용

umbrella

원하는 컴포넌트 트리 중간에 사용할 수 있음 그 언더에 있는 컴포넌트들이 모두 리렌더링 됨

테마 같은 건 전체에 씌우기

다크모드 Context 만들기

  • AppTheme.jsx
import React, { useContext } from "react";
import "./AppTheme.css";
import { DarkModeContext, DarkModeProvider } from "./context/DarkModeContext";

export default function AppTheme() {
  return (
    <DarkModeProvider>
      <Header />
      <Main />
      <Footer />
    </DarkModeProvider>
  );
}

function Header() {
  return <header className="header">Header</header>;
}

function Main() {
  return (
    <main className="main">
      Main
      <Profile />
      <Products />
    </main>
  );
}

function Profile() {
  return (
    <div>
      Profile
      <User />
    </div>
  );
}

function User() {
  return <div>User</div>;
}

function Products() {
  return (
    <div>
      Products
      <ProductDetail />
    </div>
  );
}

function ProductDetail() {
  const { darkMode, toggleDarkMode } = useContext(DarkModeContext);
  return (
    <div>
      Product Detail
      <p>
        DarkMode:
        {darkMode ? (
          <span style={{ backgroundColor: "black", color: "white" }}>
            Dark Mode
          </span>
        ) : (
          <span>Light Mode</span>
        )}
      </p>
      <button onClick={() => toggleDarkMode()}>Toggle</button>
    </div>
  );
}

function Footer() {
  return <footer className="footer">Footer</footer>;
}
  • DarkModeProvider.jsx
import { createContext, useState } from "react";

export const DarkModeContext = createContext(); // 데이터를 컨텍스에 담고 있다

export function DarkModeProvider({ children }) {
  // 프로바이더는 데이터를 잘 보여주고 있는 우산을 만든다
  const [darkMode, setDarkMode] = useState(false);
  const toggleDarkMode = () => setDarkMode((mode) => !mode);
  return (
    <DarkModeContext.Provider value={{ darkMode, toggleDarkMode }}>
      {children}
    </DarkModeContext.Provider>
  );
}

footer에서만 쓰면 위처럼 그 위만 감싸주면 된다

성능 개선에 대한 단상

import React, { useReducer } from "react";
import personReducer from "./reducer/person-reducer";

export default function AppMentorsButton() {
  const [person, dispatch] = useReducer(personReducer, initialPerson);

  const handleUpdate = () => {
    const prev = prompt(`누구의 이름을 바꾸고 싶은가요?`);
    const current = prompt(`이름을 무엇으로 바꾸고 싶은가요?`);
    dispatch({ type: "updated", prev, current });
  };

  const handleAdd = () => {
    const name = prompt(`멘토의 이름은?`);
    const title = prompt(`멘토의 직함은?`);
    dispatch({ type: "added", name, title });
  };

  const handleDelete = () => {
    const name = prompt(`누구를 삭제하고 싶은가요?`);
    dispatch({ type: "deleted", name });
  };

  return (
    <div>
      <h1>
        {person.name}{person.title}
      </h1>
      <p>{person.name}의 멘토는:</p>
      <ul>
        {person.mentors.map((mentor, index) => (
          <li key={index}>
            {mentor.name} ({mentor.title})
          </li>
        ))}
      </ul>
      {/* <button onClick={handleUpdate}>멘토의 이름을 바꾸기</button>
      <button onClick={handleAdd}>멘토 추가하기</button>
      <button onClick={handleDelete}>멘토 삭제하기</button> */}
      <Button text="멘토 이름 바꾸기" onClick={handleUpdate} />
      <Button text="삭제하기" onClick={handleDelete} />
      <Button text="멘토 추가하기" onClick={handleAdd} />
    </div>
  );
}

function Button({ text, onClick }) {
  console.log("Button", text, "re-rendering 😜");
  return (
    <button
      onClick={onClick}
      style={{
        backgroundColor: "black",
        color: "white",
        borderRadius: "20px",
        margin: "0.4rem",
      }}
    >
      {text}
    </button>
  );
}

const initialPerson = {
  name: "엘리",
  title: "개발자",
  mentors: [
    {
      name: "밥",
      title: "시니어개발자",
    },
    {
      name: "제임스",
      title: "시니어개발자",
    },
  ],
};

새롭게 person이 업데이트 될 때마다 컴포넌트 다시 업데이트ㅏ라면서 앱멘토버튼 함수 다시 호출

함수가 호출이 될 때마다 새롭게 만들어진 변수가 다시 업데이트가 됨

매번 새로운 props로 전달한대

헷갈려...

ㅎ.ㅎ… 귀에 안들어옴 우선은 이런 것이 있다는 것만 알아두고 나중에 다시 보자.

성능 개선해보기

1. 컴포넌트 안에서 맨 처음에만 개선해야 한다면 : useMemo()

이거 기억해둬 : 딱 한 번만 사용됨

만약 text가 변경이 되면 다시 실행해야한다면 이렇게 사용할 수 있음

⇒ 무거운 내용이 처음만 하고 나중엔 실행되지 않길 바란다면 useMemo를 사용할 수 있다

2. 함수가 매번 새로 만들어지는 것을 개선: useCallback()

딱 한 번만 콜백함수를 만들어줄

3. 값이 전달될 때항상 새로운 props 객체가 만들어지더라도 비교해서 이전과 같은 값이라면 리렌더링을 하지 않도록

버튼이라는 컴포넌트를 선언식으로 만들어줌

처음부터 유즈메모쓰고 유즈콜백써서 코드를 복잡하게 만들 필요는 없음

  • AppMentorsButton.jsx
import React, { memo, useCallback, useMemo, useReducer } from 'react';
import personReducer from './reducer/person-reducer';

export default function AppMentorsButton() {
  const [person, dispatch] = useReducer(personReducer, initialPerson);

  const handleUpdate = useCallback(() => {
    const prev = prompt(`누구의 이름을 바꾸고 싶은가요?`);
    const current = prompt(`이름을 무엇으로 바꾸고 싶은가요?`);
    dispatch({ type: 'updated', prev, current });
  }, []);

  const handleAdd = useCallback(() => {
    const name = prompt(`멘토의 이름은?`);
    const title = prompt(`멘토의 직함은?`);
    dispatch({ type: 'added', name, title });
  }, []);

  const handleDelete = useCallback(() => {
    const name = prompt(`누구를 삭제하고 싶은가요?`);
    dispatch({ type: 'deleted', name });
  }, []);

  return (
    <div>
      <h1>
        {person.name}{person.title}
      </h1>
      <p>{person.name}의 멘토는:</p>
      <ul>
        {person.mentors.map((mentor, index) => (
          <li key={index}>
            {mentor.name} ({mentor.title})
          </li>
        ))}
      </ul>
      <Button text='멘토 이름 바꾸기' onClick={handleUpdate} />
      <Button text='삭제하기' onClick={handleDelete} />
      <Button text='멘토 추가하기' onClick={handleAdd} />
    </div>
  );
}

const Button = memo(({ text, onClick }) => {
  console.log('Button', text, 're-rendering 😜');
  const result = useMemo(() => calculateSomething(), []);
  return (
    <button
      onClick={onClick}
      style={{
        backgroundColor: 'black',
        color: 'white',
        borderRadius: '20px',
        margin: '0.4rem',
      }}
    >
      {`${text} ${result}`}
    </button>
  );
});

function calculateSomething() {
  for (let i = 0; i < 10000; i++) {
    console.log('😆');
  }
  return 10;
}

const initialPerson = {
  name: '엘리',
  title: '개발자',
  mentors: [
    {
      name: '밥',
      title: '시니어개발자',
    },
    {
      name: '제임스',
      title: '시니어개발자',
    },
  ],
};

🔥 로딩, 에러 상태 추가

참고용 코드

로딩과 에러를 간직할 수 있는 state를 만들어야함

  • Products.jsx
import React, { useEffect, useState } from "react";

export default function Products() {
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState();
  const [products, setProducts] = useState([]);
  const [checked, setChecked] = useState(false);
  const handleChange = () => setChecked((prev) => !prev); // 이전 값을 반대로 토글링

  useEffect(() => {
    setLoading(true);
    setError(undefined);
    fetch(`data/${checked ? "sale_" : ""}products.json`)
      .then((res) => res.json())
      .then((data) => {
        console.log("🔥뜨끈한 데이터를 네트워크에서 받아옴");
        setProducts(data);
      })
      .catch((e) => setError("에러가 발생했음!"))
      .finally(() => setLoading(false));
    return () => {
      console.log("🧹 깨끗하게 청소하는 일들을 합니다.");
    };
  }, [checked]);

  if (loading) return <p>Loading...</p>;

  if (error) return <p>{error}</p>;
  return (
    <>
      <input
        id="checkbox"
        type="checkbox"
        value={checked}
        onChange={handleChange}
      />
      <label htmlFor="checkbox">Show Only 🔥 Sale</label>
      <ul>
        {products.map((product) => (
          <li key={product.id}>
            <article>
              <h3>{product.name}</h3>
              <p>{product.price}</p>
            </article>
          </li>
        ))}
      </ul>
    </>
  );
}

로딩

에러

.catch((e)!!!!

커스텀 훅 만들기

리액트에서 훅은 그냥 함수, function으로 시작한다.
훅은 컴포넌트와 마찬가지로 내부 usestate나 useeffect를 사용가능

다 똑같지만 다른점은,

  • 일반 컴포넌트는 리액트에게 전달해줄 ui 반환 jsx를 사용
  • 커스텀 훅은 외부 사람들과 공개하고 싶은 원하는 데이터를 공유

훅은 값의 재사용이 아니라 로직의 재사용을 위한 것이다

클래스 컴포넌트

클래스형 컴포넌트 (전통적)

우선 넘어간다.

profile
Backend

0개의 댓글