Hook
Hook ( κ°κ³ 리 )
: λΎ°μ‘±νκ³ κ΅¬λΆλ¬μ§ λ λ‘ λ€λ₯Έ 물체μ κ±Έμ΄ μ°κ²°νλ λꡬ
κ°κ³ 리λ 물건μ μ‘κ±°λ κ±ΈκΈ° μν΄ μ¬μ©νλ λꡬμ
λλ€. hookμ 리μ‘νΈ μ»΄ν¬λνΈμμ μνλ λΌμ΄νμ¬μ΄ν΄μ μ‘κ±°λ κ±Έμ΄μ μ¬μ©ν μ μλ λꡬλΌλ μλ―Έμμ μ λλμμ΅λλ€.
μ»΄ν¬λνΈμμ μνμ λΌμ΄νμ¬μ΄ν΄ κ΄λ¦¬λ₯Ό μν ν¨μν νλ‘κ·Έλλ° λ°©μμ μ 곡νλ API
리μ‘νΈ 16.8 λ²μ λΆν° λμ
hook μ’ λ₯
β’
μν κ΄λ¦¬μ© hook
β’
λΌμ΄νμ¬μ΄ν΄ κ΄λ¦¬μ© hook
β’
κΈ°ν hook
μνκ΄λ¦¬ hook
μ΄λ¦ | μ€λͺ
|
useState() | μνλ₯Ό κ΄λ¦¬νκΈ° μν κΈ°λ³Έ hookμ
λλ€. |
useReducer() | μνλ₯Ό 볡μ‘νκ² κ΄λ¦¬νκΈ° μν hookμ
λλ€. |
useContext() | λΆλͺ¨ μ»΄ν¬λνΈμ μνλ₯Ό μμ μ»΄ν¬λνΈμμ 곡μ νκΈ° μν hookμ
λλ€. |
λΌμ΄νμ¬μ΄ν΄ hook
μ΄λ¦ | μ€λͺ
|
useEffect() | μ»΄ν¬λνΈμ λΌμ΄νμ¬μ΄ν΄ μ΄λ²€νΈλ₯Ό μ²λ¦¬νκΈ° μν hookμ
λλ€. |
useLayoutEffect() | μ»΄ν¬λνΈμ λ μ΄μμ μ΄λ²€νΈλ₯Ό μ²λ¦¬νκΈ° μν hookμ
λλ€. |
useRef() | μ»΄ν¬λνΈμ DOM μμλ 리μ‘νΈ ν
μ μνλ₯Ό μ°Έμ‘°νκΈ° μν hookμ
λλ€. |
κΈ°ν hook
μ΄λ¦ | μ€λͺ
|
useMemo() | μ»΄ν¬λνΈκ° λ λλ§λ λλ§λ€ κ°μ κ³μ°νμ§ μκ³ , μ΄μ μ κ³μ°ν κ°μ μ¬μ¬μ©νκΈ° μν hookμ
λλ€. |
useCallback() | μ»΄ν¬λνΈκ° λ λλ§λ λλ§λ€ ν¨μλ₯Ό μ¬μ€ννμ§ μκ³ , μ΄μ μ μ€νν ν¨μλ₯Ό μ¬μ¬μ©νκΈ° μν hookμ
λλ€. |
useImperativeHandle() | μ»΄ν¬λνΈμ μνλ λΌμ΄νμ¬μ΄ν΄μ imperativeνκ² κ΄λ¦¬νκΈ° μν hookμ
λλ€. |
hook μ΄ λμ λ μ΄μ
리μ‘νΈ 16.8 μ΄μ λ²μ μμλ ν΄λμ€ν μ»΄ν¬λνΈμμλ§ μνκ΄λ¦¬ λ° λΌμ΄νμ¬μ΄ν΄ λ©μλλ₯Ό μ¬μ©ν μ μμμ΅λλ€.
μ¦, ν¨μν μ»΄ν¬λνΈμμλ μνκ΄λ¦¬ λ° λΌμ΄νμ¬μ΄ν΄ λ©μλλ₯Ό μ¬μ©ν λ°©λ²μ΄ μμμ΅λλ€.
ν¨μν μ»΄ν¬λνΈμμ μν λ° λΌμ΄νμ¬μ΄ν΄ κ΄λ ¨ λ‘μ§μ λ κ°νΈνκ² μμ±νκ³ μ¬μ¬μ© κ°λ₯ν μ½λλ₯Ό μ½κ² λ§λ€κΈ° μν¨μ
λλ€. μ΄μ μλ ν΄λμ€ν μ»΄ν¬λνΈμμλ§ μν(state) λ° λΌμ΄νμ¬μ΄ν΄ κ΄λ ¨ λ‘μ§μ λ€λ£° μ μμμ§λ§, Hooksκ° λμ
λλ©΄μ ν¨μν μ»΄ν¬λνΈμμλ μ΄λ¬ν κΈ°λ₯λ€μ μ¬μ©ν μ μκ² λμμ΅λλ€.
hook μ₯μ
hookμ μ¬μ©νλ©΄ λ€μκ³Ό κ°μ μ₯μ μ΄ μμ΅λλ€.
β’
리μ‘νΈ μ»΄ν¬λνΈμ μ½λλ₯Ό λ κ°κ²°νκ³ κ°λ
μ± μκ² μμ±ν μ μμ΅λλ€.
β’
μν κ΄λ¦¬μ λΌμ΄νμ¬μ΄ν΄ κ΄λ¦¬λ₯Ό λ μ½κ² ꡬνν μ μμ΅λλ€.
β’
리μ‘νΈ μ»΄ν¬λνΈλ₯Ό λ μ μ°νκ² μ¬μ©ν μ μμ΅λλ€.
hookμ 리μ‘νΈ μ»΄ν¬λνΈ κ°λ°μ λ³΄λ€ νΈλ¦¬νκ³ ν¨μ¨μ μΌλ‘ ν μ μλλ‘ ν΄μ£Όλ μ μ©ν κΈ°λ₯μ
λλ€.
ν΄λμ€ μ»΄ν¬λνΈμ λΌμ΄νμ¬μ΄ν΄ λ©μλ vs hooks
Class Component
Lifecycle Method | Hooks |
componentDidMount | useEffect(() => {}, []) |
componentDidUpdate | useEffect(() => {}, [/* dependency array */]) |
componentWillUnmount | useEffect(() => { return () => {} }, []) |
getDerivedStateFromProps | useStateλ₯Ό μ¬μ©νμ¬ μν μ
λ°μ΄νΈ` |
shouldComponentUpdate | μ΅μ ν κΈ°μ μ¬μ© (μ: React.memo) |
getSnapshotBeforeUpdate | useEffect λλ useLayoutEffect λ±μ μ¬μ©νμ¬ DOM μν μ²λ¦¬` |
componentDidMount
β’
Class Component
componentDidMount() {
// μ»΄ν¬λνΈκ° λ§μ΄νΈλ λ μ€νλλ μ½λ
}
JavaScript
볡μ¬
β’
Functional Component (hook)
useEffect(() => {
// μ»΄ν¬λνΈκ° λ§μ΄νΈλ λ μ€νλλ μ½λ
}, []);
JavaScript
볡μ¬
componentDidUpdate
β’
Class Component
componentDidUpdate(prevProps, prevState) {
// μ»΄ν¬λνΈκ° μ
λ°μ΄νΈλ λ μ€νλλ μ½λ
}
JavaScript
볡μ¬
β’
Functional Component (hook)
useEffect(() => {
// μ»΄ν¬λνΈκ° μ
λ°μ΄νΈλ λ μ€νλλ μ½λ
}, [/* dependency array */]);
JavaScript
볡μ¬
componentWillUnmount
β’
Class Component
componentWillUnmount() {
// μ»΄ν¬λνΈκ° μΈλ§μ΄νΈλ λ μ€νλλ μ½λ
}
JavaScript
볡μ¬
β’
Functional Component (hook)
useEffect(() => {
return () => {
// μ»΄ν¬λνΈκ° μΈλ§μ΄νΈλ λ μ€νλλ μ½λ
};
}, []);
JavaScript
볡μ¬
getDerivedStateFromProps
β’
Class Component
static getDerivedStateFromProps(nextProps, nextState) {
// propsλ‘λΆν° νμλ μνλ₯Ό μ
λ°μ΄νΈ
return null;
}
JavaScript
볡μ¬
β’
Functional Component (hook)
useState()
JavaScript
볡μ¬
shouldComponentUpdate
β’
Class Component
shouldComponentUpdate(nextProps, nextState) {
// μ»΄ν¬λνΈ μ
λ°μ΄νΈ μ¬λΆλ₯Ό κ²°μ
return true; // λλ false
}
JavaScript
볡μ¬
β’
Functional Component (hook)
React.memo λ₯Ό ν΅ν μ΅μ ν
JavaScript
볡μ¬
getSnapshotBeforeUpdate
β’
Class Component
getSnapshotBeforeUpdate(prevProps, prevState) {
// μ»΄ν¬λνΈ μ
λ°μ΄νΈ μ μ DOM μνλ₯Ό μΊ‘μ²
return null;
}
JavaScript
볡μ¬
β’
Functional Component (hook)
useEffectμ useLayoutEffect λ±μ νμ©νμ¬ DOM μνλ₯Ό μ²λ¦¬ν©λλ€.
JavaScript
볡μ¬
μ useEffect κ° 2λ² νΈμΆλλμ?
StrictMode μΈ κ²½μ°, useEffect() μ 2λ² νΈμΆλλ νμμ΄ μμ΅λλ€.
StrictModeμμ useEffectκ° λ λ² νΈμΆλλ κ²μ λ§μΌλ €λ©΄, ν΄λ‘μ λ₯Ό μ¬μ©νμ¬ μ»΄ν¬λνΈμ stateλ propsλ₯Ό μ°Έμ‘°νμ§ μλλ‘ ν΄μΌ ν©λλ€.
ν΄κ²° λ°©λ²
StrictMode μ κ±°
ν΄λ‘μ μ¬μ©
β’
useEffect κ° 2λ² νΈμΆλλ νμμ ν΄κ²°νλ €λ©΄ μλ 2κ°μ§ λ°©λ² μ€ 1κ°μ§λ₯Ό μ¬μ©νλ©΄ λ©λλ€.
λ€λ§, κ·Όλ³Έμ μΈ ν΄κ²° λ°©λ²μ ν΄λ‘μ λ₯Ό μ¬μ©νλ κ²μ
λλ€.
μ΄μ StrictMode
StrictModeλ Reactμ μ½λ μ€νμΌμ λ³΄λ€ μ격νκ² κ²μ¬νλ λͺ¨λμ
λλ€. StrictModeλ₯Ό μ¬μ©νλ©΄ λ€μκ³Ό κ°μ μ΄μ μ΄ μμ΅λλ€.
β’
μ½λμ μΌκ΄μ±μ λμ΄κ³ λ²κ·Έλ₯Ό μ€μΌ μ μμ΅λλ€.
β’
μ½λμ μ±λ₯μ ν₯μμν¬ μ μμ΅λλ€.
β’
μ½λμ ν
μ€νΈ κ°λ₯μ±μ ν₯μμν¬ μ μμ΅λλ€.
StrictModeλ λ€μκ³Ό κ°μ κ²μ¬λ₯Ό μνν©λλ€.
β’
λ λλ§ λ¨κ³μ λΆμμ© κ²μ¬
β’
ν΄λμ€ μ»΄ν¬λνΈμ μμ νμ§ μμ μλͺ
μ£ΌκΈ° λ©μλ κ²μ¬
β’
useState(),Β useEffect(),Β useContext()Β ν
μ λΆμ μ ν μ¬μ© κ²μ¬
β’
리μ‘νΈ APIμ μλͺ»λ μ¬μ© κ²μ¬
StrictModeλ₯Ό μ¬μ©νλ €λ©΄ λ€μκ³Ό κ°μ΄ μ½λμ React.StrictMode μ»΄ν¬λνΈλ₯Ό μΆκ°ν©λλ€.
import React, { StrictMode } from "react";
function App() {
return (
<StrictMode>
// StrictMode μλμ μλ μ»΄ν¬λνΈκ° κ²μ¬λ©λλ€.
<MyComponent />
</StrictMode>
);
}
JavaScript
볡μ¬
νμ νμΈ
const App = () => {
const [count, setCount] = useState(0);
useEffect(() => {
// νμ΄λ¨Έλ₯Ό μ€μ ν©λλ€.
setInterval(() => setCount(count + 1), 1000);
}, [count]);
return (
<div>
<h1>νμ¬ μΉ΄μ΄νΈ: {count}</h1>
</div>
);
};
JavaScript
볡μ¬
μ΄ μ½λμμλ incrementCount ν¨μλ₯Ό ν΄λ‘μ λ‘ μμ±νμ§ μμ΅λλ€. λμ , setInterval() ν¨μμ 첫 λ²μ§Έ μΈμλ‘ μ λ¬λ μ½λ°± ν¨μμμ count λ³μμ μ§μ μ κ·Όν©λλ€. λ°λΌμ, useEffect()κ° λ λ² νΈμΆλ λλ§λ€ count λ³μμ κ°μ΄ λ³κ²½λ©λλ€.
( νμ ν΄κ²° λ΄μ© μμ μ€ )
νμ ν΄κ²°
ν΄λ‘μ μ¬μ©
const App = () => {
const [count, setCount] = useState(0);
useEffect(() => {
// ν΄λ‘μ λ₯Ό μ»΄ν¬λνΈμ propsλ‘ μ΄λν©λλ€.
const incrementCount = () => setCount(count + 1);
// μ½λ°± ν¨μμμ ν΄λ‘μ λ₯Ό μ°Έμ‘°ν©λλ€.
return () => {
clearInterval(incrementCount);
};
}, [count]);
return (
<div>
<h1>νμ¬ μΉ΄μ΄νΈ: {count}</h1>
</div>
);
};
JavaScript
볡μ¬
ν΄λ‘μ (closure) λ?
ν΄λ‘μ λ ν¨μμ κ·Έ ν¨μκ° μ μΈλ νκ²½μ ν¨κ» λ¬Άμ κ²μ
λλ€. ν΄λ‘μ λ ν¨μκ° μ μΈλ νκ²½μ λ³μμ μ κ·Όν μ μλ κΈ°λ₯μ μ 곡ν©λλ€.
function outer() {
const inner = () => {
// inner ν¨μλ outer ν¨μκ° μ μΈλ νκ²½μ count λ³μμ μ κ·Όν μ μμ΅λλ€.
const count = 10;
return count;
};
return inner;
}
const closure = outer();
const result = closure();
console.log(result); // 10
JavaScript
볡μ¬
μ μ½λμμλ outer() ν¨μμμ inner() ν¨μλ₯Ό λ°νν©λλ€. inner() ν¨μλ outer() ν¨μκ° μ μΈλ νκ²½μ count λ³μμ μ κ·Όν μ μμ΅λλ€. closure λ³μλ outer() ν¨μμ λ°νκ°μΈ inner() ν¨μλ₯Ό μ°Έμ‘°ν©λλ€. result λ³μλ closure() ν¨μλ₯Ό νΈμΆνμ¬ λ°νλ κ°μ μ μ₯ν©λλ€.
ν΄λ‘μ λ λ€μκ³Ό κ°μ μ©λλ‘ μ¬μ©λ μ μμ΅λλ€.
β’
ν¨μμ λ΄λΆ μνλ₯Ό μ μ§ν©λλ€.
β’
κ³ μ°¨ ν¨μμμ ν¨μμ μΈμλ‘ μ λ¬λ ν¨μμ νκ²½μ μ μ§ν©λλ€.
β’
μ½λ°± ν¨μμμ μΈλΆ νκ²½μ λ³μμ μ κ·Όν©λλ€.
ν΄λ‘μ λ₯Ό μ¬μ©ν λλ λ€μκ³Ό κ°μ μ£Όμ μ¬νμ κ³ λ €ν΄μΌ ν©λλ€.
β’
ν΄λ‘μ λ ν¨μκ° μ μΈλ νκ²½μ λ³μμ μ κ·Όν μ μκΈ° λλ¬Έμ, λ³μμ λ²μλ₯Ό μ μ΄ν΄ν΄μΌ ν©λλ€.
β’
ν΄λ‘μ λ λ©λͺ¨λ¦¬ λμμ μμΈμ΄ λ μ μμΌλ―λ‘, μ¬μ©μ΄ μλ£λ νμλ μμ ν΄μΌ ν©λλ€.
useState()
리μ‘νΈ ν¨μ μ»΄ν¬λνΈμμ μν κ΄λ¦¬λ₯Ό ν μ μλ ν
β’
μν κ°μ μ μ₯νκ³ , μν κ°μ΄ λ³κ²½λ λλ§λ€ μ»΄ν¬λνΈλ₯Ό 리λ λλ§ν©λλ€.
β’
useStateλ λ κ°μ μΈμλ₯Ό λ°μ΅λλ€.
β¦
첫 λ²μ§Έ μΈμλ μν κ°μ μ΄κΉκ°μ
λλ€.
β¦
λ λ²μ§Έ μΈμλ μν κ°μ λ³κ²½νλ ν¨μμ
λλ€.
λ¬Έλ²
const [μν, μνλ³κ²½ν¨μ] = useState(μ΄κΈ°κ°);
JavaScript
볡μ¬
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(count + 1)}>+</button>
<button onClick={() => setCount(count - 1)}>-</button>
<p>{count}</p>
</div>
);
}
JavaScript
볡μ¬
useEffect()
리μ‘νΈ ν¨μ μ»΄ν¬λνΈμ μλͺ
μ£ΌκΈ°μ κ΄λ ¨λ μμ
μ μνν μ μλ ν
β’
μ»΄ν¬λνΈκ° λ§μ΄νΈλκ±°λ μΈλ§μ΄νΈλ λ, μν κ°μ΄ λ³κ²½λ λ λ± νΉμ μμ μ μμ
μ μνν μ μμ΅λλ€.
β’
useEffectλ λ κ°μ μΈμλ₯Ό λ°μ΅λλ€.
β¦
첫 λ²μ§Έ μΈμλ μμ
μ μννλ ν¨μμ
λλ€.
β¦
λ λ²μ§Έ μΈμλ μμ
μ μνν λ μ°Έμ‘°ν΄μΌ νλ μμ‘΄μ± λ°°μ΄μ
λλ€.
λ¬Έλ²
useEffect( () => {
// μ»΄ν¬λνΈκ° λ§μ΄νΈ λ μ΄ν μ€ν
// μΆκ°μ μΌλ‘, μμ‘΄μ± λ°°μ΄μ λ°λΌμ μ€νλ¨
return () => {
// μ»΄ν¬λνΈκ° μΈλ§μ΄νΈ λκΈ° μ μ μ€νλ¨
}
}, μμ‘΄μ±λ°°μ΄ );
JavaScript
볡μ¬
1.
μμ‘΄μ± λ°°μ΄μ [ ] λΉ λ°°μ΄λ‘ μ§μ ν κ²½μ°
2.
μμ‘΄μ± λ°°μ΄μ λ³μλ₯Ό μ§μ ν κ²½μ°
3.
μμ‘΄μ± λ°°μ΄μ μλ΅ν κ²½μ°
μμ‘΄μ± λ°°μ΄μ [ ] λΉ λ°°μ΄λ‘ μ§μ ν κ²½μ°
μ»΄ν¬λνΈ λ§μ΄νΈμ μΈλ§μ΄νΈ μ, useEffect() κ° ν λ²λ§ μ€νλ©λλ€.
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
// μ»΄ν¬λνΈκ° λ§μ΄νΈ/μΈλ§μ΄νΈ μ ν λ²λ§ μ€ν
}, []);
return (
<div>
<button onClick={() => setCount(count + 1)}>+</button>
<button onClick={() => setCount(count - 1)}>-</button>
<p>{count}</p>
</div>
);
}
JavaScript
볡μ¬
μμ‘΄μ± λ°°μ΄μ λ³μλ₯Ό μ§μ ν κ²½μ°
μμ‘΄μ± λ³μκ° λ³κ²½λ λλ§λ€ useEffect() κ° μ€νλ©λλ€.
function Counter() {
const [count, setCount] = useState(0);
const id = 1;
useEffect(() => {
// idμ κ°μ΄ λ³κ²½λ λλ§λ€ μ€ν
}, [id]);
return (
<div>
<button onClick={() => setCount(count + 1)}>+</button>
<button onClick={() => setCount(count - 1)}>-</button>
<p>{count}</p>
</div>
);
}
JavaScript
볡μ¬
μμ‘΄μ± λ°°μ΄μ μλ΅ν κ²½μ°
μ»΄ν¬λνΈκ° λ§μ΄νΈλ λλ§λ€, μνκ° μ
λ°μ΄νΈλ λλ§λ€ useEffect() κ° μ€νλ©λλ€.
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
// μ»΄ν¬λνΈκ° λ§μ΄νΈλ λλ§λ€, κ·Έλ¦¬κ³ μνκ° μ
λ°μ΄νΈ λ λλ§λ€ μ€ν
});
return (
<div>
<button onClick={() => setCount(count + 1)}>+</button>
<button onClick={() => setCount(count - 1)}>-</button>
<p>{count}</p>
</div>
);
}
JavaScript
볡μ¬
μ€μ΅μ½λ
βTodo List - ν μΌ λͺ©λ‘ UI λ§λ€κΈ°β
μμ νμΌ
β’
App.js
β’
App.css
β’
components
β¦
TodoContainer.jsx
β¦
TodoHeader.jsx
β¦
TodoInput.jsx
β¦
TodoList.jsx
β¦
TodoItem.jsx
β¦
TodoFooter.jsx
μ»΄ν¬λνΈ κ΅¬μ‘°
β’
App.
β¦
TodoContainer
βͺ
TodoHeader
βͺ
TodoInput
βͺ
TodoList
β’
TodoItem
βͺ
TodoFooter
μ½λ μμ
β’
components
β¦
TodoContainer.jsx
β¦
TodoHeader.jsx
β¦
TodoInput.jsx
β¦
TodoList.jsx
β¦
TodoItem.jsx
β¦
TodoFooter.jsx
β’
App.css
β’
App.js
TodoContainer.jsx
import React, { useEffect, useState } from 'react'
import TodoHeader from './TodoHeader'
import TodoInput from './TodoInput'
import TodoList from './TodoList'
import TodoFooter from './TodoFooter'
const TodoContainer = () => {
// β
state
const [todoList, setTodoList] = useState([]);
const [input, setInput] = useState('')
// β hook
useEffect(() => {
// β
Mount, Update
// ν μΌ λͺ©λ‘ μμ² [GET]
fetch('http://192.168.30.119:8080/todos')
.then( ( response ) => response.json() )
.then( ( data ) => setTodoList(data) )
.catch( (error) => console.log(error) );
console.log('[GET] - /todos - ν μΌλͺ©λ‘ μμ²');
// β
UnMount
return () => {
}
}, [])
// ν μΌ λ±λ‘
const onSubmit = () => {
console.log('ν μΌ : ' + input);
if( input == '' ) {
// alert('ν μΌμ μ
λ ₯ν΄μ£ΌμΈμ')
// return
}
const data = {
name: input == '' ? 'μ λͺ©μμ' : input, // ν μΌ μ λͺ©
status: 0, // μλ£ μ¬λΆ(λ―Έμλ£-0,μλ£-1)
};
const init = {
method : 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data)
};
// ν μΌ λ±λ‘ [POST]
// β‘ μλ‘ λ±λ‘λ ν μΌ λ°μ΄ν°
fetch('http://192.168.30.119:8080/todos',init)
.then( ( response ) => response.json() )
.then( ( data ) => setTodoList([data, ...todoList]) )
.catch( (error) => console.log(error) );
setInput('')
}
// ν μΌ μ
λ ₯ μ΄λ²€νΈ
const onChange = (e) => {
setInput(e.target.value);
}
// ν μΌ μλ£ μ¬λΆ μμ
const onToggle = (todo) => {
console.log('ν μΌ μλ£ μ¬λΆ μ²λ¦¬');
console.log(todo);
// PUT μμ²
const data = {
no: todo.no,
name: todo.name,
status: todo.status ? 0 : 1,
}
const init = {
method: 'PUT',
headers: {
'Content-Type' : 'application/json',
},
body: JSON.stringify(data),
}
// ν μΌ μμ [PUT]
// β‘ 'μμ μλ£ λ©μμ§'
fetch('http://192.168.30.119:8080/todos',init)
.then( ( response ) => response.text() )
.then( ( data ) => console.log(data) )
.catch( (error) => console.log(error) );
// ν μΌ μλ£ μ²λ¦¬ ν,
// β‘ ν΄λ¦ν ν μΌμ μλ£ μ¬λΆλ₯Ό toggle
// let updatedTodoList = todoList.map((item) => {
// return item.no === todo.no ? {...item, status: !item.status} : item;
// });
// })
// β‘ μλ£λ ν μΌμ μλμͺ½ μ λ ¬
// let sortedTodoList = updatedTodoList.sort((a, b) => {
// μ λ ¬ κΈ°μ€
// 1. μλ£ μ¬λΆ - 0, 1 μ€λ¦μ°¨μ
// 2. ν μΌ λ²νΈ λ΄λ¦Όμ°¨μ
// return a.status - b.status == 0 ? b.no - a.no : a.status - b.status;
// })
const sortedTodoList
= todoList.map((item) => item.no === todo.no ? {...item, status: !item.status} : item)
.sort((a, b) => a.status - b.status == 0 ? b.no - a.no : a.status - b.status)
setTodoList(sortedTodoList)
};
// ν μΌ μμ
const onDelete = (no) => {
// DELETE μμ²
const init = {
method: 'DELETE',
};
// ν μΌ μμ [DELETE]
// β‘ 'μμ μλ£ λ©μμ§'
fetch(`http://192.168.30.119:8080/todos/${no}`,init)
.then( ( response ) => response.text() )
.then( ( data ) => console.log(data) )
.catch( (error) => console.log(error) );
// μμ λ ν μΌ νλͺ© μ κ±°
const updatedTodoList = todoList.filter( (todo) => todo.no != no )
setTodoList( updatedTodoList )
}
// μ 체 μμ
const onDeleteAll = () => {
// [DELETE] /todos/-1
// DELETE μμ²
const init = {
method: 'DELETE',
};
// μ 체 ν μΌ μμ [DELETE]
// β‘ 'μμ μλ£ λ©μμ§'
fetch(`http://192.168.30.119:8080/todos/-1`,init)
.then( ( response ) => response.text() )
.then( ( data ) => console.log(data) )
.catch( (error) => console.log(error) );
// μμ λ ν μΌ νλͺ© μ κ±°
setTodoList( [] )
}
// μ 체 μλ£
const onCompleteAll = () => {
// PUT μμ²
// μ 체μλ£
// /todos : data : { no : -1 }
const data = {
no: -1,
}
const init = {
method: 'PUT',
headers: {
'Content-Type' : 'application/json',
},
body: JSON.stringify(data),
}
// ν μΌ μ 체 μλ£ [PUT]
// β‘ 'μμ μλ£ λ©μμ§'
fetch('http://192.168.30.119:8080/todos',init)
.then( ( response ) => response.text() )
.then( ( data ) => console.log(data) )
.catch( (error) => console.log(error) );
const sortedTodoList
= todoList.map((item) => ({ ...item, status: 1 }) )
.sort((a, b) => a.status - b.status == 0 ? b.no - a.no : a.status - b.status)
setTodoList(sortedTodoList)
}
// ν μΌ μμ
const onUpdate = (todo) => {
console.log('ν μΌ μμ μ²λ¦¬');
console.log(todo);
// PUT μμ²
const data = {
no: todo.no,
name: todo.name,
status: todo.status,
}
const init = {
method: 'PUT',
headers: {
'Content-Type' : 'application/json',
},
body: JSON.stringify(data),
}
// ν μΌ μμ [PUT]
// β‘ 'μμ μλ£ λ©μμ§'
fetch('http://192.168.30.119:8080/todos',init)
.then( ( response ) => response.text() )
.then( ( data ) => console.log(data) )
.catch( (error) => console.log(error) );
const sortedTodoList
= todoList.sort((a, b) => a.status - b.status == 0 ? b.no - a.no : a.status - b.status)
setTodoList(sortedTodoList)
};
return (
<div className="container">
<TodoHeader/>
<TodoInput input={input} onChange={onChange} onSubmit={onSubmit}/>
<TodoList todoList={todoList} onToggle={onToggle} onDelete={onDelete} onUpdate={onUpdate} />
<TodoFooter onDeleteAll={onDeleteAll} onCompleteAll={onCompleteAll} />
</div>
)
}
export default TodoContainer
JavaScript
볡μ¬
TodoHeader.jsx
import React from 'react'
const TodoHeader = () => {
return (
<div className='header'>
<h1>To Do List</h1>
</div>
)
}
export default TodoHeader
JavaScript
볡μ¬
TodoInput.jsx
import React, { useState } from 'react'
const TodoInput = ({ input, onSubmit, onChange }) => {
return (
<div>
<form className='form'>
<input placeholder='ν μΌ μ
λ ₯'
className='input'
onChange={onChange}
value={input}
/>
<button type='button'
className='btn'
onClick={onSubmit}
>μΆκ°</button>
</form>
</div>
)
}
export default TodoInput
JavaScript
볡μ¬
TodoList.jsx
import React from 'react'
import TodoItem from './TodoItem'
const TodoList = ( { todoList, onToggle, onDelete, onUpdate } ) => {
return (
<ul className="todoList">
{todoList.map( (todo) => (
<TodoItem
key={todo.no}
todo={todo}
onToggle={onToggle}
onDelete={onDelete}
onUpdate={onUpdate}
/>
))}
</ul>
)
}
export default TodoList
JavaScript
볡μ¬
TodoItem.jsx
import React from 'react'
const TodoItem = ( { todo, onToggle, onDelete, onUpdate } ) => {
let { no, name, status } = todo;
status = status == 1 ? true : false;
const className = status ? 'todoItem active' : 'todoItem';
return (
<li className={className}>
<div className="item">
<input type="checkbox"
id={todo.no}
checked={status}
onChange={ () => onToggle(todo) } />
<label htmlFor={todo.no}></label>
{/* ν μΌ */}
<input type="text"
id={`name-${todo.no}`}
className='input'
// value={todo.name}
defaultValue={todo.name}
/>
</div>
<div className="item">
<button className='btn btn-sm'
onClick={ () => {
todo.name = document.getElementById(`name-${todo.no}`).value;
onUpdate(todo);
}}>β
</button>
<button className='btn btn-sm' onClick={ () => onDelete(no) }>β</button>
</div>
</li>
)
}
export default TodoItem
JavaScript
볡μ¬
TodoFooter.jsx
import React from 'react'
const TodoFooter = ({ onDeleteAll, onCompleteAll }) => {
return (
<div className='footer'>
<div className="item">
<button className='btn' onClick={ onDeleteAll }>μ 체μμ </button>
</div>
<div className="item">
<button className='btn' onClick={ onCompleteAll }>μ 체μλ£</button>
</div>
</div>
)
}
export default TodoFooter
JavaScript
볡μ¬
App.css
.container {
width: 480px;
min-height: 600px;
margin: 100px auto;
padding: 20px;
border: 2px solid cornflowerblue;
border-radius: 10px;
box-shadow: 5px 5px 5px rgba(0,0,0,0.3);
background-color: rgba(200,200,200,0.1);
}
.header {
text-align: center;
font-size: 20px;
color: royalblue;
}
.form {
display: flex;
justify-content: space-between;
margin: 10px 10px 10px 0;
}
.form .input {
width: 80%;
height: 30px;
}
.input::placeholder {
color: royalblue;
}
.input {
border: none;
border-bottom: 1px solid royalblue;
padding-left: 12px;
font-size: 18px;
outline: none;
color: blue;
font-weight: bold;
background-color: transparent;
}
.form .btn {
width: 15%;
}
.btn {
width: 80px;
height: 40px;
padding: 0 10px;
font-weight: bold;
border: none;
border-radius: 12px;
background-color: cornflowerblue;
color: white;
cursor: pointer;
}
.btn.btn-sm {
width: auto;
height: 40px;
padding: 0 12px;
}
.btn:hover {
background-color: royalblue;
box-shadow: 2px 2px 5px rgba(0,0,0,0.5);
}
.btn:active {
box-shadow: none;
}
.todoList {
height: 500px;
overflow-y: auto;
}
ul { margin: 0; padding: 0; }
.todoItem {
list-style-type: none;
display: flex;
justify-content: space-between;
align-items: center;
margin: 10px 10px 10px 0;
padding: 20px;
border: 2px solid cornflowerblue;
border-radius: 12px;
color: royalblue;
font-size: 18px;
box-shadow: 5px 5px 5px rgba(0,0,0,0.3);
}
.todoItem.active {
background-color: royalblue;
color: white;
}
.todoItem.active .btn:hover {
background-color: blue;
}
.todoItem .item {
display: flex;
align-items: center;
gap: 14px;
}
/* 체ν¬λ°μ€ */
.todoItem input[type='checkbox'] {
display: none;
}
.todoItem label {
display: inline-block;
width: 20px;
height: 20px;
border: 3px dashed royalblue;
border-radius: 50%;
}
.todoItem input:checked ~ label {
background-color: cornflowerblue;
border: 3px solid blue;
}
.todoItem input:checked ~ input {
text-decoration: line-through;
}
.footer {
display: flex;
justify-content: space-between;
align-items: center;
margin: 10px 10px 10px 0;
}
/* μ€ν¬λ‘€λ° */
::-webkit-scrollbar-track
{
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
background-color: transparent;
border-radius: 10px;
}
::-webkit-scrollbar
{
width: 10px;
background-color: transparent;
}
::-webkit-scrollbar-thumb
{
border-radius: 10px;
background-image: -webkit-gradient(linear,
left bottom,
left top,
color-stop(0.44, rgb(122,153,217)),
color-stop(0.72, rgb(73,125,189)),
color-stop(0.86, rgb(28,58,148)));
}
/* λ°°κ²½ */
body {
background: linear-gradient(
-45deg,
#ffffff,
#ffe4e9,
#fcc8e2,
#f6baed,
#efb0ff,
#d3a2ff,
#8eb8ff,
#91efff,
#d5f9ff
);
background-size: 400% 400%;
animation: gradient 9s ease infinite;
height: 100vh;
}
@keyframes gradient {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
CSS
볡μ¬
App.js
import './App.css';
import TodoContainer from './components/TodoContainer';
function App() {
return (
<>
<TodoContainer/>
</>
);
}
export default App;
JavaScript
볡μ¬