React hooksを使ってみた
(LTのスライド資料です。右の中央のメニューから"start presentation"を選択するとスライドがフルスクリーン表示されます。)
自己紹介
岩本 海童 (いわもと かいどう)
機械学習の研究したりフロントエンド開発したりしています
TypeScriptとReactが大好き
React hooksって何?
Reactの関数コンポーネントで状態を持ったりその他のReactの機能を使ったりできる機能
たぶん例を見た方が早い
参考資料
hooksの例: useState
状態を保持する
クラスコンポーネントのstateに相当する
code:ts
function MyComponent() {
return <>
{count}
<button onClick={() => setCount(count + 1)}>+1</button>
</>
}
hooksの例: useEffect
コンポーネントのマウントや更新などのタイミングで特定の処理を実行する
クラスコンポーネントのcomponentDidMount, componentDidUpdate, componentWillUnmountに相当する
code:ts
function TweetList({tweets, loadTweets}: Props) {
useEffect(() => {
loadTweets()
}, [])
return <ul>{tweets.map((tw) => <Tweet tweet={tw}/>)}</ul>
}
hooksで何が嬉しいか?
関数コンポーネントだけ書けばよくなる
クラスコンポーネントでしかできないことが無くなった
高階コンポーネントやrender propsを使わずに再利用可能なコードを書きやすくなる
コンポーネントの中にあるコードを切り出すことが容易になる
クラスコンポーネントに比べて、コードが目的ごとにまとまる
hooksを組み合わせてカスタムhooksを作る
code:ts
function useWindowWidth() {
useEffect(() => {
const handler = () => setWidth(window.innerWidth)
window.addEventListener('resize', handler)
return () => window.removeEventListener('resize', handler)
}, [])
return width
}
カスタムhooksを使ってみる
code:ts
function MyComponent() {
const width = useWindowWidth()
return <div>width is {width}</div>
}
useEffectの第1引数の関数が関数を返しているんだが?
関数は返しても返さなくてもよい
返す関数は、外側の関数が起こした副作用に対して、後片付けを行う
useEffectの第2引数の配列は何?
副作用が実行されるタイミングを制御するもの & メモ化のためのキー
未指定の場合は更新のたびに毎回実行される
空配列の場合は1回だけ実行される
空ではない配列の場合は配列の中身が前回と違ったときだけ実行される
同じような引数がいくつかの他のhooks関数にもある
値が変わるたびに実行される副作用
リストから商品が選択されるたびにその詳細を読み込む
code:ts
function MyComponent() {
...
useEffect(() => {
loadProduct(productId).then(...)
...
}
hooksの注意点
ループやif文の中で使わない
同じ関数コンポーネント内でhooks関数が呼ばれる回数と順番は常に同じでなくてはならないため
関数コンポーネントの中だけで使う
Reactのレンダリング処理と結びついているので、適当な場所で使うとエラーになる
「中」というのは、関数が呼び出されてから、returnが実行されるまで
hooksの使用例
hooksの使用例: useStateのセッターにフックをかける (1)
code:ts
}
hooksの使用例: useStateのセッターにフックをかける (2)
code:ts
function MyComponent() {
)
...
}
hooksの使用例: Reduxのストアにアクセスしてみる
今まで: react-redux
connectでラップするのだるい (特にTypeScriptで書く場合)
関数コンポーネントでフラットに書けて最高!
redux-react-hook
React hooksでReduxのストアにアクセスする
使うのは主にuseMappedState, useDispatcher
自分がやった+α
useDispatcherがアプリで定義されたdispatcherの型を使うように再定義
useActionCreator
redux-react-hooksの使用例
code:ts
function ShowProduct({productId}: Props) {
const product = useMappedState(useCallback(
(state) => findProductById(state.products, productId),
[]))
const dispatch = useDispatch()
const loadProduct = useCallback(
(productId: number) => dispatch(actions.loadProduct(productId)),
)
return <Product product={product}/>
}
react-reduxだと...?
code:js
const ShowProduct = connect(
(state, {productId}) => ({product: findProductById(state.products, productId)}),
(dispatch) => ({loadProduct: (productId) => dispatch(actions.loadProduct(productId)})
)(
({productId, product, loadProduct}) => {
return <Lifecycle componentDidMount={() => loadProduct(productId)}>
<Product product={product} />
</Lifecycle>
}
)
Lifecycleは、関数コンポーネント内でコンポーネントのライフサイクルイベントを扱えるようにしたやつ
react-reduxだと...?
ここではあえてJSで書いてみましたが、TSで型を書くとめちゃくちゃしんどいです
余計な型を書かないためにredux-react-hookに移行してもいいくらい
redux-react-hookの例では、必要な型は省略してません
どちらの書き方が良いか、好みが分かれそうなところではある...
redux-react-hook + α
useDispatchの型を上書き
制約を強くして、事前に定義されたアクションの型のみ受け付けるようにする
useActionCreatorを定義
(...args) => dispatch(actions.someActionCreator(...args))って書くのだるい
useActionCreator(dispatch, actions.someActionCreator)で同等のふるまいをするようにした
hooksの使用例: フォームをいい感じにする
Reactでフォームを扱うの、全部一から書くとけっこうしんどい
hooksで解決!!
AngularJS(古い)のフォームの仕組みが頭に残っていたので、参考にしてみた
実装はまだ非公開です
そのうち公開するかも
hooksの使用例: フォームをいい感じにする
code:ts
function MyComponent() {
const email = useFormField({
name: 'email',
constraints: {
email: true,
required: true,
},
})
const phoneNumber = useFormField({
name: 'phoneNumber',
constraints: {
digits: true,
minLength: 4,
},
)
const onSubmit = useCallback((e) => {
e.preventDefault()
e.stopPropagation()
// フォームオブジェクトやフィールドオブジェクトはisValidというプロパティを持つ
if (form.isValid) {
// do something
}
}, [])
return <form onSubmit={onSubmit}>
{/* 必要なpropsはフィールドオブジェクトが持っているので、自分では何も書かなくても良い。
自分で追加したければその後ろに書き足すだけ。 */}
<input {...email.props} />
<input {...phoneNumber.props} />
{/* フィールドオブジェクトのerrorsには、constraintsで指定したのと同じプロパティが含まれていて、
制約に違反した場合にtrueになる */}
{email.errors.required && <p>メールアドレスが未入力!</p>}
{email.errors.email && <p>メールアドレスの形式が不正!</p>}
{phoneNumber.errors.digits && <p>電話番号の形式が不正!</p>}
{phoneNumber.errors.minLength && <p>電話番号が短すぎ!</p>}
<button type="submit">Go</button>
</form>
}
hooksの使用例: フォームをいい感じにする
ちなみにこれ、TypeScriptで型をがちがちに書いています
errorsプロパティを型でちゃんと縛るところをがんばりました
hooksを使ってみて
良いものだと思います
関数かクラスか、で悩まなくて良くなりそう
全体的にコードのインデントレベルが浅くなって、読みやすいコードになる
特にTypeScriptでRedux使っている方におすすめです