mongolyyのブログ

開発(Javascript, Typescript, React, Next.js)や開発手法(スクラム, アジャイル)、勉強したことについて色々書ければと。

「Building React From Scratch」動画の視聴メモ(後編)

はじめに

mongolyy.hatenablog.com

に引き続き、


www.youtube.com

を見ていこうと思います。

動画視聴メモ

Reconciler

https://github.com/zpao/building-react-from-scratch/blob/master/dilithium/src/Reconciler.js

function mountComponent(component) {
  // This will generate the DOM node that will go into the DOM. We defer to the
  // component instance since it will contain the renderer specific implementation
  // of what that means. This allows the Reconciler to be reused across DOM & Native.
  let markup = component.mountComponent();

  // React does more work here to ensure that refs work. We don't need to.
  return markup;
}

まずは、mountComponent。これはレンダラーによらず(ReactDOM & React Native)、DOMノードの生成に使われる。

function receiveComponent(component, element) {
  // Shortcut! We won't do anythign if the next element is the same as the
  // current one. This is unlikely in normal JSX usage, but it an optimization
  // that can be unlocked with Babel's inline-element transform.
  let prevElement = component._currentElement;
  if (prevElement === element) {
    return;
  }

  // Defer to the instance.
  component.receiveComponent(element);
}

次に、receiveComponent。これは現在のelementから何も変わっていなければ、何もしないショートカットを備えている。

Internal Component Lifecycle

  1. constructor
  2. mountComponent
  3. receiveComponent
  4. updateComponent
  5. unmountComponent

Component API

https://github.com/zpao/building-react-from-scratch/blob/master/dilithium/src/Component.js

主な関数は以下のような構成

  • constructor
    • propsをセットして、必要なプロパティの準備、render関数があるチェックをする。
    • 実際にこの関数が直接呼ばれることはなく、基底クラスとして#superで呼び出される。
  • setState
    • キューに格納し、バッチ処理でstateをセットしている
    • partial stateは非同期で処理される(サンプルコードでは同期処理のように書かれているが)
  • mountComponent
    • componentWillMountを呼び出す。
    • Reconciler#mountComponentを通して、実際に描画するmarkupを作成する
  • receiveComponent
    • updateCompnentするためのグルーコード
  • updateComponent
    • DOMノードを書き換える
  • unmountComponent

おわりに

Component APIはReconcilerのAPIを使用して、マークアップを生成し、それをReact-domを使用して、実際のDOMに反映している感じっぽい。
で、ReconcilerはReact DOMとReact Nativeで共通化されたコードが有る。
ということがわかりました。

ReconcilerはReactの中でも重要な役割を担っていると改めて感じました。

「Building React From Scratch」動画の視聴メモ(前編)

はじめに

最近「Reactハンズオンラーニング」という書籍を読んでいるのですが、

Reactの基本として、jsxを使用しないプリミティブな書き方をしていて、Reactが担っている役割に興味が出てきたので、以下の「Building React From Scratch」という動画を読みて理解を深めることにしました。
全編英語だったので、整理のためにメモを残したいと思います。


www.youtube.com

Githubリポジトリ

github.com

動画視聴メモ

クラッチで作るライブラリの名前はDilithium(https://youtu.be/_MAD4Oly9yg?t=212)。スタートレック用語ぽい (ダイリチウム - Wikipedia)。

Top LevelのAPIは以下の3つ(https://youtu.be/_MAD4Oly9yg?t=231

  • createElement
  • Component
  • render

ComponentクラスのAPIは以下の5つ(https://youtu.be/_MAD4Oly9yg?t=256

  • constructor()
  • render()
  • setState()
  • this.props
  • this.state

実際にうまく動いているかテストするコンポーネントとして、値が変わり、それと連動して色が変化するボタン CounterButton なるものを作った(https://youtu.be/_MAD4Oly9yg?t=320

以上で準備は完了。
実装をやっていく。

まずはcreateElement。(https://youtu.be/_MAD4Oly9yg?t=390
Github上では https://github.com/zpao/building-react-from-scratch/blob/a61c831266c7521869ba0293b06c3e2c238a0230/dilithium/src/Element.js

function createElement(type, config, children) {
  // Clone the passed in config (props). In React we move some special
  // props off of this object (keys, refs).
  let props = Object.assign({}, config);

  // Build props.children. We'll make it an array if we have more than 1.
  let childCount = arguments.length - 2;
  if (childCount === 1) {
    props.children = children;
  } else if (childCount > 1) {
    props.children = Array.prototype.slice.call(arguments, 2);
  }

  // React Features not supported:
  // - keys
  // - refs
  // - defaultProps (usually set here)

  return {
    type,
    props,
  };
}

まずは、configを取得してpropsとして、ディープコピー
'Array.prototype.slice.call(arguments, 2);' は初めて見たが、第3引数以降の引数を配列にする書き方と理解
propsのchildrenに子コンポーネントをセットして、returnしている

次は、render(https://youtu.be/_MAD4Oly9yg?t=449Githubでは https://github.com/zpao/building-react-from-scratch/blob/a61c831266c7521869ba0293b06c3e2c238a0230/dilithium/src/Mount.js

function render(element, node) {
  assert(Element.isValidElement(element));

  // First check if we've already rendered into this node.
  // If so, we'll be doing an update.
  // Otherwise we'll assume this is an initial render.
  if (isRoot(node)) {
    update(element, node);
  } else {
    mount(element, node);
  }
}

説明でも言っているが、めっちゃシンプル。
すでにレンダーされていたら更新するし、レンダーされていなければ、マウント(初期化、インスタンス化、レンダー)する。

次はマウント(https://youtu.be/_MAD4Oly9yg?t=509)。

function mount(element, node) {
  // Mark this node as a root.
  node.dataset[ROOT_KEY] = rootID;

  // Create the internal instance. We're assuming for now that we only have
  // `Component`s being rendered at the root.
  let component = instantiateComponent(element);

  instancesByRootID[rootID] = component;

  // This will return a DOM node. React does more work here to determine if we're remounting
  // server-rendered content.
  let renderedNode = Reconciler.mountComponent(component, node);

  // Empty out `node` so we can put it under our control.
  DOM.empty(node);

  DOM.appendChild(node, renderedNode);

  // Incrememnt rootID so we can track appropriately.
  rootID++;
}

コンポーネントの初期化をして、そのコンポーネントを、rootIDをキーとして、instancesByRootIDというオブジェクトに格納している。
instancesByRootIDはHashMapのイメージで使われている感じかな?
その後、ReconcilerのmountComponentでDOMノードを取得しているよう。

説明ではReconcilerはReactの中で複雑な部分を抽象化してくれていると言っている。
詳細な説明はこの後あるらしい。
あと、Reconcilerのmountは再帰的であることが重要らしい。このコードだけ見てもよくわからん。

次は、update(https://youtu.be/_MAD4Oly9yg?t=597)。

function update(element, node) {
  // Ensure we have a valid root node
  assert(node && isRoot(node));

  // Find the internal instance and update it
  let id = node.dataset[ROOT_KEY];

  let instance = instancesByRootID[id];

  if (shouldUpdateComponent(instance, element)) {
    Reconciler.receiveComponent(
      instance,
      element
    );
  }
  } else {
    // Unmount and then mount the new one
    unmountComponentAtNode(node);
    mount(element, node);
  }

  // TODO: update
}

githubではTODOとなっているところがあったが、動画中ではそうなっていなかったので適宜修正した)

updateは shouldUpdateComponent 関数で、updateであったり、更新ができないコンポーネントであれば再マウントしたりしている。

#receiveComponent はcodemodのようなものをしているとのこと。
codemodは初耳だったが facebookが過去に作ったレファクタリングのツールっぽい github.com

本来は全てのコンポーネントに対して、updateがかかっていくが、これは高負荷なので、ショートカットの仕組みがあるとのこと

ショートカットの仕組みの一つとして shouldUpdateComponent が存在する(https://youtu.be/_MAD4Oly9yg?t=677

function shouldUpdateComponent(prevElement, nextElement) {
  let prevType = typeof prevElement;
  let nextType = typeof nextElement;

  // Quickly allow strings.
  if (prevType === 'string') {
    return nextType === 'string';
  }

  // Otherwise look at element.type. In React we would also look at the key.
  return prevElement.type === nextElement.type;
}

これはelementのtypeをチェックして、同じであればtrue、異なっていればfalseを返す。
elementのタイプが異なるとき、そのコンポーネントのサブツリーは大きく変更されることが多く、その場合に書き換えるのではなく、破棄して再マウントするほうがコストは低いということのよう。

おわり

とりあえず長くなりそうなので、分割しようと思います。

とりあえず、renderですでに描画されているかチェックして、描画されているなら、updateをかけ、描画されていないなら、mountする。

レンダー周りで複雑なことはReconcilerがやっており、 DOMノードを生成したり、更新したりする処理はReconcilerでやっているとのこともわかりました。

また、更新処理において、パフォーマンスが悪くならないような工夫もされているとのこともわかりました。

次回は Reconciler からです。(https://youtu.be/_MAD4Oly9yg?t=759

「プロダクトマネジメント」読んだ ~人にオススメしたい良書でした~

はじめに

メーカーのコーポレート部門でソフトウェアエンジニア兼スクラムマスターとして働いているモンゴルです。
周囲で話題に出ている「プロダクトマネジメント」読んだので感想を書いていこうと思います。

一言でいうと、「スクラムをやっている/始めようとしている人にオススメしたくなる良書」でした。

個人的には"スクラム始めるときに読んどけ本"として「SCRUM BOOT CAMP THE BOOK」があるんですが、その本の後に、本書もぜひ読んでほしいなと思っています。

感想

いくつか印象に残った章について感想を書いていこうと思います

プロダクトマネージャーのキャリアパス

本書では、戦術、運営、戦略の3つの割合でいくつかのキャリアが存在することが記載されていました。
今までプロダクトマネージャーのキャリアは考えたことがなかったので、プロダクトマネージャーやPOの相談に乗る場合は、このようなことも意識したいなと思いました。

戦略

NetflixのRokuの例が非常に参考になる例でした。
目先にとらわれて戦略を見失ってしまうことがありますが、そういった場合に、戦略、ビジョンに立ち返って考え直すというのはなかなかできないですが、インセプションデッキなどを作ることで考え直しやすくする土壌を作っておきたいなと感じました。

良い戦略フレームワークを作る

プロダクトのカタは使っていきたい考え方でしたし、他の章でも出てきますが、判断の材料としてデータを収集するということが大事だとも感じました。

問題の探索とソリューションの探索

オンライン講座サービスの例が非常に興味深かったです。
事例は問題を捉え違えたり、アイデア先行でユーザーの課題を解決できない、というようなもので、あるある、、とうなずきながら読んでいました。
個人的に、そこに対する対応方法について知見が薄かったので非常に参考になりました。
自分が所属するチームに共有したいなと感じました。

MVPという言葉も、ビルドトラップを招きやすいというのは初耳でしたが、そのような場を体験、見たりするな、、と、感じたので、今後はMVPでは「何を学習するのか?」ということをちゃんと意識してプロダクトづくりしたいなと思いました。

まとめ

非常に勉強になりました。
会社の経費で購入したのですが、自費でも購入して手元に常においておこうと思います。

私がNext.jsでSSRを選択した理由~WEB+DB PRESS Vol.123を読んで~

背景

仕事でNext.jsを使っているのですが、ちょっと前に気になる本を見つけて読んで色々感じたことがあったので書いていこうと思います。

本に書いてあったCSRSSRについて

自分もどちらにするかについて、迷いがあったので非常にためになりました。
この本を読む前に色々検討して、SSR一本でアプリを作成していましたが、本書を読んだことで色々整理されたので自分の考えもいれつつ、書いていきたいと思います。

CSRのメリット

  • 非同期で描画されるのでユーザー体験が良くなる

SSRのメリット

  • クライアント側のCPU負荷が少ない
  • クライアント側の実行環境への依存が少なくなる。JSが動かない環境だとしても多少は動く
  • 最初に表示するページのみ、CSRと比べると表示が早い
  • (私の経験ですが)CSRよりもAPIが少なくできる傾向にあり、外部で脆弱性検査を受ける場合は費用が抑えられる
  • (私の経験ですが)1ファイルにデータ取得部分と表示部分を書くことになるので、CSRのコードと比べてコードの見通しがいいように感じる。ただし、CSRもGraphQLやOpenAPI等、型付きでレスポンスが帰ってくる場合は、あまり変わらないかもしれない。CSRの場合は、読み込み中の処理を書かないといけないのでその分コードは長くなる。ここらへんもコードが読みにくいと感じる一因かもしれない

私の考え

SSRCSRどちらかに寄せたほうがいいと思っています。
なぜなら、CSRSSRのコードが混在した場合、サーバーサイドJSとクライアントサイドJSの挙動の違い、サーバーサイドJSでは環境変数を使用できて、クライアントサイドで環境変数が読み込めない問題(※)などで、同じ処理なのに使い回せない、見分けるために命名を変えるといった対策が必要であるからです。

NEXT_PUBLIC_の使用やnext.config.jsに書くことで、クライアントサイドでも読み込ませることは可能 nextjs.org nextjs.org

また、どちらかに寄せたほうがいい理由は他にもあります。

などでも言及されていますが、パフォーマンスと整ったアーキテクチャトレードオフの関係にあり、パフォーマンス最適化図られたコードを整ったアーキテクチャにするよりも整ったアーキテクチャについてパフォーマンス最適化図ることのほうが簡単と言われています。
そういう意味でもどちらかで一本化し、パフォーマンス最適化はその後で取り組むというアプローチがいいと思っています。

どちらかに寄せるとなった場合、SSRが必要な要件があるならSSR、無いならCSRでいいと思っています。
(無いなら、Reactだけでもいいのでは?とも思いますが、、)

終わりに

Next.jsはReactを元々使っていた方が多くいらっしゃるので、「CSRをどんどん使っていきたい!」という感じなのかなと思ったりもしますが、SSR一本でやってみるというのもありかなと、本記事を書いてみました。

コメントいただけると嬉しいです。

next.js + typescript + jestの組み合わせで、ts-jestでうまくいかないときはbabel+型検査をすれば良いんじゃないかと思った話

はじめに

とあるnext.jsのアプリについて、もともとbabelでtypescriptをトランスパイルして、Jestでテストするとやっていました。

ただ、Jestの公式にも記載がありますが、

jestjs.io

babelを使ったやり方だとコードについて型検査がされずに、結果としてテストコード中のオブジェクトの型が誤っていても、テストが通ってしまうという事象が発生していました。

これをなんとかすべく、ts-jestを使用しようとしたのですが、なかなかうまくいかず、結局、型検査をするnpmスクリプトを書いて解決させました。

その時の備忘録です。
諸事情によりコードが見せられないのですが、「next.js + typescript + jestの組み合わせで、ts-jestを使用しようとしている方」の役に立てれば幸いです。

何故ややこしくなっているのか?

コードをしっかり見たわけではないですが、エラーメッセージを確認する限り、testEnvironmentのデフォルトが node になっており、このままだと .tsx のテストがこけて、逆に jsdom にすると .ts のテストで instanceof を使用しているコードを中心にテストがコケるようになってしまいました(型の解釈がbabelを使用した時やと若干異なる模様)。

テストコードによって、testEnvironmentの制御もできないようであり、結局ts-jestの使用は断念せざるを得ませんでした。

どうしたか?

Next.jsの中でbabelが使用されている こともあり、ts-jestよりも開発の効率が少し悪くなったとしても、実行環境との差異を気にしてbabelを使い続けて、 Jestのドキュメントでも書いてあった、tscコマンドで型検査を別途やる という方法を取ることにしました。

具体的にはpacakage.jsonのscriptsに

"type-check": "tsc --pretty --noEmit",

を追加しました。
https://github.com/vercel/next.js/blob/canary/examples/with-typescript-eslint-jest/package.json を参考にしました。

また、github actionsでtype-checkコマンドが実行されるようなワークフローを作成し、push時に型検査がされるようにしました。

終わりに

先日出たNext.jsの公式ドキュメント

nextjs.org

や、next.jsの公式のexample

github.com

も確認してみると、babelを使用しつつ、jestでテストしてました。

ここからは完全に私見ですが、
typescriptを使用しているプロジェクトでjestでテストをするとき、ts-jest がシンプルで一般的な方法かもしれませんが、Next.js環境下においてはサーバーサイドのコードもフロントエンドのコードも混在するので、技術的に難しいところがあり、おすすめされていないのかもしれません。 また、Next.jsでbabelをもともと使用しているわけだし、環境の差異が発生しないようにbabelを使うのはいい線であるようにも感じました。

ADR(Architecture Decision Records)について考えてみた

はじめに

こんにちはメーカーでソフトウェアエンジニアとして働いているモンゴルです。
前々から、外部のコーチから「ADR(Architecture Decision Records)いいでー」と教えてもらっていたのですが、忙しさを理由に、なかなか作れていませんでした。

しかし、来月からメンバーが加わるということで、これは「ADRを作るチャンス!」と強く感じましたので、今のうちに調べて、自分だったらこう作るなーというところを妄想しておきたいと思います。

参考にしたサイト

fintan.jp

cognitect.com

github.com

考えた内容

ADRに書くスコープ

  • 開発者 のみ に関することで共有すること
    • 開発者以外にも関わることは、他メンバーもわかるドキュメント共有ツールに書く
  • 複数箇所で出現すること
  • プロジェクト固有のこと

ディレクトリ構成

ADRを作成する際は、以下のディレクトリの関連するディレクトリ配下に配置する

- docs/
   └ adr/
      ├ 1_process/          # 開発プロセスについて
      ├ 2_architecture/     # アーキテクチャについて
      ├ 3_code/             # コードについて
      └ 4_ui/               # 画面、挙動について

メンバーそれぞれからADRが出てくることが予想されるので、出しやすくするために、ジャンルを分けてみました。

テンプレート

ファイル名はXXX.mdにします。
XXXの先頭はフォルダ名の1桁目になります。

# {題目}

(話し合いをしていれば) 
- 日付
  - 2021/08/24
- 参加者
  - A
  - B

## ステータス

## コンテキスト

## 決定事項

## 結果

それぞれの内容について

  • 題目
    • 内容を簡潔に
  • ステータス
    • もやみ提起/提案中/承認/見送り/廃止
      • もやみ提起は素案なし
      • 提案中は素案あり
      • 承認は必須と推奨という、強制力が異なるものに分けることも検討したが、やめる
      • もやみ提起/見送りというのは、私のプロジェクトでADRを出しやすくするためにステータスを足してみる
  • コンテキスト
    • この決定を必要とする理由や、どういった選択肢があるか書く
  • 決定事項
    • 実現方法
    • 採用する技術・設計思想
    • 決定に至った理由、至らなかった理由
  • 結果
    • ADRを適用した後の結果を記述

ADRが決まるプロセス

パターン①:問題提起型

  1. 誰かが気になったことを書く
  2. 後日、議論しながら更新
  3. 皆でレビュー

パターン②:提案型

  1. 誰かがこうした方がいいのではということを書く
  2. 後日、議論しながら更新
  3. 皆でレビュー

パターン②:事後登録型

  1. 気になったことについて議論が起きる
  2. 議論中、議論後に誰かが内容を記載して登録
  3. 皆でレビュー

おわりに

まだADR導入していないですが、自分の中で整理できたと思います。 早くチームに導入してみたいなと思いました。

Google Cloud Certified Professional Cloud Architect を最短で受かるためにやったこと

はじめに

こんばんは。メーカー企業のコーポレート部門でソフトウェアエンジニアとして働いているモンゴルです。

先日、「Google Cloud Certified Professional Cloud Architect」を受験し、無事合格できたので、その体験記を共有したいと思います。

f:id:mongolyy:20210818224604p:plain
Google Cloud Certified Professional Cloud Architect

スペック

GCPの実務経験はなし。
趣味で少し触ったことがある程度。

  • AWSのProfessional資格は2冠を達成
  • GCPのProfessional資格はData Engieerを保有。3冠を目指し、この試験が2つ目。

勉強期間と時間

2週間
合計 約16時間

勉強方法

GCPはData Engineerの資格を持っていた

qiita.com

ので、入門的知識の習得は省いて、以下の流れで勉強しました。

  1. Couseraで勉強 (約6時間)
  2. Udemyで勉強しやすくするためにChrome拡張機能の作成 (約2時間)
  3. Udemyで模試を1周 (約8時間)

それではそれぞれについて説明していきたいと思います。

Couseraで勉強

Data Engineerと同じくCouseraの講座を受講しました。

具体的には Preparing for Google Cloud Certification: Cloud Architect Professional Certificate 日本語版 Professional Certificate | Coursera

www.coursera.org

を受講しました。

無料期間が一週間あったので、頑張って一週間で講座をやりきりました。

動画 + 確認問題という形式で、疲れている日でも勉強が進められて良かったです。
また、GCPのすべてのサービスが体系的に確認できたのも良かったと思います。

Udemyで勉強しやすくするためにChrome拡張機能の作成

先日、ネスペを受験した際に午前対策として、 ネットワークスペシャリストドットコム というサイトを利用して一問一答形式で勉強したのですが、これがすごく自分にあっていて、短時間の勉強で、高得点(92/100)を取ることができました。
この経験から、「自分で考えて答えを出す→すぐ答えを確認→わからなければ調べる」というのが自分の勉強スタイルの黄金パターンだということに気づきました。

ただ、残念ながらUdemyの模試タイプの講座は、50問解いて全ての問題の回答と説明を一気に確認するという形式になっています。
また、回答と説明の一覧画面にも問題文は書いてあるのですが、選択肢にどれが正解かというのが表示されてしまっていて、「自分で考えて答えを出す→すぐ答えを確認→わからなければ調べる」という私の黄金パターンが実施できない形式になっています。

これをなんとかすべく、回答、解説画面のデザインをJSで変更して、一問一答形式にするchrome拡張機能を作成しました!

実際に使ったやつを少し洗練させて、chromeウェブストアで公開もしました。
「show answer」ボタンをクリックして回答と解説を表示、「hide answer」ボタンをクリックして元に戻すという拡張機能です。

chrome.google.com

Udemyで模試を1周

日本語のProfessional Cloud Architectの模試(4回分)を受講しました。(当該コースが現在非公開になっているので紹介はできませんが、、)

もちろん自作のchrome拡張をフル活用して、4回分を1周しました。
以前は、1周だと定着していない問題もありました(この問題、模試でやったのにちゃんと理解していなくてわからない、、という悔しい思いをしました)。 が、今回は一問一答形式の勉強法により、わからない問題もしっかり調査、理解できており、明らかに定着していました。

模試とほぼ同一な問題が3割くらい、似たような状況の問題が3割くらい、といった形で非常に精度が高い問題が多かったので、すぐに試験を終えることもできました。
試験時間の半分くらいの時間で終えることができました。

(こういう質の高い模試があると、受験者としてはいいですが、資格自体に問題があるようにも感じてしまいますが汗)

終わりに

過去にGCPの他のProfessional資格を取得し、基礎知識があったこともあると思いますが、一問一答で勉強したことにより、短時間で資格合格ラインまで到達することができました。

GCPのProfessional資格も3冠目指して引き続き頑張りたいと思います。