usestate는 해당 컴포넌트에서 재사용되지 다수의 컴포넌트를 할수는 없음
⇒ 이상적 해결 방법은 가장 근접한 부모 컴포넌트로 state를 옮기고 아래 컴포넌트에 props로 전달 하는 방법
모든 컴포넌트마다 props로 전달하면 비효율적
모든 컴포넌트들이 필요하다면, 어플리케이션 전반적으로 필요한 경우 사용할 수 있음. 변경되면 해당 상태가 있는 모든 컴포넌트들이 모두 리렌더링 됨
ex) 언어, 테마(다크모드), 로그인
빈번히 업데이트 되는 상태는 context api를 사용하지 않음 → 대신 umbrella 사용
원하는 컴포넌트 트리 중간에 사용할 수 있음 그 언더에 있는 컴포넌트들이 모두 리렌더링 됨
테마 같은 건 전체에 씌우기
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>;
}
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로 전달한대
헷갈려...
ㅎ.ㅎ… 귀에 안들어옴 우선은 이런 것이 있다는 것만 알아두고 나중에 다시 보자.
이거 기억해둬 : 딱 한 번만 사용됨
만약 text가 변경이 되면 다시 실행해야한다면 이렇게 사용할 수 있음
⇒ 무거운 내용이 처음만 하고 나중엔 실행되지 않길 바란다면 useMemo를 사용할 수 있다
딱 한 번만 콜백함수를 만들어줄
버튼이라는 컴포넌트를 선언식으로 만들어줌
처음부터 유즈메모쓰고 유즈콜백써서 코드를 복잡하게 만들 필요는 없음
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를 만들어야함
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를 사용가능
다 똑같지만 다른점은,
훅은 값의 재사용이 아니라 로직의 재사용을 위한 것이다
클래스형 컴포넌트 (전통적)
우선 넘어간다.