Dohyung Ahn
Frontend Engineer at protopie.io
type Component = (props, state) => UI
// 이게 성립한다면
f(props, state) => UI
// 이것도 성립합니다
f(g(h(props, state))) => UI
// 입력값이 같으면 결과값이 같다는 전제 아래
function f(props) {
const name = props.name
const genre = props.genre
const likeCount = props.likeCount
const makeArtist = () => `Artist name: ${name}, Genre: ${genre}`
return {
artistInfo: makeArtist(),
likeCount
}
}
f({name: 'Michael Jackson', genre: 'Pop', likeCount: 1})
// {artistInfo: 'Artist name: 'Michael Jackson, Genre: Pop', likeCount: 1}
f({name: 'Michael Jackson', genre: 'Pop', likeCount: 2})
// {artistInfo: 'Artist name: 'Michael Jackson, Genre: Pop', likeCount: 2}
f({name: 'Michael Jackson', genre: 'Pop', likeCount: 3})
// {artistInfo: 'Artist name: 'Michael Jackson, Genre: Pop', likeCount: 3}
function Counter() {
const [count, setCount] = useState(0)
function handleAlertClick() {
setTimeout(() => {
alert('클릭한 횟수: ' + count)
}, 3000)
}
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
<button onClick={handleAlertClick}>
Show alert
</button>
</div>
)
}
// 처음 랜더링 시
function Counter() {
const count = 0; // useState() 로부터 리턴 // ...
function handleAlertClick() {
setTimeout(() => {
alert('You clicked on: ' + count);
}, 3000);
}
// ...
}
// 클릭하면 함수가 다시 호출된다
function Counter() {
const count = 1; // useState() 로부터 리턴 // ...
function handleAlertClick() {
setTimeout(() => {
alert('You clicked on: ' + count);
}, 3000);
}
// ...
}
// 또 한번 클릭하면, 다시 함수가 호출된다
function Counter() {
const count = 2; // useState() 로부터 리턴 // ...
function handleAlertClick() {
setTimeout(() => {
alert('You clicked on: ' + count);
}, 3000);
}
// ...
}
function Counter() {
const [count, setCount] = useState(0)
useEffect(() => {
setTimeout(() => {
console.log(`You clicked ${count} times`)
}, 3000)
})
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
)
}
useEffect(() => {
// ...
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange)
return function cleanup() {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange)
}
})
class Chart extends Component {
componentDidMount() {
// 차트가 마운트되면 실행
}
componentDidUpdate(prevProps) {
if (prevProps.data == props.data) return
// data 속성이 업데이트 되었다면 실행
}
componentWillUnmount() {
// 차트가 언마운트 되기 전에 실행
}
render() {
return (
<svg className="Chart" />
)
}
}
const Chart = ({ data }) => {
useEffect(() => {
// 차트가 마운트되면 실행
// 차트가 업데이트되면 실행
return () => {
// 데이터가 업데이트되면 실행
// 차트가 언마운트되기 전에 실행
}
}, [data])
return (
<svg className="Chart" />
)
}
function Greeting({ name }) {
// props, state 가 변할 때마다 실행된다.
useEffect(() => {
document.title = 'Hello, ' + name
})
return (
<h1 className="Greeting">
Hello, {name}
</h1>
)
}
useEffect(fn, deps)
<h1 className="hello">
Hello, Dohyung
</h1>
// { className: 'hello', children: 'Hello, Dohyung' }
<h1 className="hello">
Hello, Lance
</h1>
// { className: 'hello', children: 'Hello, Lance' }
children 부분만 바뀌었으니 children만 업데이트
useEffect(() => {
document.title = 'Hello, ' + name
})
// () => { document.title = 'Hello, Dohyung' }
useEffect(() => {
document.title = 'Hello, ' + name
})
// () => { document.title = 'Hello, Lance' }
두 함수의 달라진 부분을 어떻게 직접 잡아낼 수 있나?
useEffect(() => {
document.title = 'Hello, ' + name
}, [name])
function Counter() {
const [count, setCount] = useState(0)
useEffect(() => {
const id = setInterval(() => {
setCount(count + 1)
}, 1000)
return () => clearInterval(id)
}, [])
return <h1>{count}</h1>
}
// 실제로 일어나는 일
function Counter() {
// ...
useEffect(() => {
const id = setInterval(() => {
setCount(0 + 1) // 계속 0 반복
}, 1000)
return () => clearInterval(id)
}, [])
// ...
}
카운터를 동작하게 하는데
정말 count 값이 필요했을까요?
setState(prevState => nextState)
function Counter() {
const [count, setCount] = useState(0)
useEffect(() => {
const id = setInterval(() => {
setCount(c => c + 1)
}, 1000)
return () => clearInterval(id)
}, [])
return <h1>{count}</h1>
}
function Counter() {
const [count, setCount] = useState(0);
const [step, setStep] = useState(1);
useEffect(() => {
const id = setInterval(() => {
setCount(c => c + step)
}, 1000)
return () => clearInterval(id)
}, [step])
return (
<>
<h1>{count}</h1>
<input
value={step}
onChange={e => setStep(Number(e.target.value))}
/>
</>
)
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState)
const { count, step } = state
useEffect(() => {
const id = setInterval(() => {
dispatch({type: 'TICK'})
}, 1000)
return () => clearInterval(id)
}, [dispatch])
return (
<>
<h1>{count}</h1>
<input
value={step}
onChange={
e => dispatch({
type: 'SET_STEP',
payload: Number(e.target.value)
})
}
/>
</>
)
}
function SearchResults() {
const [query, setQuery] = useState('react');
useEffect(() => {
function getFetchUrl() {
return 'https://hn.algolia.com/api/v1/search?query='
+ query
}
async function fetchData() {
const result = await axios(getFetchUrl());
setData(result.data);
}
fetchData()
}, [query])
// ...
}
function SearchResults() {
const [query, setQuery] = useState('react');
const getFetchUrl = useCallback(() =>
'https://hn.algolia.com/api/v1/search?query=' + query
, [query])
useEffect(() => {
const url = getFetchUrl();
// ... 데이터를 불러와서 무언가를 한다 ...
}, [getFetchUrl])
// ...
}
useEffect는 엄연히 리액트의 저수준 API
리액트 커뮤니티에서
다양한 방식으로 추상화된 커스텀 훅이 나옴
특수한 목적이 아닌 이상
useEffect 자체를 쓸 일은 많이 줄어들 것
By Dohyung Ahn
처음부터 리액트+훅을 접하거나 클래스 컴포넌트에서 훅으로 넘어오면서 제일 중요한 기본 훅인 useEffect를 쉽게 다루기 위한 핵심 정보를 전달해 드립니다.