이 발표를 보고나서 Dan Abramov에게 묘한 매력을 느꼈다. 영어도 잘 못하면서 팔로우를 시작했다. Reactjs에 대해서 이런저런 것을 많이 보게 되었다. 그리고 어느 덧 발표했던 기능이 정식출시했다. 이름은 React Hooks다.
기본 개념
Hooks소개의 Motivation에도 말하고 있지만 Hooks의 기본 목적인 간결함이다. 다음의 코드는 Hooks를 사용한 코드다.
import React, { useState } from 'react';
function Example() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
이 코드가 얼마나 간결한 코드인가하면 Hooks를 사용하지 않은 다음의 코드를 보면 알 수 있다.
import React, { Component } from 'react';
class Example extends Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
this.handleChangeCount = this.handleChangeCount.bind(this);
}
handleChangeCount() {
this.setState({
count: this.state.count + 1
})
}
render() {
const { count } = this.state;
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => this.handleChangeCount()}>
Click me
</button>
</div>
);
}
}
동일한 동작을 하는 코드지만 뭔가 많이 길다. count++
를 하기 위해서 함수를 만들고 bind
도 한다. 그리고 state.count
를 만들기 위해서 constructor
도 만들고 아무 의미없는 super(props)
도 넣어줘야 했다. render
에서 this
를 생략하기 위해서 const
로 별도의 변수를 만들기도 했다. Hooks를 사용한 코드는 사용하지 않은 코드에서 count를 컨트롤 하기 위해 만들었던 14줄의 코드를 단 한 줄로 만들었다.
const [count, setCount] = useState(0);
여기서 사용한 useState
가 바로 Hooks의 하나다.
useState
useState
는 component
의 state
를 관리하기 위한 hook이다. useState
의 코드만 놓고 보면 사실 이전과 크게 다르지 않은 것 같다. 하지만 사용에는 큰 차이가 있다. hooks를 사용할 때는 기본적으로 component
를 class
가 아닌 function
으로 만든다. 그래서 setState
를 하기 위한 handler도 필요없고 자연스레 handler를 bind
할 필요도 없어진다. javascript의 클로저로 인해 어디서든 바로 setState
에 값을 넘겨주면 된다. state
값도 마찬가지다.
// before
this.state = { state: initialState };
this.setState({ state: newValue });
// hooks
const [state, setState] = useState(initialState);
setState(newValue);
다른 유용한 hook들도 몇개 더 살펴보자.
useEffect
useEffect
는 쉽게 말해 componentDidUpdate
라고 보면 된다. 다른 점은 componentDidMount
, componentWillUnmount
가 하는 일도 한다. 기본구조는 다음과 같다.
useEffect(() => {
// BODY
return () => {
// CLEAN-UP
};
}, [TARGET]);
React Component의 lifecycle상에서 언제 실행되는지를 보면 쉽게 무슨 일을 하는지 파악할 수 있다.
- Mounting에서는
render
후에componentDidMount
가 실행된다. Hooks에서는BODY
부분이 실행된다. - Unmounting에서는
componentWillUnmount
가 실행된다. Hooks에서는CLEAN-UP
부분이 실행된다. - Updating에서는
render
후에componentDidUpdate
가 실행된다. Hooks에서는CLEAN-UP
이 실행된 후BODY
가 실행된다. Updating은 이전에도 그렇지만props
,state
가 변경되면 수행된다.
여기까지만 보면 useEffect
내부가 굉장히 커지고 복잡해질 것 같다. 하지만 TARGET
으로 인해 이 복잡성이 크게 줄어든다. TARGET은 변경을 추적할 대상이다. 아래의 코드에서 첫번째 useEffect
는 count
라는 state
값이 변경된 경우에만 동작한다. 두번째 useEffect
는 props.post.title
값이 변경된 경우에만 동작한다. TARGET
을 생략하면 모든 값에 대해서 동작한다.
useEffect(() => { ... }, [count]);
useEffect(() => { ... }, [props.post.title]);
물론 componentDidUpdate
를 잘 사용하지않는 나로서는 조금 번거롭게 느껴지기도 한다.
useRef
useRef
는 React.createRef
와 거의 같다. 다른 점은 initialValue
를 사용할 수 있다는 점이다. 하지만 내가 사용하는 대부분의 경우 ref
는 container
와 child
의 검증을 위해서 사용했다. 그래서 이게 있다고 ref
를 form
에 사용할 것 같지는 않다.
useReducer
useReducer
는 코드만 보면 redux
에서 사용하던 reducer
와 흡사하다. 하지만 이것은 redux
가 아니다. 여러 컴포넌트에 useReducer
로 만든 reducer
를 같이 사용할 수 있다면 완전히 대체할 수도 있을 것 같다.
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
function Counter({initialCount}) {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'increment'})}>+</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
</>
);
}
정리한 것 외에도 여러가지 Hook이 있고 hook을 만들 수도 있다. 어쨌거나 코드 스타일을 OOP에서 Function으로 바꿔야 해서 좀 더 javascript다운 코드를 만들 수 있을 것 같고 무엇보다 간결한 코드를 만들 수 있을 것 같다. 테스트도 더 쉬워진 것 같다. 얼른 해보고 싶다.
+ 추가: 몇개 변경해봤는데 너무 좋다. redux를 어떻게 써야하는 지를 몰라서 일단 중단했는데 알아보고나서 더 해야겠다.