[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개의 댓글

관련 채용 정보