React Hooks api を眺める

React Conf 2018 にて Hooks APIが発表された。
ざっと触った時のメモ。

React Hooksとは

Functionalコンポーネントからコンポーネントの状態やライフサイクル機能をフックさせるAPI。
後方互換があるため既存のアプリにも将来的に組み込むことが可能。
提供されているReact Hooks apiから独自のHooksを

React Hooksを試す

React Hooks apiを使って無理やりカウンターを作る。
さすがに全ては使うことができないので、以下の機能を試した。

  • Basic Hooks
    • useState
    • useEffect
    • useContext
  • Additional Hooks
    • useReducer
    • useCallback
    • useRef

ちなみにReact Hooksは現時点でstableではないので、プロダクションアプリで利用すべきではない。
またTypeScriptなどで利用する場合、必ずしも型情報が充分に提供されているわけではない。
React Hooks apiのリストからわかる通り、useReducer useContext あたりを使うことで、将来的にはReduxは必要なくなる感じっぽい。
(Redux Devtoolsなどのコミュニティが作り上げたエコシステムがあるのですぐには無くならないかもしれない)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
import * as React from "react";
import { render } from "react-dom";

const IncrementAction = `increment`;
const DecrementAction = `decrement`;
const ResetAction = `reset`;
const InitAction = `init`;

type CounterAction = {
type?: string;
};

type CountType = {
count: number,
}

const initialState: CountType = { count: 0 };
const initAction = { type: InitAction };
const CounterState: CountType & CounterAction = Object.assign({}, initialState, initAction);
type CounterStateType = typeof CounterState;

function reducer(state: CounterStateType, action: CounterAction) {
switch (action.type) {
case ResetAction:
return initialState;
case IncrementAction:
return { count: state.count + 1 };
case DecrementAction:
return { count: state.count - 1 };
case InitAction:
default:
return state;
}
}

const Counter: React.FC<CounterStateType> = () => {
// const [state, setState] = useState(initialState);
const [state, dispatch] = React.useReducer(reducer, CounterState);

React.useEffect(
() => {
document.title = `Counter count is ${state.count}`;
},
[state.count]
);

const incrementButton = React.useRef<HTMLButtonElement>(null);
const onResetButtonClickHandler = () => {
if (incrementButton && incrementButton.current) {
incrementButton.current.focus();
}
};

const { increment, decrement, reset } = React.useContext(CounterContext);

const increaseClickHandler = React.useCallback(
() => dispatch({ type: "increment" }),
[]
);

const decreaseClickHandler = React.useCallback(
() => dispatch({ type: "decrement" }),
[]
);

const resetClickHandler = React.useCallback(
() => dispatch({ type: "reset" }),
[]
);

return (
<div>
<h1>Counter Example App</h1>
<p>count: {state.count}</p>
<button
onClick={() => {
resetClickHandler();
onResetButtonClickHandler();
}}
>
{reset}
</button>
<button onClick={increaseClickHandler} ref={incrementButton}>
{increment}
</button>
<button onClick={decreaseClickHandler}>{decrement}</button>
</div>
);
};

const CounterContext = React.createContext({
increment: "increment (+)",
decrement: "decrement (-)",
reset: "reset (0)"
});

const App = () => {
return (
<div>
<Counter count={1} />
</div>
);
};

render(<App />, document.getElementById("root"));

その他

React Hooks自体は単純なStack Queueで管理されているので、Root Componentレンダリングのタイミングで各コンポーネントのHooksが発火していく。

React hooks: not magic, just arraysでも言及されているようにその順序を破壊しないように使う必要がある。

1
Don’t call Hooks inside loops, conditions, or nested functions.

Rules of Hooks より


React Hooksの導入でアプリのリファクタリングにかける時間的なコストは下げられそう。
またパフォーマンスの問題などこれまでより考慮することが減りそうなのはとてもよい。

SuspenseなどからもReact Componentがあたかも生物のように自律的に機能することになっていきそうな感じだな。

参考にした記事

Hooks API Reference
Under the hood of React’s hooks system
React hooks: not magic, just arrays

今回作成したサンプル

https://codesandbox.io/embed/vj15lzxx4l

Comments