๐ Hooks
ํจ์ํ ์ปดํฌ๋ํธ์์๋ ์ํ ๊ด๋ฆฌ๋ฅผ ํ ์ ์๋ useState, ๋ ๋๋ง ์งํ ์์ ์ ์ค์ ํ๋ useEffect ๋ฑ์ ๊ธฐ๋ฅ์ ์ ๊ณตํด ๊ธฐ์กด์ ํจ์ํ ์ปดํฌ๋ํธ์์ ํ ์ ์์๋ ๋ค์ํ ์์ ์ ํ ์ ์๊ฒ ํด ์ค.
๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ธ Hook์ผ๋ก ํจ์ํ ์ปดํฌ๋ํธ์์๋ ๊ฐ๋ณ์ ์ธ ์ํ๋ฅผ ์ง๋ ์ ์๊ฒ ํด ์ค. ํจ์ํ ์ปดํฌ๋ํธ์์ ์ํ๋ฅผ ๊ด๋ฆฌํด์ผ ํ๋ค๋ฉด useState๋ฅผ ์ฌ์ฉํ๋ฉด ๋จ.

// Counter.jsx
import React, { useState } from 'react';
const Counter = () => {
// ํ์ฌ 0์ ๋ฃ์ด ์ค => ๊ฒฐ๊ตญ ์นด์ดํฐ์ ๊ธฐ๋ณธ๊ฐ์ 0์ผ๋ก ์ค์ ํ๊ฒ ๋ค๋ ์๋ฏธ
// ์ด ํจ์๊ฐ ํธ์ถ๋๋ฉด ๋ฐฐ์ด์ ๋ฐํํจ.
// ๋ฐฐ์ด์ ์ฒซ ๋ฒ์งธ ์์๋ ์ํ ๊ฐ, ๋ ๋ฒ์งธ ์์๋ ์ํ๋ฅผ ์ค์ ํ๋ ํจ์
// ์ด ํจ์์ ํ๋ผ๋ฏธํฐ๋ฅผ ๋ฃ์ด์ ํธ์ถํ๋ฉด ์ ๋ฌ๋ฐ์ ํ๋ผ๋ฏธํฐ๋ก ๊ฐ์ด ๋ฐ๋๊ณ ์ปดํฌ๋ํธ๊ฐ ์ ์์ ์ผ๋ก ๋ฆฌ๋ ๋๋ง๋จ.
const [value, setValue] = useState(0);
return (
<div>
<p>
ํ์ฌ ์นด์ดํฐ ๊ฐ์ <b>{value}</b>์
๋๋ค.
</p>
<button onClick={() => setValue(value + 1)}>+1</button>
<button onClick={() => setValue(value - 1)}>-1</button>
</div>
);
};
export default Counter;
// App.jsx
import React from 'react';
import Counter from './features/8_Hooks/Counter';
const App = () => {
return <Counter />;
};
export default App;



โ๐ค useState๋ฅผ ์ฌ๋ฌ ๋ฒ ์ฌ์ฉํ๋ ค๋ฉด?
ํ๋์ useState ํจ์๋ ํ๋์ ์ํ ๊ฐ๋ง ๊ด๋ฆฌํ ์ ์์!
์ปดํฌ๋ํธ์์ ๊ด๋ฆฌํด์ผ ํ ์ํ๊ฐ ์ฌ๋ฌ ๊ฐ๋ผ๋ฉด???
๐ useState๋ฅผ ์ฌ๋ฌ ๋ฒ ์ฌ์ฉํ๋ฉด ๋จ!
// Info.jsx
import React, { useState } from 'react';
// 8.1.1 useState๋ฅผ ์ฌ๋ฌ ๋ฒ ์ฌ์ฉํ๊ธฐ
const Info = () => {
const [name, setName] = useState('');
const [nickname, setNickname] = useState('');
const onChangeName = (e) => {
setName(e.target.value);
};
const onChangeNickname = (e) => {
setNickname(e.target.value);
};
return (
<div>
<div>
<input value={name} onChange={onChangeName} />
<input value={nickname} onChange={onChangeNickname} />
</div>
<div>
<div>
<b>์ด๋ฆ:</b> {name}
</div>
<div>
<b>๋๋ค์:</b> {nickname}
</div>
</div>
</div>
);
};
export default Info;
// App.jsx
import React from 'react';
import Info from './features/8_Hooks/Info';
const App = () => {
return <Info />;
};
export default App;


๋ฆฌ์กํธ ์ปดํฌ๋ํธ๊ฐ ๋ ๋๋ง๋ ๋๋ง๋ค ํน์ ์์ ์ ์ํํ๋๋ก ์ค์ ํ ์ ์๋ Hook์ผ๋ก ํด๋์คํ ์ปดํฌ๋ํธ์ componentDidMount์ componentDidUpdate๋ฅผ ํฉ์น ํํ๋ก ๋ณด์๋ ๋ฌด๋ฐฉํจ.
// Info.jsx
import React, { useState, useEffect } from 'react';
const Info = () => {
const [name, setName] = useState('');
const [nickname, setNickname] = useState('');
useEffect(() => {
console.log('๋ ๋๋ง์ด ์๋ฃ๋์์ต๋๋ค!');
console.log({
name,
nickname,
});
});
const onChangeName = (e) => {
setName(e.target.value);
};
const onChangeNickname = (e) => {
setNickname(e.target.value);
};
return (
<div>
<div>
<input value={name} onChange={onChangeName} />
<input value={nickname} onChange={onChangeNickname} />
</div>
<div>
<div>
<b>์ด๋ฆ:</b> {name}
</div>
<div>
<b>๋๋ค์:</b> {nickname}
</div>
</div>
</div>
);
};
export default Info;


โ๐ค ๋ง์ดํธ๋ ๋๋ง ์คํํ๊ณ ์ถ์ ๋?
useEffect์์ ์ค์ ํ ํจ์๋ฅผ ์ปดํฌ๋ํธ๊ฐ ํ๋ฉด์ ๋งจ ์ฒ์ ๋ ๋๋ง๋ ๋๋ง ์คํํ๊ณ , ์
๋ฐ์ดํธ๋ ๋๋ ์คํํ์ง ์์ผ๋ ค๋ฉด...
๐ ํจ์์ ๋ ๋ฒ์งธ ํ๋ผ๋ฏธํฐ๋ก ๋น์ด ์๋ ๋ฐฐ์ด์ ๋ฃ์ด ์ฃผ๋ฉด ๋จ.
// Info.jsx -useEffect
useEffect(() => {
console.log('๋ง์ดํธ๋ ๋๋ง ์คํ๋ฉ๋๋ค.');
}, []);

์ปดํฌ๋ํธ๊ฐ ์ฒ์ ๋ํ๋ ๋๋ง ์ฝ์์ ๋ฌธ๊ตฌ๊ฐ ๋ํ๋๊ณ , ๊ทธ ์ดํ์๋ ๋ํ๋์ง ์์.
โ๐ค ํน์ ๊ฐ์ด ์ ๋ฐ์ดํธ๋ ๋๋ง ์คํํ๊ณ ์ถ์ ๋?
componentDidUpdate(prevProps, prevState) {
if (prevProps.value !== this.props.value) {
doSomething();
}
}
// Info.jsx-useEffect
useEffect(() => {
console.log(name);
}, [name]);

โ๏ธ ๋ท์ ๋ฆฌํ๊ธฐ
useEffect๋ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ ๋๋ง๋๊ณ ๋ ์งํ๋ง๋ค ์คํ๋๋ฉฐ, ๋ ๋ฒ์งธ ํ๋ผ๋ฏธํฐ ๋ฐฐ์ด์ ๋ฌด์์ ๋ฃ๋์ง์ ๋ฐ๋ผ ์คํ๋๋ ์กฐ๊ฑด์ด ๋ฌ๋ผ์ง.
โ๐ค ์ปดํฌ๋ํธ๊ฐ ์ธ๋ง์ดํธ๋๊ธฐ ์ ์ด๋ ์
๋ฐ์ดํธ๋๊ธฐ ์ง์ ์ ์ด๋ ํ ์์
์ ์ํํ๊ณ ์ถ๋ค๋ฉด?
๐ useEffect์์ ๋ท์ ๋ฆฌ(cleanup) ํจ์๋ฅผ ๋ฐํ
// Info.jsx-useEffect
import React, { useState, useEffect } from 'react';
const Info = () => {
const [name, setName] = useState('');
const [nickname, setNickname] = useState('');
// 8.2.3 ๋ท์ ๋ฆฌํ๊ธฐ
useEffect(() => {
console.log('effect');
console.log(name);
return () => {
console.log('cleanup');
console.log(name);
};
});
const onChangeName = (e) => {
setName(e.target.value);
};
const onChangeNickname = (e) => {
setNickname(e.target.value);
};
return (
<div>
<div>
<input value={name} onChange={onChangeName} />
<input value={nickname} onChange={onChangeNickname} />
</div>
<div>
<div>
<b>์ด๋ฆ:</b> {name}
</div>
<div>
<b>๋๋ค์:</b> {nickname}
</div>
</div>
</div>
);
};
export default Info;
// useState๋ฅผ ์ฌ์ฉํด App ์ปดํฌ๋ํธ์์
// Info ์ปดํฌ๋ํธ์ ๊ฐ์์ฑ ๋ฐ๊ฟ ์ ์๊ฒ ํ๊ธฐ
import React, { useState } from 'react';
import Info from './features/8_Hooks/Info';
const App = () => {
const [visible, setVisible] = useState(false);
return (
<div>
<button
onClick={() => {
setVisible(!visible);
}}
>
{visible ? '์จ๊ธฐ๊ธฐ' : '๋ณด์ด๊ธฐ'}
</button>
<hr />
{visible && <Info />}
</div>
);
};
export default App;

์ปดํฌ๋ํธ๊ฐ ๋ํ๋ ๋ ์ฝ์์ effect๊ฐ ๋ํ๋๊ณ , ์ฌ๋ผ์ง ๋ cleanup์ด ๋ํ๋จ. ์ด์ ์ธํ์ ์ด๋ฆ์ ์ ์ด ๋ณด๊ณ ์ฝ์์ ์ด๋ค ๊ฒฐ๊ณผ๊ฐ ๋ํ๋๋์ง ํ์ธํด๋ณด์!


๋ ๋๋ง๋ ๋๋ง๋ค ๋ท์ ๋ฆฌ ํจ์๊ฐ ๊ณ์ ๋ํ๋๋ ๊ฒ์ ํ์ธํ ์ ์์. ๊ทธ๋ฆฌ๊ณ ๋ท์ ๋ฆฌ ํจ์๊ฐ ํธ์ถ๋ ๋๋ ์ ๋ฐ์ดํธ๋๊ธฐ ์ง์ ์ ๊ฐ์ ๋ณด์ฌ์ค.
โ๐ค ์ค์ง ์ธ๋ง์ดํธ๋ ๋๋ง ๋ท์ ๋ฆฌ ํจ์๋ฅผ ํธ์ถํ๊ณ ์ถ๋ค๋ฉด?
๐ useEffect ํจ์์ ๋ ๋ฒ์งธ ํ๋ผ๋ฏธํฐ์ ๋น์ด ์๋ ๋ฐฐ์ด์ ๋ฃ์ผ๋ฉด ๋จ.
// Info.jsx-useEffect
useEffect(() => {
console.log('effect');
console.log(name);
return () => {
console.log('cleanup');
console.log(name);
};
}, []);
useState๋ณด๋ค ๋ ๋ค์ํ ์ปดํฌ๋ํธ ์ํฉ์ ๋ฐ๋ผ ๋ค์ํ ์ํ๋ฅผ ๋ค๋ฅธ ๊ฐ์ผ๋ก ์ ๋ฐ์ดํธํด ์ฃผ๊ณ ์ถ์ ๋ ์ฌ์ฉํ๋ Hook์ด๋ค.
ํ์ฌ ์ํ, ์
๋ฐ์ดํธ๋ฅผ ์ํด ํ์ํ ์ ๋ณด๋ฅผ ๋ด์ ์ก์
(action) ๊ฐ์ ์ ๋ฌ๋ฐ์ ์๋ก์ด ์ํ๋ฅผ ๋ฐํํ๋ ํจ์๋ก ๋ฆฌ๋์ ํจ์์์ ์๋ก์ด ์ํ๋ฅผ ๋ง๋ค ๋๋ ๋ฐ๋์ ๋ถ๋ณ์ฑ์ ์ง์ผ์ค์ผ ํจ.
// Counter.jsx
// 8.3.1 ์นด์ดํฐ ๊ตฌํํ๊ธฐ
import React, { useReducer } from 'react';
function reducer(state, action) {
// action.type์ ๋ฐ๋ผ ๋ค๋ฅธ ์์
์ํ
switch (action.type) {
case 'INCREMENT':
return { value: state.value + 1 };
case 'DECREMENT':
return { value: state.value - 1 };
default:
// ์๋ฌด๊ฒ๋ ํด๋น๋์ง ์์ ๋ ๊ธฐ์กด ์ํ ๋ฐํ
return state;
}
}
const Counter = () => {
const [state, dispatch] = useReducer(reducer, { value: 0 });
return (
<div>
<p>
ํ์ฌ ์นด์ดํฐ ๊ฐ์ <b>{state.value}</b>์
๋๋ค.
</p>
<button onClick={() => dispatch({ type: 'INCREMENT' })}>+1</button>
<button onClick={() => dispatch({ type: 'DECREMENT' })}>-1</button>
</div>
);
};
export default Counter;
import React, { useReducer } from 'react';
function reducer(state, action) {
// action.type์ ๋ฐ๋ผ ๋ค๋ฅธ ์์
์ํ
switch (action.type) {
case 'INCREMENT':
return { value: state.value + 1 };
case 'DECREMENT':
return { value: state.value - 1 };
default:
// ์๋ฌด๊ฒ๋ ํด๋น๋์ง ์์ ๋ ๊ธฐ์กด ์ํ ๋ฐํ
return state;
}
}
const Counter = () => {
const [state, dispatch] = useReducer(reducer, { value: 0 });
return (
<div>
<p>
ํ์ฌ ์นด์ดํฐ ๊ฐ์ <b>{state.value}</b>์
๋๋ค.
</p>
<button onClick={() => dispatch({ type: 'INCREMENT' })}>+1</button>
<button onClick={() => dispatch({ type: 'DECREMENT' })}>-1</button>
</div>
);
};
export default Counter;
// App.jsx
import React from 'react';
import Counter from './features/8_Hooks/Counter';
const App = () => {
return <Counter />;
};
export default App;

useReducer์ ์ฒซ ๋ฒ์งธ ํ๋ผ๋ฏธํฐ์๋ ๋ฆฌ๋์ ํจ์๋ฅผ ๋ฃ๊ณ , ๋ ๋ฒ์งธ ํ๋ผ๋ฏธํฐ์๋ ํด๋น ๋ฆฌ๋์์ ๊ธฐ๋ณธ๊ฐ์ ๋ฃ์ด์ค.
์ด Hook์ ์ฌ์ฉํ๋ฉด state ๊ฐ๊ณผ dispatch ํจ์๋ฅผ ๋ฐ์ ์ด. ์ฌ๊ธฐ์ state๋ ํ์ฌ ๊ฐ๋ฆฌํค๊ณ ์๋ ์ํ๊ณ , dispatch๋ ์ก์ ์ ๋ฐ์์ํค๋ ํจ์์.
dispatch(action)๊ณผ ๊ฐ์ ํํ๋ก, ํจ์ ์์ ํ๋ผ๋ฏธํฐ๋ก ์ก์
๊ฐ์ ๋ฃ์ด ์ฃผ๋ฉด ๋ฆฌ๋์ ํจ์๊ฐ ํธ์ถ๋๋ ๊ตฌ์กฐ! useReducer๋ฅผ ์ฌ์ฉ ์, ํฐ ์ฅ์ ์ ์ปดํฌ๋ํธ ์
๋ฐ์ดํธ ๋ก์ง์ ์ปดํฌ๋ํธ ๋ฐ๊นฅ์ผ๋ก ๋นผ๋ผ ์ ์๋ค๋ ๊ฒ!
// Info_input.jsx
// 8.3.2 ์ธํ ์ํ ๊ด๋ฆฌํ๊ธฐ
import React, { useReducer } from 'react';
function reducer(state, action) {
return {
...state,
[action.name]: action.value,
};
}
const Info_input = () => {
const [state, dispatch] = useReducer(reducer, {
name: '',
nickname: '',
});
const { name, nickname } = state;
const onChange = (e) => {
dispatch(e.target);
};
return (
<div>
<div>
<input name="name" value={name} onChange={onChange} />
<input name="nickname" value={nickname} onChange={onChange} />
</div>
<div>
<div>
<b>์ด๋ฆ:</b> {name}
</div>
<div>
<b>๋๋ค์: </b>
{nickname}
</div>
</div>
</div>
);
};
export default Info_input;
// App.jsx
import React from 'react';
import Info_input from './features/8_Hooks/Info_input';
const App = () => {
return <Info_input />;
};
export default App;

useReducer์์์ ์ก์ ์ ๊ทธ ์ด๋ค ๊ฐ๋ ์ฌ์ฉ ๊ฐ๋ฅ! ๊ทธ๋์ ์ด๋ฒ์๋ ์ด๋ฒคํธ ๊ฐ์ฒด๊ฐ ์ง๋๊ณ ์๋ e.target ๊ฐ ์์ฒด๋ฅผ ์ก์ ๊ฐ์ผ๋ก ์ฌ์ฉํ์. ์ด๋ฐ ์์ผ๋ก ์ธํ์ ๊ด๋ฆฌํ๋ฉด ์๋ฌด๋ฆฌ ์ธํ์ ๊ฐ์๊ฐ ๋ง์์ ธ๋ ์ฝ๋๋ฅผ ์งง๊ณ ๊น๋ํ๊ฒ ์ ์ง ๊ฐ๋ฅ!
useMemo๋ฅผ ์ฌ์ฉํ๋ฉด ํจ์ํ ์ปดํฌ๋ํธ ๋ด๋ถ์์ ๋ฐ์ํ๋ ์ฐ์ฐ์ ์ต์ ํํ ์ ์์.
// Average.jsx
import React, { useCallback, useMemo, useState } from 'react';
const getAverage = numbers => {
console.log('ํ๊ท ๊ฐ ๊ณ์ฐ ์ค..');
if (numbers.length === 0) return 0;
const sum = numbers.reduce((a, b) => a + b);
return sum / numbers.length;
};
const Average = () => {
const [list, setList] = useState([]);
const [number, setNumber] = useState('');
const onChange = e => {
setNumber(e.target.value);
};
const onInsert = e => {
const nextList = list.concat(parseInt(number));
setList(nextList);
setNumber('');
};
return (
<div>
<input value={number} onChange={onChange} />
<button onClick={onInsert}>๋ฑ๋ก</button>
<ul>
{list.map((value, index) => (
<li key={index}>{value}</li>
))}
</ul>
<div>
<b>ํ๊ท ๊ฐ:</b> {getAverage(list)}
</div>
</div>
);
};
export default Average;
// App.jsx
import React from 'react';
import Average from './features/8_Hooks/Average';
const App = () => {
return <Average />;
};
export default App;


์ซ์๋ฅผ ๋ฑ๋กํ ๋๋ฟ๋ง ์๋๋ผ ์ธํ ๋ด์ฉ์ด ์์ ๋ ๋๋ ์ฐ๋ฆฌ๊ฐ ๋ง๋ getAverage ํจ์๊ฐ ํธ์ถ๋จ... ์ธํ ๋ด์ฉ์ด ๋ฐ๋ ๋๋ ํ๊ท ๊ฐ์ ๋ค์ ๊ณ์ฐํ ํ์๋ ์์! ์ด๋ ๊ฒ ๋ ๋๋งํ ๋๋ง๋ค ๊ณ์ฐํ๋ ๊ฒ์ ๋ญ๋น!!!
useMemo Hook์ ์ฌ์ฉํ๋ฉด ์ด๋ฌํ ์์ ์ ์ต์ ํํ ์ ์์! ๋ ๋๋งํ๋ ๊ณผ์ ์์ ํน์ ๊ฐ์ด ๋ฐ๋์์ ๋๋ง ์ฐ์ฐ์ ์คํํ๊ณ , ์ํ๋ ๊ฐ์ด ๋ฐ๋์ง ์์๋ค๋ฉด ์ด์ ์ ์ฐ์ฐํ๋ ๊ฒฐ๊ณผ๋ฅผ ๋ค์ ์ฌ์ฉํ๋ ๋ฐฉ์์ผ๋ก ์๋์ ๊ฐ์ด ์์ ํด๋ณด์.
// Average.jsx
// ========================= ์ ์ฝ๋๋ฅผ useMemo Hook์ ์ฌ์ฉํ๋ฉด ์ด๋ฌํ ์์
์ ์ต์ ํํ๊ธฐ
// ๋ ๋๋งํ๋ ๊ณผ์ ์์ ํน์ ๊ฐ์ด ๋ฐ๋์์ ๋๋ง ์ฐ์ฐ์ ์คํํ๊ณ ,
// ์ํ๋ ๊ฐ์ด ๋ฐ๋์ง ์์๋ค๋ฉด
// ์ด์ ์ ์ฐ์ฐํ๋ ๊ฒฐ๊ณผ๋ฅผ ๋ค์ ์ฌ์ฉํ๋ ๋ฐฉ์
import React, { useState, useMemo } from 'react';
const getAverage = (numbers) => {
console.log('ํ๊ท ๊ฐ ๊ณ์ฐ ์ค..');
if (numbers.length === 0) return 0;
const sum = numbers.reduce((a, b) => a + b);
return sum / numbers.length;
};
const Average = () => {
const [list, setList] = useState([]);
const [number, setNumber] = useState('');
const onChange = (e) => {
setNumber(e.target.value);
};
const onInsert = () => {
const nextList = list.concat(parseInt(number));
setList(nextList);
setNumber('');
};
const avg = useMemo(() => getAverage(list), [list]);
return (
<div>
<input value={number} onChange={onChange} />
<button onClick={onInsert}>๋ฑ๋ก</button>
<ul>
{list.map((value, index) => (
<li key={index}>{value}</li>
))}
</ul>
<div>
<b>ํ๊ท ๊ฐ:</b> {avg}
</div>
</div>
);
};
export default Average;

useMemo๋ฅผ ํตํ ์ฐ์ฐ ์ต์ ํ์ํค๋ฉด list ๋ฐฐ์ด์ ๋ด์ฉ์ด ๋ฐ๋ ๋๋ง getAverage ํจ์๊ฐ ํธ์ถ๋๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.

๋ ๋๋ง ์ฑ๋ฅ์ ์ต์ ํํด์ผ ํ๋ ์ํฉ์์ ์ฌ์ฉํจ.(useMemo์ ์๋นํ ๋น์ท) ์ด Hook์ ์ฌ์ฉํ๋ฉด ์ด๋ฒคํธ ํธ๋ค๋ฌ ํจ์๋ฅผ ํ์ํ ๋๋ง ์์ฑํ ์ ์์.
๋ฐฉ๊ธ ๊ตฌํํ Average ์ปดํฌ๋ํธ๋ฅผ ๋ณด๋ฉด onChange์ onInsert๋ผ๋ ํจ์๋ฅผ ์ ์ธํจ. ์ด๋ ๊ฒ ์ ์ธํ๋ฉด ์ปดํฌ๋ํธ๊ฐ ๋ฆฌ๋ ๋๋ง๋ ๋๋ง๋ค ์ด ํจ์๋ค์ด ์๋ก ์์ฑ๋จ. ๋๋ถ๋ถ ๋ฌธ์ ์์ง๋ง, ์ปดํฌ๋ํธ์ ๋ ๋๋ง์ด ์์ฃผ ๋ฐ์ํ๊ฑฐ๋ ๋ ๋๋งํด์ผ ํ ์ปดํฌ๋ํธ์ ๊ฐ์๊ฐ ๋ง์์ง๋ฉด ์ด ๋ถ๋ถ์ ์ต์ ํํด ์ฃผ๋ ๊ฒ์ด ์ข์.
useCallback์ ์ฌ์ฉํ์ฌ ํ๋ฒ ์ต์ ํํด๋ณด์!
// Average.jsx
import React, { useState, useMemo, useCallback } from 'react';
const getAverage = numbers => {
console.log('ํ๊ท ๊ฐ ๊ณ์ฐ ์ค..');
if (numbers.length === 0) return 0;
const sum = numbers.reduce((a, b) => a + b);
return sum / numbers.length;
};
const Average = () => {
const [list, setList] = useState([]);
const [number, setNumber] = useState('');
const onChange = useCallback(e => {
setNumber(e.target.value);
}, []); // ์ปดํฌ๋ํธ๊ฐ ์ฒ์ ๋ ๋๋ง๋ ๋๋ง ํจ์ ์์ฑ
const onInsert = useCallback(() => {
const nextList = list.concat(parseInt(number));
setList(nextList);
setNumber('');
}, [number, list]); // number ํน์ list๊ฐ ๋ฐ๋์์ ๋๋ง ํจ์ ์์ฑ
const avg = useMemo(() => getAverage(list), [list]);
return (
<div>
<input value={number} onChange={onChange} />
<button onClick={onInsert}>๋ฑ๋ก</button>
<ul>
{list.map((value, index) => (
<li key={index}>{value}</li>
))}
</ul>
<div>
<b>ํ๊ท ๊ฐ:</b> {avg}
</div>
</div>
);
};
export default Average;
useCallback์ ์ฒซ ๋ฒ์งธ ํ๋ผ๋ฏธํฐ์๋ ์์ฑํ๊ณ ์ถ์ ํจ์๋ฅผ ๋ฃ๊ณ , ๋ ๋ฒ์งธ ํ๋ผ๋ฏธํฐ์๋ ๋ฐฐ์ด์ ๋ฃ๋๋ค. ์ด ๋ฐฐ์ด์๋ ์ด๋ค ๊ฐ์ด ๋ฐ๋์์ ๋ ํจ์๋ฅผ ์๋ก ์์ฑํด์ผ ํ๋์ง ๋ช ์ํด์ผ ํจ!
onChange์ฒ๋ผ ๋น์ด ์๋ ๋ฐฐ์ด์ ๋ฃ๊ฒ ๋๋ฉด?
๐ ์ปดํฌ๋ํธ๊ฐ ๋ ๋๋ง๋ ๋ ๋จ ํ ๋ฒ๋ง ํจ์๊ฐ ์์ฑ๋๋ฉฐ, onInsert์ฒ๋ผ ๋ฐฐ์ด ์์ number์ list๋ฅผ ๋ฃ๊ฒ ๋๋ฉด ์ธํ ๋ด์ฉ์ด ๋ฐ๋๊ฑฐ๋ ์๋ก์ด ํญ๋ชฉ์ด ์ถ๊ฐ๋ ๋๋ง๋ค ํจ์๊ฐ ์์ฑ๋จ.
ํจ์ ๋ด๋ถ์์ ์ํ ๊ฐ์ ์์กดํด์ผ ํ ๋?
๐ ๊ทธ ๊ฐ์ ๋ฐ๋์ ๋ ๋ฒ์งธ ํ๋ผ๋ฏธํฐ ์์ ํฌํจ์์ผ์ค์ผ ํจ.
(์๋ฅผ ๋ค์ด onChange์ ๊ฒฝ์ฐ ๊ธฐ์กด์ ๊ฐ์ ์กฐํํ์ง ์๊ณ ๋ฐ๋ก ์ค์ ๋ง ํ๊ธฐ ๋๋ฌธ์ ๋ฐฐ์ด์ด ๋น์ด ์์ด๋ ์๊ด์์ง๋ง, onInsert๋ ๊ธฐ์กด์ number์ list๋ฅผ ์กฐํํด์ nextList๋ฅผ ์์ฑํ๊ธฐ ๋๋ฌธ์ ๋ฐฐ์ด ์์ number์ list๋ฅผ ๊ผญ ๋ฃ์ด์ค์ผ ํจ.)
์ฐธ๊ณ ๋ก ๋ค์ ๋ ์ฝ๋๋ ์์ ํ ๋๊ฐ์ ์ฝ๋!
useCallback์ ๊ฒฐ๊ตญ useMemo๋ก ํจ์๋ฅผ ๋ฐํํ๋ ์ํฉ์์ ๋ ํธํ๊ฒ ์ฌ์ฉํ ์ ์๋ Hook. ์ซ์, ๋ฌธ์์ด, ๊ฐ์ฒด์ฒ๋ผ ์ผ๋ฐ ๊ฐ์ ์ฌ์ฌ์ฉํ๋ ค๋ฉด useMemo๋ฅผ ์ฌ์ฉํ๊ณ , ํจ์๋ฅผ ์ฌ์ฌ์ฉํ๋ ค๋ฉด useCallback์ ์ฌ์ฉํ๋ฉด ๋จ.
// ์์ ์ฝ๋
useCallback(() => {
console.log('hello world!');
}, [])
useMemo(() => {
const fn = () => {
console.log('hello world!');
};
return fn;
}, [])
ํจ์ํ ์ปดํฌ๋ํธ์์ ref๋ฅผ ์ฝ๊ฒ ์ฌ์ฉํ ์ ์๋๋ก ํด ์ค.
useRef๋ฅผ ์ฌ์ฉํ์ฌ ref๋ฅผ ์ค์ ํ๋ฉด useRef๋ฅผ ํตํด ๋ง๋ ๊ฐ์ฒด ์์ current ๊ฐ์ด ์ค์ ์๋ฆฌ๋จผํธ๋ฅผ ๊ฐ๋ฆฌํด.
โ๐ค ๋ฆฌ์กํธ์์ ref๋ ๋ฌด์์ธ๊ฐ?
๐ ref๋ React์์ ํน์ DOM ์์๋ React ์ปดํฌ๋ํธ์ ์ง์ ์ ๊ทผํ ์ ์๋๋ก ํด์ฃผ๋ "์ฐธ์กฐ(reference)"์ด๋ค.
์ผ๋ฐ์ ์ผ๋ก React๋ ๋ฐ์ดํฐ๋ฅผ ์ํ๋ props๋ก ๊ด๋ฆฌํ๊ณ , DOM ์์๋ฅผ ์ง์ ์กฐ์ํ์ง ์๋๋ก ์ค๊ณ๋์ผ๋ ํน์ ์ํฉ์์๋ DOM์ ์ง์ ์ ๊ทผํ๊ฑฐ๋ React์์ ๊ด๋ฆฌํ์ง ์๋ ์ธ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ํตํฉํด์ผ ํ ๋๊ฐ ์๋๋ฐ ์ด๋ฐ ๊ฒฝ์ฐ์ ref๋ฅผ ์ฌ์ฉํ๋ค.
// Average.jsx
import React, { useState, useMemo, useCallback, useRef } from 'react';
const getAverage = numbers => {
console.log('ํ๊ท ๊ฐ ๊ณ์ฐ ์ค..');
if (numbers.length === 0) return 0;
const sum = numbers.reduce((a, b) => a + b);
return sum / numbers.length;
};
const Average = () => {
const [list, setList] = useState([]);
const [number, setNumber] = useState('');
const inputEl = useRef(null);
const onChange = useCallback(e => {
setNumber(e.target.value);
}, []); // ์ปดํฌ๋ํธ๊ฐ ์ฒ์ ๋ ๋๋ง๋ ๋๋ง ํจ์ ์์ฑ
const onInsert = useCallback(() => {
const nextList = list.concat(parseInt(number));
setList(nextList);
setNumber('');
inputEl.current.focus();
}, [number, list]); // number ํน์ list๊ฐ ๋ฐ๋์์ ๋๋ง ํจ์ ์์ฑ
const avg = useMemo(() => getAverage(list), [list]);
return (
<div>
<input value={number} onChange={onChange} ref={inputEl} />
<button onClick={onInsert}>๋ฑ๋ก</button>
<ul>
{list.map((value, index) => (
<li key={index}>{value}</li>
))}
</ul>
<div>
<b>ํ๊ท ๊ฐ:</b> {avg}
</div>
</div>
);
};
export default Average;
โ๏ธ ๋ก์ปฌ ๋ณ์ ์ฌ์ฉํ๊ธฐ
์ถ๊ฐ๋ก ์ปดํฌ๋ํธ ๋ก์ปฌ ๋ณ์๋ฅผ ์ฌ์ฉํด์ผ ํ ๋๋ useRef๋ฅผ ํ์ฉํ ์ ์์. ์ฌ๊ธฐ์ ๋ก์ปฌ ๋ณ์๋, ๋ ๋๋ง๊ณผ ์๊ด์์ด ๋ฐ๋ ์ ์๋ ๊ฐ์ ์๋ฏธํจ.
// ์์ ์ฝ๋
import React, { Component } from 'react';
class MyComponent extends Component {
id = 1
setId = (n) => {
this.id = n;
}
printId = () => {
console.log(this.id);
}
render() {
return (
<div>
MyComponent
</div>
);
}
}
export default MyComponent;
ํจ์ํ ์ปดํฌ๋ํธ๋ก ์์ฑํ๋ค๋ฉด?
// ์์ ์ฝ๋
import React, { useRef } from 'react';
const RefSample = () => {
const id = useRef(1);
const setId = (n) => {
id.current = n;
}
const printId = () => {
console.log(id.current);
}
return (
<div>
refsample
</div>
);
};
export default RefSample;
ref ์์ ๊ฐ์ด ๋ฐ๋์ด๋ ์ปดํฌ๋ํธ๊ฐ ๋ ๋๋ง๋์ง ์๋๋ค๋ ์ ์๋ ์ฃผ์!
๋ ๋๋ง๊ณผ ๊ด๋ จ๋์ง ์์ ๊ฐ์ ๊ด๋ฆฌํ ๋๋ง ์ด๋ฌํ ๋ฐฉ์์ผ๋ก ์ฝ๋๋ฅผ ์์ฑํด์ผ ํจ.