Hook은 함수 컴포넌트에서 React state
와 생명주기 기능(lifecycle features)을 “연동(hook into)“할 수 있게 해주는 함수이다. Hook은 class
안에서는 동작하지 않는 대신에 class
없이 React를 사용할 수 있게 해주는 것이다.
import React, { useState } from "react"
function Component(props) {
const [state, setState] = useState(init); // (o)
if (...) { const [state, setState] = useState(init); } // (x)
}
컴포넌트가 렌더링될 때마다 항상 동일한 순서로 호출되는 것을 보장하기 위해서 조건문이나 반복문, 중첩된 함수내에서 Hook을 호출하면 안된다. 그 대신에 컴포넌트 내 최상단에서만 Hook을 호출하도록 하자.
Hook을 일반적인 javascript 함수 내에서 호출하면 안된다. 그 대신 다음 2가지 경우에는 가능하다.
useState
는 현재 state
값과 이를 업데이트하는 함수를 쌍으로 제공한다. 이 함수는 event handler
나 다른 곳에서 호출할 수 있다. useState는 React class
의 setState
와 거의 동일하지만 이전 state
와 새로운 state
가 합쳐지지 않는 차이가 있다.
import React, { useState } from 'react';
function App(props) {
const [state, setState] = useState(initialState);
const [count, setCount] = useState(0); // ex. 초기값이 0인 state의 count 변수
return (...);
}
useState
의 인자(initialState
)는 최초 렌더링 과정에서 state
의 초기값으로 지정된다. setState
는 state
를 갱신할 때 사용되고 새 state
값을 받아서 컴포넌트의 리렌더링을 큐에 등록한다.
Q. useState는 어떻게 상태를 저장하는가?
A. 일단 함수는 실행이 완료되면 함수 내에서 사용했던 메모리를 정리(GC)하기 때문에 상태를 저장하지 못한다. 이 때문에 React Hooks 이전에는 클래스 컴포넌트를 사용할 수 밖에 없었다.
함수형 컴포넌트는 Hooks를 사용하면서 상태 관리가 가능해졌다. Hooks에서state
를 저장하려면useState()
를 사용한다.useState
역시 함수이며 클로저를 이용해 변수를 저장한다.// 간략한 useState의 내부 구조 const useState = (init = undefined) => { let value = init const getter = () => value const setter = next => (value = next) return [getter, setter] } const [state, setState] = useState('클로저')
초기값을 받아 내부의 지역변수
value
에 할당한다. 내부 함수getter()
는 지역변수init
을 바라보고 있다. 또 다른 내부 함수setter()
는next
라는 인자를 받아value
의 값을 수정한다. 이후 다시getter()
를 호출하게 되면 변경된value
의 값을 호출하게 된다.
두 함수는 배열 형태로 리턴되고useState
를 사용할 때는 배열 구조분해 할당 형태로 많이 사용하게 된다. 일반적인 함수라면return
과 함께 실행이 종료되고 나서 내부의 데이터들이 가비지 컬렉팅 되어야 하겠지만, 이 경우 내부 함수가 지역 변수를 참조하고 있으므로 사라지지 않는다.
또한 외부로 노출된getter
,setter
함수를 통해 내부 변수에 지속적으로 접근하며 호출/재할당을 할 수 있다. 이는 클래스형 컴포넌트에서state
가 해온 역할과 동일하다.
useEffect
는 함수 컴포넌트 내에서 이런 side effects
를 수행할 수 있게 해준다. React class
의 componentDidMount
나 componentDidUpdate
, componentWillUnmount
와 같은 목적으로 제공되지만, 하나의 API로 통합된 것이다.
import React, { useEffect } from 'react';
function App(props) {
useEffect(() => {
// effect content
return (() => {
// clean up할 content === React class의 componentWillUnmount
});
}, [...]); // 두 번째 인자인 [] 내부에 작성된 state가 변경될 때만 함수(첫 번째 인자)를 실행
return (...);
}
React는 우리가 넘긴 함수를 기억했다가(이 함수를 ‘effect’라고 부른다.) DOM 업데이트를 수행한 후에 불러낸다. useEffect는 기본적으로 최초 렌더링과 업데이트할 때마다 수행되는데 effect를 필요에 따라 수정하는 방식으로 다양한 조건부 동작을 수행하도록 변경할 수 있다. 또한 useEffect 내부에서 return 하는 함수는 해당 컴포넌트가 종료되고나서 실행된다. 이는 React class 컴포넌트의 API인 componentWillUnmount
와 동일한 기능이다.
class 컴포넌트
import React from "react";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
componentDidMount() {
console.log(`componentDidMount`);
}
componentWillUpdate() { // Deprecated이지만 비교를 위해서 사용
console.log(`componentWillUpdate`);
}
componentDidUpdate() {
console.log(`componentDidUpdate`);
}
componentWillUnmount() {
console.log(`componentWillUnmount`)
}
render() {
return (
<div>
<h1>Count: {this.state.count}</h1>
<button onClick={() => {this.setState({ count: this.state.count + 1 })}}>
Click me
</button>
</div>
);
}
}
export default App;
function 컴포넌트
import React, { useState, useEffect } from 'react';
function App() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log(`componentDidMount`);
return ( () => {
console.log(`componentWillUnmount`);
})
}, []); // 빈 배열을 두 번째로 넘기면 최초 mount에만 effect 실행
useEffect(() => {
console.log(`componentWillUpdate`);
return ( () => {
console.log(`componentDidUpdate`);
}) // 두 번째 인자를 넘기지 않으면 최초 mount와 state가 변경될 때마다 effect 실행
});
useEffect(() => {
console.log(`only count componentWillUpdate`);
return ( () => {
console.log(`only count componentDidUpdate`);
})
}, [count]); // count가 변경될 때만 effect 재실행
return (
<div className="App">
<h1>Count: {count}</h1>
<button onClick={() => setCount(count + 1)}>
Click me!!
</button>
</div>
);
}
export default App;