๐
38์ผ์ฐจ
Part 10. React ๋ก ์ผํ๋ชฐ ๋ง๋ค๊ธฐ (React ๊ธฐ๋ณธ)
Ch 7. Hooks & Context
Ch 6. Hooks & Context
๐ Basic Hooks : ์ปดํฌ๋ํธ์ state์ ๊ด๋ จ๋ ๋ก์ง์ ์ฌ์ฌ์ฉํ ์ ์๋ค.
Functional Component != Stateless Componet because state hooks
https://ko.reactjs.org/docs/hooks-intro.html
- useState: state ๋ฅผ ๋์ฒดํ ์ ์๋ค.
// ./App.js
...
import Example1 from "./components/Example1";
import Example2 from "./components/Example2";
import Example3 from "./components/Example3";
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<Example1 />
<Example2 />
<Example3 />
</header>
</div>
);
}
export default App;
// ./components/Example1.jsx
// Class Component
import React from "react";
export default class Example1 extends React.Component {
state = { count: 0 };
render() {
const { count } = this.state;
return (
<div>
<p>You clicked {count} times</p>
<button onClick={this.click}>Click me</button>
</div>
);
}
click = () => {
this.setState({ count: this.state.count + 1 });
};
}
// ./component/Example2.jsx
// Functional Component
import React from "react";
export default function Example2() {
const [count, setCount] = React.useState(0); // Hooks
return (
<div>
<p>You clicked {count} times</p>
<button onClick={click}>Click me</button>
</div>
);
function click() {
setCount(count + 1);
}
}
// ./components/Example3.jsx
// Functional Compnent - Object
import React from "react";
// useState => count
// useState => {count: 0}
export default function Example3() {
const [state, setState] = React.useState({ count: 0 }); // Hooks
return (
<div>
<p>You clicked {state.count} times</p>
<button onClick={click}>Click me</button>
</div>
);
function click() {
setState((state) => ({
count: state.count + 1,
}));
}
}
- useEffect: ๋ผ์ดํ ์ฌ์ดํด ํ ์ ๋์ฒดํ ์ ์๋ค. (componentDidMount, componentDidUpdate, componentWillUnmont)
https://rinae.dev/posts/a-complete-guide-to-useeffect-ko
// ./App.jsx
...
import Example4 from "./components/Example4";
import Example5 from "./components/Example5";
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<Example4 />
<Example5 />
</header>
</div>
);
}
export default App;
// ./components/Example4.jsx
// Class Component
import React from "react";
export default class Example4 extends React.Component {
state = { count: 0 };
render() {
const { count } = this.state;
return (
<div>
<p>You clicked {count} times</p>
<button onClick={this.click}>Click me</button>
</div>
);
}
componentDidMount() {
console.log("componenDidMount", this.state.count);
}
componentDidUpdate() {
console.log("componentDidUpdate", this.state.count);
}
click = () => {
this.setState({ count: this.state.count + 1 });
};
}
// ./component/Example5.jsx
// Functional Component
import React from "react";
export default function Example5() {
const [count, setCount] = React.useState(0); // Hooks
// ์ต์ด
React.useEffect(() => {
console.log("componentDidMount");
return () => {
// cleanup
// componentWillUnmount
};
}, []);
// ์ต์ด & count๊ฐ ์
๋ฐ์ดํธ ๋ ๋๋ง๋ค
React.useEffect(() => {
console.log("componentDidMount & componentDidUpdate by count", count);
return () => {
//cleanup
console.log("cleanup by count", count);
};
}, [count]);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={click}>Click me</button>
</div>
);
function click() {
setCount(count + 1);
}
}
- useContext
๐ Custom Hooks (useSomething)
// ./App.js
...
import useWindowWith from "./hooks/useWindowWidth";
function App() {
const width = useWindowWith();
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
{width}
</header>
</div>
);
}
export default App;
// ./hooks/useWindowWidth.js
import React, { useEffect } from "react";
export default function useWindowWith() {
const [width, setWidth] = React.useState(window.innerWidth);
useEffect(() => {
const resize = () => {
setWidth(window.innerWidth);
};
window.addEventListener("resize", resize);
return () => {
window.removeEventListener("resize", resize);
};
}, []);
return width;
}
- useHasMouted vs withHasMounted
...
import withHasMounted from "./hocs/withHasMounted";
import useHasMounted from "./hooks/useHasMounted";
function App({ hasMounted }) {
const width = useWindowWith();
const hasMountedFromHooks = useHasMounted();
console.log(hasMounted, hasMountedFromHooks);
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
</header>
</div>
);
}
export default withHasMounted(App);
// ./hooks/useHasMounted.js
import { useEffect, useState } from "react";
export default function useHasMounted() {
const [hasMounted, setHasMounted] = useState(false);
useEffect(() => {
setHasMounted(true);
}, []);
return hasMounted;
}
// ./hocs/withHasMounted.jsx
import React from "react";
export default function withHasMounted(Component) {
class NewComponent extends React.Component {
state = {
hasMounted: false,
};
render() {
const { hasMounted } = this.state;
return <Component {...this.props} hasMounted={hasMounted} />;
}
componentDidMount() {
this.setState({ hasMounted: true });
}
}
NewComponent.displayName = `withHasMountder(${Component.name})`;
return NewComponent;
}
๐ Additional Hooks
render ์ฌ์ด์ ์ด๋ค ์ํ๋ฅผ ์ ์งํด์ค๋ค.
- useReducer
// ./App.js
import Example6 from "./components/Example6";
import useWindowWith from "./hooks/useWindowWidth";
import withHasMounted from "./hocs/withHasMounted";
import useHasMounted from "./hooks/useHasMounted";
function App({ hasMounted }) {
const width = useWindowWith();
const hasMountedFromHooks = useHasMounted();
console.log(hasMounted, hasMountedFromHooks);
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<Example6 />
</header>
</div>
);
}
export default withHasMounted(App);
import { useReducer } from "react";
// reducer: state ๋ฅผ ๋ณ๊ฒฝํ๋ ๋ก์ง์ด ๋ด๊ฒจ ์๋ ํจ์
const reducer = (state, action) => {
if (action.type === "PLUS") {
return {
count: state.count + 1,
};
}
return state;
};
// dispatch: action ๊ฐ์ฒด๋ฅผ ๋ฃ์ด์ ์คํ
// action: ๊ฐ์ฒด์ด๊ณ ํ์ ํ๋กํผํฐ๋ก type ์ ๊ฐ์ง๋ค.
export default function Example6() {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<div>
<p>You clicked {state.count} times</p>
<button onClick={click}>Click me</button>
</div>
);
function click() {
dispatch({ type: "PLUS" });
}
}
- useCallback, useMemo
// ./App.js
import Example7 from "./components/Example6";
import useWindowWith from "./hooks/useWindowWidth";
import withHasMounted from "./hocs/withHasMounted";
import useHasMounted from "./hooks/useHasMounted";
function App({ hasMounted }) {
const width = useWindowWith();
const hasMountedFromHooks = useHasMounted();
console.log(hasMounted, hasMountedFromHooks);
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<Example7 />
</header>
</div>
);
}
export default withHasMounted(App);
// ./components/Example7.jsx
import { useCallback, useMemo, useState } from "react";
function sum(persons) {
console.log("sum...");
return persons.map((person) => person.age).reduce((l, r) => l + r, 0);
}
export default function Example7() {
const [value, setValue] = useState("");
const [persons] = useState([
{ name: "Mark", age: 39 },
{ name: "Hanna", age: 28 },
]);
const count = useMemo(() => {
return sum(persons);
}, [persons]);
const click = useCallback(() => {
console.log(value);
}, []);
return (
<div>
<input value={value} onChange={change} />
<p>{count}</p>
<button onClick={click}>click</button>
</div>
);
function change(e) {
setValue(e.target.value);
}
}
- useRef, useImperativeHandle
// ./App.js
import Example8 from "./components/Example6";
import useWindowWith from "./hooks/useWindowWidth";
import withHasMounted from "./hocs/withHasMounted";
import useHasMounted from "./hooks/useHasMounted";
function App({ hasMounted }) {
const width = useWindowWith();
const hasMountedFromHooks = useHasMounted();
console.log(hasMounted, hasMountedFromHooks);
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<Example8 />
</header>
</div>
);
}
export default withHasMounted(App);
// ./components/Example8.jsx
import { createRef, useRef, useState } from "react";
export default function Example8() {
const [value, setValue] = useState("");
const input1Ref = createRef();
const input2Ref = useRef();
console.log(input1Ref.current, input2Ref.current);
return (
<div>
<input value={value} onChange={change} />
<input ref={input1Ref} />
<input ref={input2Ref} />
</div>
);
function change(e) {
setValue(e.target.value);
}
}
- useLayoutEffect
- useDebugValue
๐ React Router Hooks
https://v5.reactrouter.com/web/api/Hooks
๐ ์ปดํฌ๋ํธ ๊ฐ ํต์
- ํ์ ์ปดํฌ๋ํธ๋ฅผ ๋ณ๊ฒฝํ๊ธฐ
// ./App.js
import logo from "./logo.svg";
import "./App.css";
import A from "./components/A";
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<A />
</header>
</div>
);
}
export default App;
// ./components/A.jsx
import { useState } from "react";
export default function A() {
const [value, setValue] = useState("์์ง ์๋ฐ๋");
return (
<div>
<B value={value} />
<button onClick={click}>E ์ ๊ฐ์ ๋ฐ๊พธ๊ธฐ</button>
</div>
);
function click() {
setValue("E ์ ๊ฐ์ ๋ณ๊ฒฝ");
}
}
function B({ value }) {
return (
<div>
<p>์ฌ๊ธด B</p>
<C value={value} />
</div>
);
}
function C({ value }) {
return (
<div>
<p>์ฌ๊ธด C</p>
<D value={value} />
</div>
);
}
function D({ value }) {
return (
<div>
<p>์ฌ๊ธด D</p>
<E value={value} />
</div>
);
}
function E({ value }) {
return (
<div>
<p>์ฌ๊ธด E</p>
<h3>{value}</h3>
</div>
);
}
- ์์ ์ปดํฌ๋ํธ๋ฅผ ๋ณ๊ฒฝํ๊ธฐ
// ./components/A.jsx
import { useState } from "react";
export default function A() {
const [value, setValue] = useState("์์ง ์๋ฐ๋");
return (
<div>
<p>{value}</p>
<B setValue={setValue} />
</div>
);
}
function B({ setValue }) {
return (
<div>
<p>์ฌ๊ธด B</p>
<C setValue={setValue} />
</div>
);
}
function C({ setValue }) {
return (
<div>
<p>์ฌ๊ธด C</p>
<D setValue={setValue} />
</div>
);
}
function D({ setValue }) {
return (
<div>
<p>์ฌ๊ธด D</p>
<E setValue={setValue} />
</div>
);
}
function E({ setValue }) {
return (
<div>
<p>์ฌ๊ธด E</p>
<button onClick={click}>ํด๋ฆญ</button>
</div>
);
function click() {
setValue("A ์ ๊ฐ์ ๋ณ๊ฒฝ");
}
}
๐ ContextAPI : ํ์ ์ปดํฌ๋ํธ ์ ์ฒด์ ๋ฐ์ดํฐ๋ฅผ ๊ณต์ ํ๋ ๋ฒ
https://ko.reactjs.org/docs/context.html
// ./App.jsx
import logo from "./logo.svg";
import "./App.css";
import Example1 from "./components/Example1";
import Example2 from "./components/Example2";
import Example3 from "./components/Example3";
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<Example1 />
<Example2 />
<Example3 />
</header>
</div>
);
}
export default App;
- ๋ฐ์ดํฐ๋ฅผ Set ํ๊ธฐ (๊ฐ์ฅ ์์ ์ปดํฌ๋ํธ => ํ๋ก๋ฐ์ด๋)
- ์ผ๋จ ์ปจํ ์คํธ๋ฅผ ์์ฑํ๋ค.
- ์ปจํ ์คํธ.ํ๋ก๋ฐ์ด๋๋ฅผ ์ฌ์ฉํ๋ค.
- value ๋ฅผ ์ฌ์ฉ
// ./contexts/PersonContext.js
import React from "react";
const PersonContext = React.createContext();
export default PersonContext;
// ./index.js
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import PersonContext from "./contexts/PersonContext";
const persons = [
{ id: 0, name: "Mark", age: 39 },
{ id: 1, name: "Hanna", age: 28 },
];
ReactDOM.render(
<React.StrictMode>
<PersonContext.Provider value={persons}>
<App />
</PersonContext.Provider>
</React.StrictMode>,
document.getElementById("root")
);
...
- ๋ฐ์ดํฐ๋ฅผ Get ํ๊ธฐ (1) - Cousumer
- ์ปจํ ์ค๋ฅผ ๊ฐ์ ธ์จ๋ค.
- ์ปจํ ์คํธ.์ปจ์๋จธ๋ฅผ ์ฌ์ฉํ๋ค.
- value ๋ฅผ ์ฌ์ฉ
// ./components/Example1.jsx
import PersonContext from "../contexts/PersonContext";
export default function Example1() {
return (
<PersonContext.Consumer>
{(persons) => (
<ul>
{persons.map((person) => (
<li>{person.name}</li>
))}
</ul>
)}
</PersonContext.Consumer>
);
}
- ๋ฐ์ดํฐ๋ฅผ Get ํ๊ธฐ (2) - class
- static contextType ์ ์ปจํ ์คํธ๋ฅผ ์ค์ ํ๋ค.
- this.context => value ์ด๋ค.
// ./components/Example2.jsx
import React from "react";
import PersonContext from "../contexts/PersonContext";
export default class Example2 extends React.Component {
// static contextType = PersonContext;
render() {
const persons = this.context;
return (
<ul>
{persons.map((person) => (
<li>{person.name}</li>
))}
</ul>
);
}
}
Example2.contextType = PersonContext;
- ๋ฐ์ดํฐ๋ฅผ Get ํ๊ธฐ (3) - functional โญ
- useContext ๋ก ์ปจํ ์คํธ๋ฅผ ์ธ์๋ก ํธ์ถํ๋ค.
- useContext ์ ๋ฆฌํด์ด value ์ด๋ค.
// ./components/Example3.jsx
import { useContext } from "react";
import PersonContext from "../contexts/PersonContext";
export default function Example3() {
const persons = useContext(PersonContext);
return (
<ul>
{persons.map((person) => (
<li>{person.name}</li>
))}
</ul>
);
}