React Routerの公式ドキュメントを読んでみた
まだそんなにガチで使ったことなかったからざっくり流し読みした程度。
reacttraining.com まずはこのPhilosophyページを読むとわかりやすいと思った。
以下雑感。
Redirect
reacttraining.com
こう言う風にコンポーネントを書くようにしてRedirectするのは、railsのredirect_to
とかに慣れてるとちょっと面食らうなと思った。
routhingのネスト
railsのresources
みたいな感じのこともできるんだと感動。
これうまく使えれば、railsみたいにコントローラーファイルを複数に分けることもできるよなあ。
すごく便利そう。
デバイス別のレイアウトの分け方
reacttraining.com
これもすごいなと思った。
ルーティングでここまでできるんだと感動。
media queryのところは
GitHub - d6u/react-container-query: Modular responsive component
この辺とかつかってやればよさそうだし。
最近やたら薄い記事しか書いてないな...
仕事で色々知見が貯まってきてはいるので、どこかのタイミングで書いておきたい。
Reduxの公式ドキュメントを読んでみた
読んでみたはいいけど、全然頭に入ってこねー。
正直、仕事でReduxを使ったことがない。
仕事でやったことあるのは、RailsのviewをReactで作るって感じで、stateもそんなに大きくないからHooksで十分だった。
個人開発でも大体Hooksで書いてしまう。
これから使う予定があるとしたら、個人開発で作ってるSPAの認証の際に、認証情報をReduxに入れるくらいだと思うんだけど、それだとあまりReduxの設計にこだわらなくていいんだよなあ。
useReducer
とReactのcontext
が出てきた今、そんなにReduxを使う機会は多くなっていないような気がする。
「Reduxは大規模開発では使ったほうがいい」という趣旨の記事を何度かみたことあるけど、まだそんな規模の開発をしたことないから、公式ドキュメントのAdvanced TutorialやRecipesに書かれている内容はピンとこなかった。
むしろ、JavaScript: Reduxが必要なとき/不要なとき(翻訳) に書いてあるような、Reduxを使わないでいいタイミングや逆に使うべきタイミングを知りたい。
この記事はHooks登場以前のものみたいだけど、Hooks登場以降のRedux使用の見分け方を知りたいな。
大規模開発以外にも色々な条件がありそうな気がする。
またReduxを実際に使うタイミングで読んでみることにしよう。
Reactの公式ドキュメント読んでみた その3 HOOKS編
今回はReactのHooksの公式ガイドを読んでみた。
Hooksはこれまで仕事などで使っていたので、割とわかっていると思っていたが、思いのほか知らないことが多かった。やっぱり公式を読んでおくのは大事。
ただ、まだパフォーマンスを気にするほどの画面は作ったことがないので、その辺は飛ばしている。
あと、まだ使ったことのない useMemo
とか useCallback
などは、また別の記事に使い心地について書くようにしたい。
前回のstateやpropsの取得
ja.reactjs.org
refを使って自分でやる必要があるのか。
refってフォームの入力値を取得するためにしか使ってこなかったけど、こういう使い方もあるのね。
DOMノードの位置とサイズの測定
ja.reactjs.org
こういうのもref使うのか。
思ったよりrefって活躍しそうだからちゃんと使えるようになっておこう。
どうでもいいけど、refってTypeScriptで書きにくい気がする。
useEffect
内で使用する関数の宣言場所
ja.reactjs.org
これ今やっている仕事でちょうどダメな例の方をやってしまっていた...
出社したら修正しておこう。
というか、基本的に、useEffect
内で使う関数は、もしその関数がstate
やprops
を使うならuseEffect
の中に書いておけって話か。
何でもかんでも内部に書くと見通しが悪くなってあまり好きじゃないんだけど、そっちのほうが安全そう。
単純に計算結果を返すだけの関数なら外部に書いても大丈夫みたいだけどね。
子コンポーネントのメモ化
これは少し感動した。
いつか使ってみたい。
計算量の大きいオブジェクトの遅延作成
まだそんなに計算量の大きいオブジェクトを作ったことはないけど、これもよさそうなテクニック。
setStateの初期値には関数も渡せるのね。
Reactの公式ドキュメント読んでみた その2ADVANCED GUIDES編
前回はReactの基本コンセプトを読んだので、今回は次のADVANCE GUIDESを読んでいこうと思う。
ただ、今回は前回ほどの量はなさそう。
理由としては、これどのタイミングで使うんだ...?ってものがあったり関数コンポーネントを使うようになってる現在ではあまり役に立たなそうなものがあったりしたから。
コード分割(code-splitting)
これは便利そう。要はlazy loadしたいのね。
業務でSPAを作ったことはまだないが、実際に作ることになったらよさそうだなあと思う。
Contextの使用
Contextってグローバルに値を読めるようになって怖いからなるべく使わないようにしてたんだけど、実際どのくらい使われるのか謎。
ja.reactjs.org
ここの冒頭にも書いてあるけど、必要な値を直接埋め込んでしまうから再利用性低くなるのはそうだと思う。
Context使うくらいならRedux使ったりしたほうがいいんじゃないだろうか。
blog.logrocket.com ここを読むと、Reduxとかを使うのはやりすぎな時に使う感じっぽい。
Error Boundary
ただ、Error Boundary
については、ComponentDidCatch
っていうライフサイクルメソッドを使わないといけないらしいんだけど、関数コンポーネントを使うのが主流となった今はこれは使えないよなあと思った。
blog.bitsrc.io
で、色々ググったところ、現時点ではError Boundaryを使うなら結局はクラスコンポーネントを使わないといけないっぽい?
この辺はまたおいおい調べよう。
今回はだいたいこんな感じ。
次はHooksについてのドキュメントを読もう。
Reactの公式ドキュメント読んでみた その1 MAIN CONCEPT編
最近はReactのドキュメントに目を通している。 以前にいきなり納期がタイトなReactの案件を振られて、四苦八苦しながらコード書いて以降、Reactについてわかった気になっていたが、改めてドキュメントを読むと色々と学びがあったのでメモしておく。
ある条件下でレンダリングさせない
これは知らなかった。
return
でnull
を返せばそのコンポーネントのレンダリングを阻止できるのね。
見せたくないだけなら、条件分岐の元になるフラグでdisplay: none
をスタイルに追加すればいいかなと思ってたけど、どっちがいいんだろう。
stackoverflowにこんな質問があったけど、CSSでやれば早いし、return null
でやればDOMは小さくなるよ、って感じなのかな。
そもそもそんなに気にしなくてもいいのかも。
map
で要素を繰り返し表示するときに key
にindex
を使わない
これまでは普通にkey
にindex
使ってた...。
ただ、前に書いたコードを見たら並び替えはないから特に問題はなさそう。
ここで紹介されているkey
にindex
を使ったサンプルを見ると確かにヤバそう。
本当は頭に行を追加したいはずなのに、末尾に追加されてしまってる。
codeburst.io
ここにはどういうものをkey
にすればいいかの指針が載っている。
フォームでは制御コンポーネント(controlled components
)を使う
制御コンポーネントってのはstate
でinput
の値を管理するようなもので、そうしないものが非制御コンポーネント(uncontrolled components
)。
ref
を使う場合は非制御コンポーネントになる。
僕はreactを使う案件にアサインされたはじめのほうでref
使えって言われたから盲目的にそうしていたけど、
ja.reactjs.org この記事の中で
ほとんどの場合では、フォームの実装には制御されたコンポーネントを使用することをお勧めしています。
と書いているし、state
を使うほうが公式のやり方なのか。
ただ、state
でやると入力値が変わるたびにstate
の更新が走ってそれはそれでめんどくさくないかと思ってたらこういう記事を見つけた。 この記事の結論はとてもわかりやすい。
goshakkk.name 次フォームを扱う時はこのやり方に倣おう。
Reactで画面を作る過程
この記事は全体的に最高。
特にコンポーネントの切り出しにUIイメージを使うとかstate
の決定の仕方とかとても参考になった。
まとめ
前に少し読んでclassで書かれてるじゃん...って思ってスルーしていたが、ちゃんと読んでみたらすごいためになる内容だった。
次は ADVANCED GUIDES とか HOOKSを読んでいきたい。
react + redux-thunk + typescriptでよくあるカウンターのサンプル
redux-thunkをreactとtypescriptで使ってみたかったので、よくある簡単なカウンターを作ってみた。
ただ、typescriptに慣れてなかったりそもそもreduxに触ったばっかりって感じで、表示のガワ部分を作ったあとのactionやreducerあたりは全部presentationalなコンポーネントの中に書いている。
いきなりファイルを分けすぎるとかえって把握しづらかったため。
index.tsx
import React from "react" import ReactDOM from "react-dom" import "./index.css" import * as serviceWorker from "./serviceWorker" import { Provider } from "react-redux" import thunk from "redux-thunk" import { createStore, applyMiddleware } from "redux" import Buttons, { reducer } from "./Buttons" let store = createStore(reducer, applyMiddleware(thunk)) ReactDOM.render( <Provider store={store}> <Buttons /> </Provider>, document.getElementById("root") ) serviceWorker.unregister()
Buttons.tsx(よくあるApp.tsxと同じ)
import * as React from "react" import { connect } from "react-redux" import { Dispatch, Reducer, ActionCreator } from "redux" import AddButton from "./AddButton" import CounterPanel from "./CounterPanel" import DecrementButton from "./DecrementButton" import IncrementButton from "./IncrementButton" import AsyncIncrementButton from "./AsyncIncrementButton" import { ThunkAction, ThunkDispatch } from "redux-thunk" interface CounterState { count: number } enum CounterActionType { ADD = "COUNT/ADD", INCREMENT = "COUNT/INCREMENT", DECREMENT = "COUNT/DECREMENT" } interface CounterAction { type: CounterActionType amount?: number } interface ButtonsProps { count?: number add?: (amount: number) => void increment?: () => void decrement?: () => void asyncIncrement?: () => void } const Buttons: React.FC<ButtonsProps> = ({ count = 0, add = () => {}, increment = () => {}, decrement = () => {}, asyncIncrement = () => {} }) => { return ( <div> <CounterPanel count={count} /> <IncrementButton onClick={increment} /> <DecrementButton onClick={decrement} /> <AddButton onClick={() => add(10)} /> <AsyncIncrementButton onClick={asyncIncrement} /> </div> ) } // action const add = (amount: number): CounterAction => ({ amount, type: CounterActionType.ADD }) const increment = (): CounterAction => ({ type: CounterActionType.INCREMENT }) const decrement = (): CounterAction => ({ type: CounterActionType.DECREMENT }) const asyncIncrement: ActionCreator< ThunkAction<void, CounterActionType, null, CounterAction> > = () => { return (dispatch: Dispatch) => { setTimeout(() => { dispatch({ type: CounterActionType.INCREMENT }) }, 3000) } } // reducer export const reducer: Reducer<CounterState, CounterAction> = ( state: CounterState = { count: 0 }, action: CounterAction ): CounterState => { switch (action.type) { case CounterActionType.ADD: return { ...state, count: state.count + (action.amount || 0) } case CounterActionType.INCREMENT: return { ...state, count: state.count + 1 } case CounterActionType.DECREMENT: return { ...state, count: state.count - 1 } default: const _: never = action.type return state } } interface StateProps { count: number } interface DispatchProps { add: (amount: number) => void increment: () => void decrement: () => void } const mapStateToProps = (state: CounterState): StateProps => ({ count: state.count }) const mapDispatchToProps = ( dispatch: ThunkDispatch<any, any, CounterAction> ) => ({ add: (amount: number) => dispatch(add(amount)), increment: () => dispatch(increment()), decrement: () => dispatch(decrement()), asyncIncrement: () => dispatch(asyncIncrement()) }) export default connect( mapStateToProps, mapDispatchToProps )(Buttons)
typescriptの型指定はまだ全然慣れない。
前の会社ではC++とかJavaとか触ってたけど、今の会社でrubyを触り始めて型についてすべて忘れてしまった。
このサンプルを作ってる中で気になったんだけど、reduxってどの程度hooksで置き換えられるんだろう。
100%は無理にしても割と置き換えられるんじゃないかなあって気がしている。
最近は
この本が良かった。
萌え系って苦手だから表紙でちょっと避けてたけど、中見たら萌え要素全く無かったし、内容もかなりわかりやすく書かれていたしですごくよかった。
そろそろサンプル以上のものを作るようにしたい。
最近の学び
仕事で久々にrailsでview周りを触ったので、そのことについて書いておく。
selectでdata-id
ユーザーへの情報の一覧を表示する管理者向けの画面に、その情報がユーザーへ公開されているか否かを表示する項目があった。
これまでは別途情報ごとの編集画面に行きそのフラグを更新していたが、管理者側の要望で情報の一覧画面からそのフラグ更新をしたいと要望があったので、その改修を行った。
はじめは
<% @information.each do |info| %> 中略 <select name="published[#{info.id}]"> <%= options_for_select({ '公開': true, '非公開': false }) %> </select>
というふうに書いていたんだけど、レビュー時にname
属性にidを入れるよりもdata-*
属性にidを入れるほうが適切と言われた。
MDNの説明的にも確かにそうだなあと思い以下のように変更:
<select name="published" data-id="#{info.id}"> <%= options_for_select({ '公開': true, '非公開': false }) %> </select>
ajaxでの送信の際、name
にidを混ぜているとslice
などを使った文字列操作が入りめんどくさかったが data-*
属性を使うとidの取得が簡単になるので便利。
ただ、ひとつ注意すべきなのは、railsのselect
ヘルパー(formの内部で使うやつではないほう)ではdata-*
属性が使えなかった(下のようなやり方はできなかった)。
select
ヘルパーとform.select
ヘルパーで違うのか、railsのバージョンが新しいからなのかはよくわからない。
bubbling phaseでイベント取得
上のセレクトボックスの話に関してもうひとつ続ける。
上のような、行う処理は同じで渡すパラメータだけ違うインターフェースがたくさんある場合、イベントハンドラをどう書くかで困った。
onchange
属性を各インターフェースにつければいいって話なんだけど、erb
ファイルとは別のファイルに書いたイベントハンドラを読み込むと、イベントハンドラがうまく発火しない。
onchange
属性に書かれたものは、erb
ファイル内に書いたイベントハンドラであればうまく動くのだが、webpacker
を導入していた && npm
でインストールしたライブラリを使いたいこともあり、やはりerb
ファイルとは別のファイルにイベントハンドラを書きたかった。
はじめはイベントハンドラを書いたファイルの中で
const selectElms = document.getElementsByName('published') Array.from(selectElms).forEach((elm) => { elm.addEventListener('change', (e) => someEventHandler(e)) })
としていたのだが、forEach
が入るのがめんどくさい。
そこで、
const selectParent = document.getElementById('selectParent') selectParent.addEventListener('change', (e) => someEventHandler(e))
というように、bubbling phaseで処理するようにすることでforEach
で回す必要がなくなりコード的にすっきりさせることができた。
これまでは、イベントの伝播はすぐ止めるようにしてたので伝播することのありがたみってよくわからなかったが、今回はじめてそのありがたみが少しわかった気がする。