Reactのprops/contextの使い分け

Reactのprops/contextの使い分け

仕事先でたまたまこれの話になり、個人的に思っていることをまとめた。 公開したのは、時々見かける「どっちを使うべき?」みたいな議論に 自分も混ざりたかった 思うところがあったから.

「とにかくpropsでいい」と自分は考えている。

なによりReactは書き方に詰まった場合に、フレームワークライブラリ固有の事情を考慮して解決するというよりも、実装や設計上の問題が一般的なプログラミングパターンの範疇の発想で解決できるのがよい

前提

以下のように考える

  • React/preact のコンポーネント = 通常のclassや関数
    • 状態を隠蔽して抽象する
    • 最近は冪等性がどうとかReact語るときにあんまりいわなくなったけども....
  • props = 関数やメソッドの引数(入力)
  • context = グローバル変数(モジュールグローバルな変数)

実装の指針

1. まず引数(props)でどうにかできないか考える

  • グローバル変数に依存しないクラス・関数の方が、テストもメンテナンスしやすい
  • 大抵の場合はpropsでどうにかなる

2. propsでは「めんどうくさい」「うまくいかない」と感じた時にグローバル変数(context)を、やむを得ず使う

以下のようなケースで、グローバル変数を使った方が良いのか一回落ち着いて考えた上で、それでもやっぱりグローバル変数(context)を使った方が良いなと思ったら使う

  • スケジュールの都合、propsよりもcontextを用いた方が簡単である
  • ページ(アプリケーション)中で同じ値を、必要な箇所で参照している
    • テーマ機能の実現
    • ページの初期化時に取得した認証情報を触る
  • propsで渡すと、どうにもコードが簡潔に書けない

注意点

  1. Contextを利用する場合, <Context.Provider/>で囲った箇所(ツリーの子孫)は、Contextの値が更新されると全て再実行される。そのため、性能問題が発生しやすいので、値が頻繁に更新されるものは、上の基準に合致していても、なるべくpropsで渡すようにする
    • そもそも自分はなるべく使わないのでそんなに困らんのだけどね.....
  2. Contextを利用する場合であっても、なるべくuseContextの呼び出し回数を最小限にする

コードレビューの指針

以下を見る:

  1. そのcontextの影響範囲・利用用途は適切か
    • 影響範囲が広い場合、注意深く検討する
  2. 気になるなら実装者に「propsに直してみるのはどうだろう?」と聞いてみる
    • 実装者に任せてみる
  3. 究極的には「どんなに”ひどい”設計・実装でも、影響範囲が小さい(使用箇所が限定されている)ならば、大抵は問題ない」ということを留意する
    • 困ったら、その時にリファクタしたり、丸々書き直せば良い
    • あとで修正しやすくするために、テストケースの有無や妥当性を検討する方がよい

Q&A

Q. propsでなるべく渡そうとすると、どうしてもpropsで受け取る値が増えてしまうけど、どうすればいい?

<Hello bar1 bar2 bar3 bar4 bar5 bar6 bar7 bar8 bar9/>

のように、たくさんの値が必要になってしまう場合. 以下のようなアプローチが考えられる

  • 「ひとつのコンポーネント(関数)で多くのことをやろうとしすぎている」ことが多いので、困難を分割する
  • 関連する値をオブジェクトに一度まとめて渡す
    • ValueObjectを作る....というか一般的なモデリングの話というか
  • render propsprops.chidlrenといった手法を試す

背景

そもそも自分はContextを使わない。

  • ライブラリや他人の書いたコードがそれを要求してくる
  • 設計上とスケジュールの制約上、どうしてもContextを用いた方が早く作れる
    • かつ、値がほとんど更新されない

場合くらいしか使うことも使った記憶もなく、自分で設計を決められる場合は

  • 全体に影響がある値は、ルートにStoreないしStateを用意する
    • 余裕があればgetter setterを別のインターフェースとして定義する
  • そしてそれをひたすらにバケツリレーする

スタイルでゴリゴリやってしまう。

グローバル変数のたとえもあるし、「選択という手間を省き、その分の思考のリソースをより根本的な設計を検討するのに注ぎ込みたい」意味もある。 また、常にpropsを用いることで、パフォーマンスやリファクタの問題をシンプルにする、という意味合いもある。

あと、自分がReactやWebクライアントサイドアプリケーションに関して一番熱心に考えていた頃はLegacy Context APIしかなかった結果が、このスタイルに自分を規定した気はする。あの頃の「機能としては存在するけどなるべく使うな」の趣を思い出せば、自然と基本はprops一択になる。

対して。現在のContext APIはだいぶよくできている。 しかしながら、グローバル変数の例えもあるし自分からは積極的に使うほどでもない、みたいな距離感。

そんな感じなので、正直、「Contextで困ったらプロファイラ使って測って遅いところ削ったり、必要に応じて抽象化のリファクタすりゃいいじゃん。別にReact専用の体系的なあれこれ要らないよHAHAHA」程度の気持ちだったところ、ケーススタディガイドラインとしては以下のような切り口もあると嬉しいのではないかと同僚からフィードバックをもらった。

たとえば

  • 巨大なContextは作るな分割しろ
  • 直接 useContext()は使うな、カスタムフックの裏に隠して最適化の余地を残せ

みたいなのあるじゃないですか、と。

そう言われると、ReactのContextで困るとそういう感じのことやることになるよなあ、てか職場を問わず毎年1回〜2回はそういう変更のコミット作ってたわ。確かにね。

ただ、それらは自分にとっての体系的な何かというよりは、一般的なプログラミングのグッドプラクティスや技法の域を出ず、React固有の何かとして特筆する気になれないのであった(というわけで、ガイドラインのContextベストプラクティスの章は彼に任せることにした)。

IE11とその他ブラウザの最新版ってどのくらいベンチスコアに差があるのか(手抜き版)

IE11のリリースは2013年秋。それから7年間で競合がつっこんだ投資の結果、ベンチマークスコアに差はどのくらい生まれているのかを知りたかった

注意点

  • EdgeHTMLを実行できる古いWindows環境は手元にないので無視する
  • Microsoft Edge (Blink)はどうせBlinkでしょ….ということで無視する
    • Edge限定のパスとかMSが用意してないことに掛ける
  • もはや大体のベンチマークは動かないので、ノー修正で動くものだけトライする
    • 結果, JSとDOMだけの計測に
    • たぶんpolyfill入れたりbabel通せば動くとは思うが、とりあえず今回はスキップする
      • MotionMarkは動かしたかったが…..
    • そのうち真面目に色々動くようにした上で再計測にしたやつをやるかも

環境

  • Windows 10 20H2
  • Dell XPS 13 (2015)
    • CPU: Intel Core i7 5500U 2.4GHz(物理2コア4スレッド)
    • RAM: 8GB
    • GPU: Intel HD Graphics 5500
  • ハイエンドではないけど、まだまだローエンドってわけでもない、はず

スコア

JetStream 1.1

IE11 Firefox 85.0 Chrome 88.0.4324.146
Latency 74.477±18.065 75.946±22.499 47.194±22.116
Throughput 69.304±4.1786 144.56±15.191 191.58±125.61
Geometric Mean 71.482±9.9780 109.16±20.701 103.91±58.452

Speedometer 1.0

IE11 Firefox 85.0 Chrome 88.0.4324.146
Score 23.6±1.9 89.9±2.8 125.4±0.81

いろいろあった

I'll leave the current job at the end of December. The reason is a difference of a direction of my musicality from the company's one.

Of course, there were bunch of things. I can talk about their details but I seem it would not be nice case study. I can only say the difference of the direction. I think everything was caused by it.

Next year....... I'm planning to start a work as a freelance. I know well that this is not a best direction for me. However, I don't have any other choice. One thing I can say for sure is that I will not have a plan to work as a freelance for many years - ideally I'd like to finish and join to a nice company in several month~1 year.

Happy holidays. Have a happy new year.

アートとサイエンスのはざまで

例えばパフォーマンスエンジニアリングは定式化されたアクティビティをどれだけ回していくかのサイエンスだと思う。けれども、一般には秘伝の最適化手法を感性で駆使したアートのように思われていることが多いのはなぜなのか。「〜すると速い」みたいな真偽不明のマントラが多いからそのように見えるのか。

僕からすると、ソフトウェアの設計やリファクタリングの方がよっぽどアートだと思う。問題構造をどのように認識して、どのような形であるべきかを解いて、具体的なコードとして落とし込んで行くアクティビティであって、それは思想や哲学の叙述という行為に近しいからだ。パターン言語とかもあるけれども、ある思想体系において理論立てられた説明と用語を用いているという感覚。

styled-componentsへの最近の感想

今の職場で既に組まれたシステムが既にstyled-componentsにべったり依存していて、別に積極的に入れ替える理由もないので普通に使っているけれども、やっぱりこれ微妙だなと思った話。

そもそもとして、ビルドシステムへの介入が多くて不必要にロックインになったり、提示されてる手法がだいたいイマイチで普通にCSSとかSCSSを書く以上の意義が見出せないので、僕は基本的に「JS中にCSSを書いたり、ES Moduleのimportを使ってCSSを読み込むタイプのアプローチ(以下CSS-in-JS)」がそんなに好きではない・大人しくCSS書く方が好きではあるんだけど。色々あった理由はこちら:

感想

上のリンク先にも色々書いてる微妙に感じる点を全部見なかった事にしても、やっぱり微妙だと思う。

  1. 何を作るにもstyled-componentsで包まれた(ファクトリ)コンポーネントを作る事になるので、ラムダ(匿名関数)を書いて済ませたい箇所で、古のJavaのCallbackクラスを書くのを強制される気持ちになる
    1. 具体的には調整用のdivやspanをぺろっと置きたいだけなのに、わざわざ書く事になる
  2. 結果、包まれた(ファクトリ)コンポーネントは変数定義順の問題があり、function宣言のhoistingなどに頼ることができないので、 BEMのelement定義してからBlock書く感じになる。なので、抽象化されたコンポーネントを定義したいのに、まず具象化された細部から書いていく必要があるし、既存のコードもそのように書かれる
    1. ここら辺、人によって書き方変わるし、lintなどのしばり方次第で如何様にも書けるだろうけど、temporal dead zoneを考えると宣言してない変数を使うのはありえないのは共通認識とする
    2. 末節・BEMでいうelement側の定義をファイルを分けてimportすれば解決する問題だが、繰り返すようにラムダ(匿名関数)を書いて済ませたい箇所でCallbackクラスを定義するような事になる
  3. そもそもテンプレートリテラルで文字列として記述しているのが、非常に中途半端に見える
    1. JSX記法がDSLとしてセマンティクスを持つ事による教訓が活かされていないのは釈然としない

イマイチだと思ったのはここら辺。他にも色々あるけれども。

かつてのCoffeeScriptを持て囃す論説への拒否感と似ているものの、そんなことをいうとCoffeeScriptに失礼だな。

それとstyled-componentsに限った話題ではないけれども、いわゆる「GUIのTheme実現が可能」みたいな売り文句は実際には十中八九使わないタイプの機能だと思っていて、なぜかというと

  • 現実にはダークモード+alphaくらいしかテーマを作らない
    • そもそもテーマ機能を真面目に用意してるプロダクトを見た数の方が少ない
  • 仮に実装するとしても、起こり得るテーマの変更はCSS custom propertiesで使って色やサイズやbackground-imageなどの変数定義で解決するのが大半なので、標準化された方法に依存した方が良い
  • もっと複雑なことをやりたい場合は、大抵はGUIの実装部分が別になるので「一つのファイル・コードで複数のテーマを実現可能」にお世話になることは(あんまり)ない
    • ユーザー定義スタイルを許容する場合、尚更public interfaceとしてclass属性などは必須だし.

から。 ちなみに自分の好きなアプローチは上の方に貼ったリンク先に全部書いてあって、それを超えるものではなかったかな。

Facebookのリデザインに見る、CSS-in-JSの良さそうな方向

色々ぐだぐだ書いているけれども、 https://engineering.fb.com/web/facebook-redesign/ に載っているサンプルコードの方向性は、良さそうに見える。実際にこの通りに書かれてるかはわからないけどね。

なぜかというと、

  1. CSSで書きたい構造をJSのオブジェクトリテラルとして透過性を持って表現しているから
    1. 生成されるCSSの構造が非常に透過的
    2. いくらなんでも文字列リテラルCSS書くとかないわーと思っていたら、オブジェクトリテラルを上手く使って表現してるのは流石
  2. Reactに対しても透過的なアプローチである(下手に依存させていない)から
    1. emotionみたいにpragmaを使うのは、Reactとの間に抽象が挟まるので僕は好きではない

という具合に見えるから。Facebookオープンソースにしてくれるといいですね。

BlinkとWebKitの違い(大雑把)

「〜がChromiumベースに!」なことが起こるたびに「Chromium/BlinkはWebKitを源流とするエンジンでしかじか」みたいな話が出てきて、「実質WebKitだから同じだね」という反応が出てくるのが恒例行事っぽくなってるけど、結構モニョモニョする。

先祖が同じなら子孫も同じ、ってそんな単純な話じゃない。

fork前、BlinkがChromium WebKitというかWebKit Chromium portと呼ばれていた頃でさえ、Chromium portとApple portの2つが同じエンジンと呼べる箇所って、layoutとかdomとかstyleとかブラウザエンジンのコア部分だけで、他はV8とJSCとか、SkiaとCore Graphicsとか、そもそもプロセス分けてる方法も違うし、呉越同舟というか寄り合い所帯感だった。composition周りだってApple portはCore Animationにべっとり依存するような実装じゃなかったけ。

それで、数少ない共有部なコアでさえ、fork後に色々変わっている。自分の知っている・思い出せる範囲で大きいやつだと

  • layout
    • ChromiumはlayoutをLayoutNGって書き直しをやった
    • WebKitもLFCってコードネームで書き直し中
  • style
    • Chromiumは大きな書き直しは(僕の聞いた限りだと)ない、はず
    • WebKitCSS JITを投入した。その時にJITの有無に関わらずに色々変更が入った
  • DOMのオブジェクト管理
    • ChromiumはOilpan GCとかやってるし
    • WebKitはconstraintsとかいう魔術的な仕組みが投入された模様

とかとか。細かいあれこれを挙げだすとキリがない。

GeckoやEdgeHTMLと比較した場合、WebKitとBlinkは近縁種だと思うけど相対的なものでしかなくて、それぞれが呉越同舟していた時代に追加された仕様未定義な挙動に箇所に関するテストケースや、同祖であることに由来する箇所くらいしか、同じエンジンとは言えない。ましてや最近実装されたものは以下略。

リポジトリを境界としたコードベースとしては、WebKitは変わらずライブラリ指向だけど、Chromium/Blinkはプラットフォーム志向で、リポジトリの成長の方向性としてはFirefox/Geckoの方が近くなってきている

でも、そう変わるのも当然で、当代で世界トップクラスにお金を持っている二社が別々の方針で(数十人とか数百人のオーダーで)エンジニアを7年も張り付けていて、しかもそれぞれプロジェクトが実現したいWebに対するスタンスは結構違っているのに色々変わらない訳が無い。

でも、一方でWebGLの実装とかはWebKit(のApple port)もANGLE使おうとしてて、このままだとGecko/Blink/WebKitのどれもANGLEになろうとしているんだから、面白い。