車輪の二度漬け

railsを触っていましたが、最近はreactに興味あります。

React Routerの公式ドキュメントを読んでみた

reacttraining.com

まだそんなにガチで使ったことなかったからざっくり流し読みした程度。

reacttraining.com まずはこのPhilosophyページを読むとわかりやすいと思った。

以下雑感。

Redirect

reacttraining.com こう言う風にコンポーネントを書くようにしてRedirectするのは、railsredirect_toとかに慣れてるとちょっと面食らうなと思った。

routhingのネスト

reacttraining.com

railsresourcesみたいな感じのこともできるんだと感動。
これうまく使えれば、railsみたいにコントローラーファイルを複数に分けることもできるよなあ。
すごく便利そう。

バイス別のレイアウトの分け方

reacttraining.com これもすごいなと思った。
ルーティングでここまでできるんだと感動。
media queryのところは GitHub - d6u/react-container-query: Modular responsive component この辺とかつかってやればよさそうだし。

最近やたら薄い記事しか書いてないな...
仕事で色々知見が貯まってきてはいるので、どこかのタイミングで書いておきたい。

Reduxの公式ドキュメントを読んでみた

redux.js.org

読んでみたはいいけど、全然頭に入ってこねー。
正直、仕事で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 内で使う関数は、もしその関数がstatepropsを使うならuseEffectの中に書いておけって話か。
何でもかんでも内部に書くと見通しが悪くなってあまり好きじゃないんだけど、そっちのほうが安全そう。 単純に計算結果を返すだけの関数なら外部に書いても大丈夫みたいだけどね。

コンポーネントのメモ化

ja.reactjs.org

これは少し感動した。
いつか使ってみたい。

計算量の大きいオブジェクトの遅延作成

ja.reactjs.org

まだそんなに計算量の大きいオブジェクトを作ったことはないけど、これもよさそうなテクニック。
setStateの初期値には関数も渡せるのね。

Reactの公式ドキュメント読んでみた その2ADVANCED GUIDES編

前回はReactの基本コンセプトを読んだので、今回は次のADVANCE GUIDESを読んでいこうと思う。
ただ、今回は前回ほどの量はなさそう。
理由としては、これどのタイミングで使うんだ...?ってものがあったり関数コンポーネントを使うようになってる現在ではあまり役に立たなそうなものがあったりしたから。

コード分割(code-splitting)

ja.reactjs.org

これは便利そう。要はlazy loadしたいのね。
業務でSPAを作ったことはまだないが、実際に作ることになったらよさそうだなあと思う。

Contextの使用

ja.reactjs.org

Contextってグローバルに値を読めるようになって怖いからなるべく使わないようにしてたんだけど、実際どのくらい使われるのか謎。

ja.reactjs.org ここの冒頭にも書いてあるけど、必要な値を直接埋め込んでしまうから再利用性低くなるのはそうだと思う。
Context使うくらいならRedux使ったりしたほうがいいんじゃないだろうか。

blog.logrocket.com ここを読むと、Reduxとかを使うのはやりすぎな時に使う感じっぽい。

Error Boundary

ja.reactjs.org

ただ、Error Boundaryについては、ComponentDidCatchっていうライフサイクルメソッドを使わないといけないらしいんだけど、関数コンポーネントを使うのが主流となった今はこれは使えないよなあと思った。
blog.bitsrc.io

stackoverflow.com

で、色々ググったところ、現時点ではError Boundaryを使うなら結局はクラスコンポーネントを使わないといけないっぽい?
この辺はまたおいおい調べよう。

今回はだいたいこんな感じ。
次はHooksについてのドキュメントを読もう。

Reactの公式ドキュメント読んでみた その1 MAIN CONCEPT編

最近はReactのドキュメントに目を通している。 以前にいきなり納期がタイトなReactの案件を振られて、四苦八苦しながらコード書いて以降、Reactについてわかった気になっていたが、改めてドキュメントを読むと色々と学びがあったのでメモしておく。

ある条件下でレンダリングさせない

ja.reactjs.org

これは知らなかった。
returnnullを返せばそのコンポーネントレンダリングを阻止できるのね。
見せたくないだけなら、条件分岐の元になるフラグでdisplay: noneをスタイルに追加すればいいかなと思ってたけど、どっちがいいんだろう。

stackoverflow.com

stackoverflowにこんな質問があったけど、CSSでやれば早いし、return null でやればDOMは小さくなるよ、って感じなのかな。
そもそもそんなに気にしなくてもいいのかも。

map で要素を繰り返し表示するときに keyindex を使わない

ja.reactjs.org

ja.reactjs.org

これまでは普通にkeyindex使ってた...。
ただ、前に書いたコードを見たら並び替えはないから特に問題はなさそう。

medium.com

ここで紹介されているkeyindexを使ったサンプルを見ると確かにヤバそう。
本当は頭に行を追加したいはずなのに、末尾に追加されてしまってる。 f:id:wheeltwice:20190731184111g:plain

codeburst.io ここにはどういうものをkeyにすればいいかの指針が載っている。

フォームでは制御コンポーネント(controlled components)を使う

制御コンポーネントってのはstateinputの値を管理するようなもので、そうしないものが非制御コンポーネント(uncontrolled components)。
refを使う場合は非制御コンポーネントになる。
僕はreactを使う案件にアサインされたはじめのほうでref使えって言われたから盲目的にそうしていたけど、

ja.reactjs.org この記事の中で

ほとんどの場合では、フォームの実装には制御されたコンポーネントを使用することをお勧めしています。

と書いているし、stateを使うほうが公式のやり方なのか。
ただ、stateでやると入力値が変わるたびにstateの更新が走ってそれはそれでめんどくさくないかと思ってたらこういう記事を見つけた。 この記事の結論はとてもわかりやすい。

goshakkk.name 次フォームを扱う時はこのやり方に倣おう。

Reactで画面を作る過程

ja.reactjs.org

この記事は全体的に最高。
特にコンポーネントの切り出しに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%は無理にしても割と置き換えられるんじゃないかなあって気がしている。

最近は

booth.pm


この本が良かった。
萌え系って苦手だから表紙でちょっと避けてたけど、中見たら萌え要素全く無かったし、内容もかなりわかりやすく書かれていたしですごくよかった。
そろそろサンプル以上のものを作るようにしたい。

最近の学び

仕事で久々にrailsでview周りを触ったので、そのことについて書いておく。

selectでdata-id

ユーザーへの情報の一覧を表示する管理者向けの画面に、その情報がユーザーへ公開されているか否かを表示する項目があった。
これまでは別途情報ごとの編集画面に行きそのフラグを更新していたが、管理者側の要望で情報の一覧画面からそのフラグ更新をしたいと要望があったので、その改修を行った。

f:id:wheeltwice:20190520140719p:plain
現行

f:id:wheeltwice:20190520141146p:plain
改修後:セレクトボックスを変更したらajaxでフラグを更新

はじめは

<% @information.each do |info| %>

中略

 <select name="published[#{info.id}]">
  <%= options_for_select({ '公開': true, '非公開': false }) %>
</select>

というふうに書いていたんだけど、レビュー時にname属性にidを入れるよりもdata-*属性にidを入れるほうが適切と言われた。

developer.mozilla.org

MDNの説明的にも確かにそうだなあと思い以下のように変更:

 <select name="published" data-id="#{info.id}">
  <%= options_for_select({ '公開': true, '非公開': false }) %>
</select>

ajaxでの送信の際、nameにidを混ぜているとsliceなどを使った文字列操作が入りめんどくさかったが data-*属性を使うとidの取得が簡単になるので便利。

ただ、ひとつ注意すべきなのは、railsselectヘルパー(formの内部で使うやつではないほう)ではdata-*属性が使えなかった(下のようなやり方はできなかった)。
selectヘルパーとform.selectヘルパーで違うのか、railsのバージョンが新しいからなのかはよくわからない。

stackoverflow.com

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で回す必要がなくなりコード的にすっきりさせることができた。

これまでは、イベントの伝播はすぐ止めるようにしてたので伝播することのありがたみってよくわからなかったが、今回はじめてそのありがたみが少しわかった気がする。