React Hooks
React 16.8로 업데이트 되며 class
를 작성하지 않고도 state
와 같은 React의 기능을 사용할 수 있게 되었습니다.
import React, { useState } from 'react';
function Example() {
// "count"라는 새로운 상태 값을 정의합니다.
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
Hook의 특징
Hook의 특징은 다음과 같습니다.
- 선택적 사용: 기존의 코드를 다시 작성할 필요 없이 컴포넌트 안에서 Hook을 사용할 수 있습니다. Hook이 필요없는 경우에는 굳이 사용하실 필요는 없습니다.
- 100% 이전 버전과의 호환성
- React 컨셉을 대체하지는 않습니다: Hook은 props, state, context, refs, lifecycle와 같은 React 개념에 좀 더 직관적인 API를 제공합니다.
동기
- 컴포넌트 사이에서 상태와 관련된 로직을 재사용하기 어렵습니다.
- 복잡한 컴포넌트들은 이해하기 어렵습니다.
- Class는 코드의 재사용성과 코드 구성을 어렵게 만듭니다.
🥊 State Hook
아래의 예시들에서 지속적으로 보일 useState
가 바로 Hook입니다!
useState
는 함수 컴포넌트로 local state를 사용하는데 쓰입니다.
const [state, stateUpdaterFunction] = useState(initialStateValue)
Declare State Variable
() => {
const [count] = useState(100)
return <div> State variable is {count}</div>
}
Update State Variable
() => {
const [age, setAge] = useState(19)
const handleClick = () => setAge(age + 1)
return (
<div>
Today I am {age} Years of Age
<div>
<button onClick={handleClick}>Get older! </button>
</div>
</div>
)
}
useState
를 통해 state variable을 쉽게 변경할 수 있습니다.
Multiple State Variables
() => {
const [age, setAge] = useState(19)
const [siblingsNum, setSiblingsNum] =
useState(10)
const handleAge = () => setAge(age + 1)
const handleSiblingsNum = () =>
setSiblingsNum(siblingsNum + 1)
return (
<div>
<p>Today I am {age} Years of Age</p>
<p>I have {siblingsNum} siblings</p>
<div>
<button onClick={handleAge}>
Get older!
</button>
<button onClick={handleSiblingsNum}>
More siblings!
</button>
</div>
</div>
)
}
위의 예시와 같이 여러개의 state도 함수형 컴포넌트로 선언하고 수정될 수 있습니다.
Use Object State Variable
() => {
const [state, setState] = useState({ age: 19, siblingsNum: 4 })
const handleClick = val =>
setState({
...state,
[val]: state[val] + 1
})
const { age, siblingsNum } = state
return (
<div>
<p>Today I am {age} Years of Age</p>
<p>I have {siblingsNum} siblings</p>
<div>
<button onClick={handleClick.bind(null, 'age')}>Get older!</button>
<button onClick={handleClick.bind(null, 'siblingsNum')}>
More siblings!
</button>
</div>
</div>
)
}
String 혹은 Number와 별개로 Object 또한 초기값으로 설정이 가능합니다.
하지만, 통합되지 않은 상태로 전체 Object를 updater function으로 전달해야 합니다.
Initialize State from Function
() => {
const [token] = useState(() => {
let token = window.localStorage.getItem("my-token");
return token || "default#-token#"
})
return <div>Token is {token}</div>
}
Functional setState
const [value, updateValue] = useState(0)
// both forms of invoking updateValue below are valid 👇
updateValue(1);
updateValue(previousValue => previousValue + 1);
🥊 Effect Hook
Effect Hook을 사용하면, 함수 컴포넌트에서 side effect
(데이터 가져오기, subscription, 수동으로 컴포넌트의 DOM수정 등)를 수행할 수 있습니다.
useEffect(effectFunction, arrayDependencies)
리액트의 class 생명주기 메서드에 친숙하다면, useEffect Hook을
componentDidMount, componentDidUpdate, componentWillMount가 합쳐진 것으로 생각해도 좋습니다.
Basic side effects
() => {
const [age, setAge] = useState(0)
const handleClick = () => setAge(age + 1)
useEffect(() => {
document.title = 'You are ' + age + ' years old!'
})
return <div>
<p> Look at the title of the current tab in your browser </p>
<button onClick={handleClick}>Update Title!! </button>
</div>
}
useEffect
를 이용해서 우리는 리액트에게 컴포넌트가 렌더링 이후에 어떤 일을 수행해야 하는 지를 이야기합니다.
리액트는 우리가 넘긴 함수(effect)를 기억했다가 DOM 업데이트를 수행한 이후에 불러낼 것입니다.
useEffect
는 렌더링 이후 매번 수행됩니다. 기본적으로 첫 번째 렌더링과 이후의 모든 업데이트에서 수행됩니다.
리액트는 effect가 수행되는 시점에 이미 DOM이 업데이트 되었음을 보장합니다.
Effect with Cleanup
() => {
useEffect(() => {
const clicked = () => console.log('window clicked')
window.addEventListener('click', clicked)
// return a clean-up function
return () => {
window.addEventListener('click', function() {
console.log('new');
});
}
}, [])
return <div>
When you click the window you'll
find a message logged to the console
</div>
}
Clean-up은 목적을 분명히 하기위해 Clean-up이라고 부르지만 꼭 화살표 함수를 반환할 필요는 없으며, 다른 이름으로 불러도 무방합니다.
effect에서 함수를 반환함으로 인해, 정리(Clean-up)을 위한 함수를 반환할 수 있습니다.
Skipping Effect
() => {
const [randomNumber, setRandomNumber] = useState(0)
const [effectLogs, setEffectLogs] = useState([])
useEffect(
() => {
setEffectLogs(prevEffectLogs => [...prevEffectLogs, 'effect fn has been invoked'])
},
[]
)
return (
<div>
<h1>{randomNumber}</h1>
<button
onClick={() => {
setRandomNumber(Math.random())
}}
>
Generate random number!
</button>
<div>
{effectLogs.map((effect, index) => (
<div key={index}>{'🍔'.repeat(index) + effect}</div>
))}
</div>
</div>
)
}
useEffect
에서 특정 값들이 리렌더링 시에 변경되지 않는다면, useEffect
의 두 번째 인수로 배열을 넘겨 effect를 건너뛰도록 할 수 있습니다.
빈 배열을 넘긴다면, mount하는 경우에만 effect가 실행될 것입니다.
🖋Hook의 규칙
Hook은 Javascript 함수이지만, Hook을 사용할 때는 두 가지 규칙을 준수해야 합니다.
- 최상위(at the Top Level)에서만 Hook을 호출해야 합니다.
- 반복문, 조건문 혹은 중첩함수 내에서 Hook을 호출해서는 안됩니다.
- 오직 React 함수 내에서 Hook을 호출해야 합니다.
- 일반적인 Javascript 함수에서 호출해서는 안됩니다.
참고자료
- React Docs: https://ko.reactjs.org/docs/
- Hooks Cheatsheet: https://react-hooks-cheatsheet.com/