lamechang-dev

Webフロントエンドエンジニア lamechangのブログ。

【アルゴリズム】トリボナッチ数列のアルゴリズム

トリボナッチ数列とは

- [tex: T{0} = 0] - [tex: T{1} = 0] - [tex: T{2} = 1] - T{N} = T{N-1} + T{N-2} + T_{N-3} (N = 3, 4...)

によって定義される数列のことであり、[0,0,1,1,2,4,7,13,24,44...] と値が続いていく数列です。フィボナッチ数列が前2つの数字を足した数列であるのに対して、トリボナッチ数列は前3つの数字を足した数列になります。以下で、フィボナッチ数列の第 N T_{N} を計算するアルゴリズムを考えます。

上記に記載した通り、既にシンプルな漸化式が定義されているのでそれを用いて再帰関数を用いてまずアルゴリズムを表現してみます。

アルゴリズム実装(1) メモ化再帰  O(N)

メモ化を利用しない単純な再帰関数の呼び出しでは、計算量は指数的に増大してしまいます(計算量の導出は省略)。なので、同じ引数によって実行されたtribonacci()の計算結果をmemo という配列に格納するように工夫し、仮に呼び出された再帰関数の引数に対応したmemoの値が存在したら単純にそれを返すようにします。

vector<long long> memo;

long long tribonacci(int i) {
  if (i == 0) return 0;
  else if (i == 1) return 0;
  else if (i == 2) return 1;

  if (memo[i] != -1) return memo[i];

  memo[i] = tribonacci(i-1) + tribonacci(i-2) + tribonacci(i-3);

  return memo[i];
}

int main(void) {
    int N;
    cin >> N;

    memo.assign(N + 1, -1);

    cout << tribonacci(N) << endl;
}

これはいわゆる一般的なキャッシュと同じ考え方であり、メモ化をしていないケースに比べると圧倒的に計算量を抑えることができますね。

アルゴリズム実装(2) 単純なfor反復  O(N)

再帰関数の利用ではメモ化でのキャッシュ効率化はほぼ必須ですが、そもそも「前の3項を順々に足し合わせていく」という挙動は単純なfor反復でも実現可能です。この場合、計算量はそのループ数に依存するので計算量は O(N)とかなり抑えられます。

#include <iostream>
#include <stdio.h>
#include <vector>
using namespace std;

int main(void) {
    int N;
    cin >> N;
    vector<long long> tribonacci;

    tribonacci.assign(N + 1, 0);

    for (int i = 0; i < N; i ++) {
      if (i == 0) tribonacci[i] = 0;
      else if (i == 1) tribonacci[i] = 0;
      else if (i == 2) tribonacci[i] = 1;
      else tribonacci[i] = tribonacci[i - 1] + tribonacci[i - 2] + tribonacci[i - 3];
    }

    cout << tribonacci[N - 1] << endl;
}

大学のパッとしないGPAを米国基準にしてみたら、思いの外まともだった

はじめに

CS大学院進学

以前にキャリアの振り返りをしたこの記事でも軽く触れていたんですが、そもそも自分には大学生の時にCS大学院に行くかどうかを迷った結果院進学をやめてSIerに入社したという経緯があります。大学4年時に行った留学先にも出願準備のためにTOEFLの参考書を持っていきました。結局ほとんど使いませんでしたが。

この時は「社会人になってから、必要性を再度感じた時に進学を目指そう」と判断しました。

lamechang-dev.hatenablog.com

で、最近米国のオンラインCS修士の記事などを拝見させていただき、以下の点が自分にとって非常に魅力的に思えました。

  • 働きながら継続できそう
  • 英語での受講ができる & 入学要件にTOEFLのスコアがあって英語力向上にも利用できそう
  • コストパフォーマンスが優れている

zenn.dev

結婚しているので家庭の事情などももちろん考えながらではあるのですが、出願に向けて、英語のリハビリにもなるしTOEFL対策だけでも今年から少しずつ始めようかな?と思っている所であります。

ただ1つ大きな懸念点が。

GPAに対する不安

出願することを検討した際に、ずっと不安要素だったものがあります。そう、学部時代のGPAですね。

米国をはじめとする海外のCS修士を目指すってなった時に誰もが気にすることだと思いますし、自分も正直大した成績で卒業してないので「そもそもミニマムの出願要件満たせてないんじゃね・・?」と思ったりしていました。

が、どうやら米国基準のGPA計算は日本のそれと違うらしく、人によっては所属大学のGPAより大きく上昇することがあるということをたまたま知り、「これは自分も確認する必要があるな・・」と思い立った次第です。

家の書類置き場の奥の方に眠っていた成績証明書を引っ張り出して計算してみました。

WES

WESとは

アメリカやカナダでの留学生や移民の資格認定評価を提供する非営利団体のことらしいです。一部のアメリカの大学では、アメリカ国外の成績表認証・GPA計算等の業務をこのWESに委託している場合もあるみたいです。

WES iGPA Calculator

そしてこのWESのHPに WES iGPA Calculator というツールが置いてあります。名前から分かる通り、GPAの計算機ですね。100%実際の基準となるとは言い切れませんが、これを利用することで米国基準で判断される場合のGPAの値を計算できそうですね。参考に利用してみる価値はありそうです。

私の出身大学の場合、A+は4点、Aは3点、Bは2点、Cは1点、不合格は0点 という計算式に基づいてGPAが算出されていましたが、どうやらWESの計算式は Aは4点、Bは3点、Cは2点、Fが0点 で算出されるっぽいです。なるほど、単純に数値が一段階ずつ上がることになりますね・・

計算されたGPA

3.4 になりました。大学院によってはGPAの推奨値として3.5以上を課す所(いわゆるトップ校など)もあるので全く問題ない数値とは言い難いですが、私が進学を検討しているオンラインのCS大学院の要件は大体クリアできているみたいです。計算してみてよかったです😿

最後に

CS修士の出願要件として最もリカバリがきかないのがGPAだと思っていて、今回それがある程度問題ないことが分かって良かったです。

大学時代ってろくに勉強もせずにバンド練習やバイト・飲み会をやっていたような気がしていたのですが、昔の成績表などを見ると意外と頑張って勉強したような科目もあって、当時の学生生活を思い出したりしました。

ただ、時が戻せるならもうちょっとちゃんと本腰を入れて勉強した上で、CSの大学院に行ってたとは思います。人生。

コワーキングスペースの土日プランを契約した話

コワーキングスペースを契約しました

家から自転車で10分弱程度のところにあるBIZcomfortを土日プランで契約しました。めちゃくちゃいいのでそれについてざっと書きます。

bizcomfort.jp

契約した理由3点

1.副業を再開するため

また本業と別のスタートアップのお手伝いを少しずつ再開することになったので、ガッと集中できる場所が欲しかった&安定したネットワーク環境が欲しかった、というのが一番の理由です。

まぁ、これがなくてもいつか契約したいとは思っていたのですが副業を再開することが背中を押しました😤

これまでは土日は午前中の時間を作業・勉強や副業に充てるようにしていたのですが、最近通っていた喫茶店(タリーズ)のWifiがめちゃくちゃ遅くなったのもあり(ちなみに私は土日に家で勉強や仕事は遊んでしまうので一切できません)、Google meetなどの会議が動画付きでろくにできないことに気づいて喫茶店での作業はもう諦めました。

2.感染対策

なんだかんだカフェは飲食店&みなさんめちゃくちゃ会話しているので、ちょっと飲食店に長居が怖いのもありました😤 コワーキングはみなさんマスクしていて静かですね。

3.めちゃくちゃコスパがいい

BIZcomfortについてですが、土日プランのコスパが本当に良すぎます。これはHPで料金見ていただければわかると思うのですが本当に良心的な値段です。

しかも自分が利用している拠点はなんとディスプレイついてて本当に助かります😿 どこもついてるのかな。あと最近個人事業主として開業したのもあって、経費にできることも考慮すると本当に安い。

また通うに際してちょっと遠いくらいの方が、通うのに軽い運動になっていいかな、と思ったのも契約した理由です。

終わりに

半年ほどは利用するのは間違いないので、またその際にレビューなどざっと書きます。

【TypeScript】レコード型とマップ型

はじめに

今回はTypeScriptにおいてObjectの型定義をしていく上で必須知識とも言っても過言ではないレコード型・マップ型についてです。花粉辛い。

レコード型

TypeScriptの組み込みのRecord 型は 、 あるものからあるものへのマップ(対応付け)としてオブジェクトを表現するための方法です。オブジェクトが特定のキー&バリューの集まりであることを強制するために Record 型は有用です。

以下のような記法を取ります。

type RockPaperScissors = "Rock" | "Paper" | "Scissors";

type Hoge = Record<RockPaperScissors, RockPaperScissors>;

const winTarget: Hoge = {
  Rock: "Scissors",
  Paper: "Rock",
  Scissors: "Paper",
};

類似の記法に インデックスシグネチャ があります。こちらも同様にオブジェクトの値の型を制約できますが、キーに採用できるのは stringnumber だけになりますね。例えば、上記の例で定義した 文字列リテラルのユニオン型である RockPaperScissors は キーに採用できません。エラーも、「代わりにmapped object type を使ってくれ」と言ってます。

このように、Record型のキーは、stringnumberのサブタイプであれば自由に制約をつけることができます。

// An index signature parameter type cannot be a union type. 
// Consider using a mapped object type instead.
const winTarget: { [key: RockPaperScissors]: RockPaperScissors } = {
  Rock: "Scissors",
  Paper: "Rock",
  Scissors: "Paper",
};

また、オブジェクトが特定のキーの集まりを定義することを強制できることもポイントです。以下のように、最初に提示した Record型の例からキーRock を消してみました。するとどうなるでしょうか?

// Property 'Scissors' is missing in type '{ Rock: "Scissors"; Paper: "Rock"; }' 
// but required in type 'Hoge'.ts(2741)
const winTarget: Hoge = {
  Rock: "Scissors",
  Paper: "Rock",
  // Scissors: "Paper",
};

有益なエラーメッセージを得られていることがわかります。これはTypeScriptにおける 完全性による網羅チェックによる挙動になります(この記事では割愛)。

マップ型

マップ型はより強力な(柔軟な)型宣言が可能になります。と言うより、そもそもTypeScriptのRecord型自体がこのマップ型を利用して実装されているため、当たり前といえば当たり前かもしれません。以下がRecord型の実際の定義です。

/**
 * Construct a type with a set of properties K of type T
 */
type Record<K extends keyof any, T> = {
    [P in K]: T;
};

Record型の定義に登場した独自の構文がありますね。[P in K]: T記述された部分です。名前が示すように、これはオブジェクトのキー・値両方の型をマッピングするための方法です。具体的な記法の例を以下に紹介していきます。

type Pokemon = {
  skills: Array<string>;
  name: string;
  weekTypes: Array<string>;
};

// すべてのフィールドを省略可能にします type OptionalPokemon = {
type OptionalPokemon = { [K in keyof Pokemon]?: Pokemon[K] };
// すべてのフィールドをnull許容にします type NullablePokemon = {
type NullablePokemon = { [K in keyof Pokemon]: Pokemon[K] | null };
// すべてのフィールドを読み取り専用にします type ReadonlyPokemon = {
type ReadonlyPokemon = { readonly [K in keyof Pokemon]: Pokemon[K] };

このように柔軟に型情報を定義することが可能です。

組み込みのマップ型

上記に紹介したようなマップ型は実際の開発現場でも利用頻度が高いものが多いです。TypeScriptはこのような頻出と思われるマップ型の型定義があらかじめ定義されています。特にチーム開発ではこれらを採用していくことでObjectの型定義の可読性が上がると思うので、積極的に使っていきましょう。

// Keys 型のキーと Values 型の値を持つオブジェクト。
Record<Keys, Values>
// Object内のすべてのフィールドを省略可能と指定します。
Partial<Object> 
// Object内のすべてのフィールドを必須(省略不可)と指定します。
Required<Object> 
// Object内のすべてのフィールドを読み取り専用と指定します。
Readonly<Object> 
// 指定された Keysだけを持つ、Objectのスーパータイプを返します。
Pick<Object, Keys>

終わり

【TypeScript】過剰プロパティチェックの挙動

過剰なプロパティチェック

TypeScriptには、過剰なプロパティチェックを行う機能が存在します。まず、以下の例を見てみましょう。

type Pokemon = {
  ability?: Array<string>;
  weekType: string;
};

const zenigame: Pokemon = {
  // Type '{ abiility: string[]; weekType: string; }' is not assignable to type 'Pokemon'.
  // Object literal may only specify known properties, but 'abiility' does not exist in type 'Pokemon'.Did you mean to write 'ability'?
  abiility: ["bodyAttack", "waterGun"],
  weekType: "denki",
};

オブジェクト zenigameは型 Pokemonを期待しているので、前回の記事の通り、そのサブタイプを渡してあげればよさそうです。

ここで、私はzenigame のプロパティ名を1つ間違えてしまいました。abiility はタイポしてますね。javascriptではこういったバグよく起こりますよね。

これは型エラーになるかなぁと一瞬思うも、しかし、よく見ると型Pokemonのプロパティabilityは型が Array<string> | undefinedであるので、プロパティabilityzenigameにないことは abilityundefinedであることと同値であり、これはArray<string> | undefinedのサブタイプなので条件を満たします。

なので、タイポはしたものの zenigameはサブタイプの条件を満たしているように見えますね。なぜTypeScriptはこのバグを検知することができたのでしょうか?

この挙動こそ、過剰プロパティチェックです。オブジェクトリテラルからTypeScriptが推論を行う型に関して適用されます。フレッシュなオブジェクトリテラル型 Aを別の型 Bに割り当てる際に、Bには存在していないプロパティがAに存在している場合、TypeScript はエラーを吐きます。

仮にそのオブジェクトリテラルが型アサーションを使用しているか、変数に割り当てられているのであればこの挙動は適用されません。具体的に以下の例を見てみましょう。

過剰プロパティチェックが適用されないケース

変数に割り当てられる際の例

type Pokemon = {
  ability?: Array<string>;
  weekType: string;
};

// オブジェクトリテラル型は代入を通して通常のオブジェクト型に拡大される
const zenigame = {
  abiility: ["bodyAttack", "waterGun"],
  weekType: "denki",
};

// OK
const pokemonA: Pokemon = zenigame;

アサーションの例

type Pokemon = {
  ability?: Array<string>;
  weekType: string;
};

// OK 
const zenigame: Pokemon = {
  abiility: ["bodyAttack", "waterGun"],
  weekType: "denki",
} as Pokemon; // asによる型アサーション。オブジェクトの型がPokemonであることをTypeScriptに主張する

と言った感じで、フレッシュでないオブジェクトリテラル については過剰プロパティチェックの挙動は生まれません。

終わり。

【TypeScript】サブタイプとスーパータイプ、そして変性について

サブタイプとスーパータイプ

TypeScript上での型の間の関係についての概念である サブタイプ スーパータイプ についてです。

サブタイプ(subtype)

A、Bという2つの型があった場合に、BがAの派生型である場合、Aが要求されるところではどこでも、Bを利用することができる。

  • 配列はオブジェクトのサブタイプ
  • タプルは配列のサブタイプ
  • 数値 1 は numberのサブタイプ

スーパータイプ(supertype)

サブタイプの逆です。

A、Bという2つの型があり、BがAの上位型である場合、Bが要求されるところではどこでも、Aを安全に使うことができる。

  • オブジェクトは配列のスーパータイプ
  • 配列はタプルのスーパータイプ
  • number は 数値 1のスーパータイプ

変性とは?

ほとんどの基本型やシンプルな型(number, string, boolean...)については、なんらかの型Aが別の型Bのサブタイプであるかどうかを判別することは簡単ですね。例えば、 number | string という型があった場合にこれが string のスーパータイプであることは明らかです。

次に以下のようなオブジェクトの例を見てみます。以下のような例を考えます。

type LegacySong = {
  id?: number | string;
  name: string;
};

function deleteSongId(song: { id?: number; name: string }) {
  delete song.id;
}
const legacySong: LegacySong = {
  id: 123456,
  name: "old song",
};
//  Type 'string | number | undefined' is not assignable to type 'number | undefined'.
//  Type 'string' is not assignable to type 'number | undefined'.
deleteSongId(legacySong);

deleteSongId()が引数として期待しているオブジェクトの型と実際に渡されているとの間に問題が生じていることがわかります。

具体的に言うと、deleteSongId() は引数として期待するオブジェクトのプロパティ idnumber | undefinedであることを期待していますが、実際に呼び出し部分で渡されているlegacySongのプロパティ id は 'string | number | undefined'になっています。

TypeScriptはこのように、関数の引数やあらゆるシチュエーションにおいてオブジェクトが渡されることが期待される場合、期待される型のスーパータイプであるプロパティの型を持つ形状は渡すことができません。そしてこの性質を共変と呼びます。

そして、共変は、4種類の変性(variance)のうちの1つにすぎません。以下にその4種類の変性をまとめます。

  • 不変性(invariance)
    • Tそのものが必要
  • 共変性(covariance)
    • Tそのもの、もしくはサブタイプが必要
  • 反変性(contravariance)
    • Tそのもの、もしくはスーパータイプが必要
  • 双変性(bivariance)
    • Tそのもの、もしくはスーパータイプ、サブタイプが必要

関数の変性

関数Aが関数Bのサブタイプであるためには、以下を満たす必要があります。

  • 関数Aが関数Bと同じかそれより低いパラメーターの数を持つ。
  • 関数Aのthis型が指定されていない、または「Aのthis型がBのthis型のスーパータイプ」である。
  • 対応するそれぞれのパラメーターについて「Aのパラメーターの型 が Bのパラメーターのスーパータイプ」である。
  • 「Aの戻り値の型 が Bの戻り値のサブタイプ」である。

それぞれの構成要素(this型、パラメーターの型、戻り値の型)について、単にサブタイプでなのかな・・・と思いきやそれに準ずる要素は戻り値の型だけで、要素によって性質が反転する必要がありますね。なぜこのようなことになるのでしょうか?

以下の例で考えてみましょう。まず検証を行うために、3つのクラスを用意します。

class Pokemon {}

//DenkiPokemonはPokemonのサブタイプ
class DenkiPokemon extends Pokemon {
  electricShock() {
    console.log("電気ショック!");
  }
}

//PikachuはDenkiPokemonのサブタイプ
class Pikachu extends DenkiPokemon {
  lightning() {
    console.log("雷!");
  }
}

次に、以下ような1つの関数を引数として期待するコールバック clone()を定義します。

cloneに対してdenkiToDenkiを渡した時にエラーが起こらないことは明らかですね。じゃあ、denkiToPokemonはどうなんだと言うと、これはエラーを起こしてしまいます!😿

const clone = (f: (b: DenkiPokemon) => DenkiPokemon): void => {
  const parent = new DenkiPokemon();
  const babyDenkiPokemon = f(parent);
  babyDenkiPokemon.electricShock();
};

const denkiToDenki = (b: DenkiPokemon): DenkiPokemon => {
  return b;
};

const denkiToPokemon = (d: DenkiPokemon): Pokemon => {
  return d;
};

clone(denkiToDenki); // OK
clone(denkiToPokemon); // Argument of type '(d: DenkiPokemon) => Pokemon' is not assignable to parameter of type '(b: DenkiPokemon) => DenkiPokemon'.

clone()の処理を確認してみましょう。仮に渡す関数fがPokemonを返すとすると、処理の中で呼んでいる電気ショックをクラスPokemonは覚えていないので、実行することができないことがわかると思います。こういった理由から、TypeScriptは、渡された関数が「少なくとも」DenkiPokemonないしそのサブタイプを返すことを、コンパイル時に確認する必要があるのです。

一方で、パラメータについてはどうでしょうか。同じように確認してみます。

const clone = (f: (b: DenkiPokemon) => DenkiPokemon): void => {
  const parent = new DenkiPokemon();
  const babyDenkiPokemon = f(parent);
  babyDenkiPokemon.electricShock();
};

const pokemonToDenki = (b: Pokemon): DenkiPokemon => {
  return new DenkiPokemon();
};

const pikachuToDenki = (d: Pikachu): DenkiPokemon => {
  d.lightning();
  return new DenkiPokemon();
};

clone(pokemonToDenki); // OK
clone(pikachuToDenki); // Argument of type '(d: Pikachu) => DenkiPokemon' is not assignable to parameter of type '(b: DenkiPokemon) => DenkiPokemon'.

こちらも先程のケースと同様の考え方になります。 仮に関数のパラメータにDenkiPokemonのサブタイプであるPikachuを渡してしまうと、実際に関数がclone()の中で呼ばれる際にクラスDenkiPokemonlightning()を覚えていないのでエラーを吐くことになります。なので、TypeScriptは、渡された関数のパラメータが「少なくとも」DenkiPokemonないしそのスーパータイプを返すことを、コンパイル時に確認する必要があるのです。

これは、関数がそのパラメーターおよびthisの型に関して反変(contravariant)であることを意味します。つまり、ある関数が別の関数のサブタイプであるためには、それぞれのパラメーターおよびthisの型が、もう一方の関数で対応するものに対してスーパータイプである必要があります。

まぁ実際の現場の開発ではこのルールを覚えて実装する、なんて必要はないと思いますが、この制約が生まれる背景はしっかり抑えとくといいのかなと思いました。

※ちなみにTypeScript の関数は、それらのパラメーターおよび this の型に関して、デフォルトで共変らしいです。より安全な反変な振る舞い を選択するには、tsconfig.jsonの中で、{"strictFunctionTypes": true}フラグを有効にしてあげればOKです。

参考文献

www.oreilly.co.jp

【TypeScript】【React】【Recoil】翻訳しながらそれなりに型をつけて細々と進めていくRecoil Tutorial その4 Asynchronous Data Queries編(2)

f:id:lamechang_dev:20220213113950p:plain

本記事は、Reactの提供元であるFacebook改めMetaが開発中の新しい状態管理ライブラリである Recoil の公式チュートリアルを翻訳しつつ、TypeScriptで型付けをしながら進めていく連載の第四弾です。細々とやっていきます🙄 今回は Asynchronous Data Queries の内容について書いていきます。前回が(1)で、今回は(2)に当たります。

recoiljs.org

Concurrent Requests

If you notice in the above example, the friendsInfoQuery uses a query to get the info for each friend. But, by doing this in a loop they are essentially serialized. If the lookup is fast, maybe that's ok. If it's expensive, you can use a concurrency helper such as waitForAll to run them in parallel. This helper accepts both arrays and named objects of dependencies.


上記の例で気付くかもしれませんが、friendsInfoQuery()はQueryを使用して各友達の情報を取得します。ただし、これをループの中で実行することにより、基本的に逐次的に処理されます。探索が速い場合は、おそらくそれで問題ありません。処理に時間がかかる場合は、waitForAllなどの同時実行ヘルパーを使用してそれらを並行して実行できます。このヘルパーは、依存する配列と名前付きオブジェクトの両方を受け入れます。


なるほど、下記のコードの通りに selectorが依存している selectorFamily である useInfoQuery の配列を waitForAll() の引数として渡すことで、内部で実行される非同期処理を逐次処理ではなく並行処理として扱うことができる、ということでしょうね。

json-serverを利用していると非同期の挙動がterminal上での出力することができますが、それを利用すると並列処理が走り効率的に非同期を処理していることがわかりやすいです。

f:id:lamechang_dev:20220223162257g:plain

export const friendsInfoQuery = selector<Array<User>>({
  key: 'FriendsInfoQuery',
  get: ({get}) => {
    const {friendList} = get(currentUserInfoQuery);
    const friends = get(waitForAll(
      friendList.map(friendID => userInfoQuery(friendID))
    ));
    return friends;  },
});


You can use waitForNone to handle incremental updates to the UI with partial data


waitForNoneを使用して、部分的なデータを使用したUIの増分更新を処理できます

waitForNone は渡された依存関係の状態の Loadable オブジェクトを返します。 例えば今回の例のように依存関係にある selectorFamily userInfoQuery の配列について、並列的にそれぞれの非同期を実行しPromiseが解決したものから部分的にUIを更新する、といったような挙動を以下のようなコードで実現することができるみたいです。

ちなみに、Loadable の詳細はこちらLoadable オブジェクトはatom もしくは selectorの現在の状態を表現しており使用可能な値があるか、エラー状態にあるか、非同期解決が保留されている可能性があります。これらはそれぞれhasValuehasError、または loadingで表現されます。

以下の例の friendsInfoQueryreturn部分を見ると、取得した friendsのうち statehasValueであるもののみにfilterをしていることがわかります。

const friendsInfoQuery = selector({
  key: 'FriendsInfoQuery',
  get: ({get}) => {
    const {friendList} = get(currentUserInfoQuery);
    const friendLoadables = get(waitForNone(
      friendList.map(friendID => userInfoQuery(friendID))
    ));
    return friendLoadables
      .filter(({state}) => state === 'hasValue')
      .map(({contents}) => contents);
  },
});

Pre-Fetching

For performance reasons you may wish to kick off fetching before rendering. That way the query can be going while we start rendering. The React docs give some examples. This pattern works with Recoil as well.


パフォーマンス上の理由から、レンダリングの前にフェッチを開始したいと考えるかもしれません。そうすれば、レンダリングを開始するときにクエリを実行できます。 Reactのドキュメントにはいくつかの例があります。このパターンはリコイルでも機能します。


Let's change the above example to initiate a fetch for the next user info as soon as the user clicks the button to change users:


上記の例を変更して、ユーザーがボタンをクリックしてユーザーを変更するとすぐに、次のユーザー情報のフェッチを開始しましょう。

これ、突然 useRecoilCallback が登場してきてるので、そちらの挙動の確認をした方がいいでしょう。

さてuseRecoilCallbackですが、こちらは subscribeさせずに atomもしくは selector を読み込んで何かをする、という時に利用されると考えます。subscribeによる状態管理は、値の変更を常に応じて状態を常に更新する必要が出てきてしまいますが、下記の例のように onClickが走った時のみに値を取得し、再レンダリングを行いたい、といったことを実現することができます。

const CurrentUserInfo: React.VFC = () => {
  const currentUser = useRecoilValue(currentUserInfoQuery);
  const friends = useRecoilValue(friendsInfoQuery);

  const changeUser = useRecoilCallback<Array<number>, void>(({snapshot, set}) => userID => {
    snapshot.getLoadable(userInfoQuery(userID)); // pre-fetch user info
    set(currentUserIDState, userID); // change current user to start new render
  });

  return (
    <div>
      <h1>{currentUser.name}</h1>
      <ul>
        {friends.map(friend =>
          <li key={friend.id} onClick={() => changeUser(friend.id)}>
            {friend.name}
          </li>
        )}
      </ul>
    </div>
  )};

export default CurrentUserInfo;


Note that this pre-fetching works by triggering the selectorFamily() to initiate an async query and populate the selector's cache. If you are using an atomFamily() instead, by either setting the atoms or relying on atom effects to initialize, then you should use useRecoilTransaction_UNSTABLE() instead of useRecoilCallback(), as trying to set the state of the provided Snapshot will have no effect on the live state in the host .


このプリフェッチは、selectorfamily()による非同期クエリの実行と、selectorのキャッシュを入することによって機能します。代わりにatomFamily()を使用している場合は、Atomを設定したり、atoms effectsによる初期化を利用することで、userecoilCallback()の代わりにuseRecoilTransaction_UNSTABLE()を使用してください。提供されたSnapshotのstateを設定しようとしても、RecoilRootのstateには影響しません。


Asynchronous Data Queries の部分は新しい情報がモリモリですね。Query Default Atom Values以降については更に記事を分けて、(3)としたいと思います。