zooo-log

読んだものとか、学んだこととか

GlideでSpreadSheetに情報連携させた後GASを動かすときに躓いた話

つまずきポイントは以下の通り

  • GASのトリガー設定
  • eventの種別

それぞれ詳しく書いていきます。

GASのトリガー設定

これはググるとすぐ出てきますが、Glideと連携させる場合は、 編集時ではなく、変更時にしないとGlideがイベントを発火させてくれず、 スプレッドシートにデータを追加してくれるものの、GASのイベントを発火するには至りません。

  • 編集時の挙動:Glideでデータ追加リクエスト→SpreadSheetにデータ追加|ここまで
  • 更新時の挙動:Glideでデータ追加リクエスト→SpreadSheetにデータ追加→GASのイベントが発火

そのため、以下のように設定する必要があります。

f:id:zoo666:20211023020031p:plain
トリガー設定

こうすることで、トリガーで設定した関数をSpreadSheetのデータ追加に応じて実行することができるようになります。

eventの種別

トリガー設定が編集時に動くものではなく、変更時に動くもの、ということで、 eventの種別もonEditのイベントではなく、onChangeのイベントになります。

developers.google.com

上記URLのページに、onChangeで渡されるイベントオブジェクトの構造があったので、それを参考に、、、 と思ったのですが、書かれている一覧にはSpreadSheetのデータに関する項目が見つからず。。。

結局、実際に渡されたオブジェクトを確認し、実装しました。 以下は実際に吐き出されたオブジェクトです。

{ authMode: 
   { toString: [Function: toString],
     name: [Function: toString],
     toJSON: [Function: toString],
     ordinal: [Function: ordinal],
     compareTo: [Function: compareTo],
     NONE: 
      { toString: [Function: toString],
        name: [Function: toString],
        toJSON: [Function: toString],
        ordinal: [Function: ordinal],
        compareTo: [Function: compareTo],
        NONE: [Circular],
        CUSTOM_FUNCTION: [Object],
        LIMITED: [Object],
        FULL: [Circular] },
     CUSTOM_FUNCTION: 
      { toString: [Function: toString],
        name: [Function: toString],
        toJSON: [Function: toString],
        ordinal: [Function: ordinal],
        compareTo: [Function: compareTo],
        NONE: [Object],
        CUSTOM_FUNCTION: [Circular],
        LIMITED: [Object],
        FULL: [Circular] },
     LIMITED: 
      { toString: [Function: toString],
        name: [Function: toString],
        toJSON: [Function: toString],
        ordinal: [Function: ordinal],
        compareTo: [Function: compareTo],
        NONE: [Object],
        CUSTOM_FUNCTION: [Object],
        LIMITED: [Circular],
        FULL: [Circular] },
     FULL: [Circular] },
  changeType: 'EDIT',
  source: {},
  triggerUid: 'xxxxxxxx',
  user: { email: '', nickname: '' } }

上記オブジェクトの中の、source の中に、スプレッドシートで更新されたデータに関する情報が含まれています。 その情報を取得する場合は、以下のような実装になります。

e.source.getActiveSheet();   // 更新のあったSheetオブジェクトを取得
e.source.getActiveRange();   // 更新された範囲のRangeオブジェクトを取得
e.source.getActiveRange().getA1Notation();   // 更新された範囲のRangeオブジェクトからA1Notation型で範囲を取得
e.source.getActiveRange().getCell(1,1).getValue(); // 更新された範囲の内、1行目の1列目の値を取得

SpreadSheetオブジェクトとRangeオブジェクトのメソッドについては、公式Docをご参照ください。

developers.google.com

developers.google.com

Glideはじめて触りましたが、ローコードという割に、最低限マクロなどの実装経験無いとやりたいことやるのは難しいのかな、と感じた次第です。

React + Prisma + PostgreSQL on Heroku でアプリを作ってみる

概要

誰でも見られて、サクッと作れる環境が欲しかったのと、 最近話題のPrismaを使ってみたかったので、Heroku上にアプリを作ってみました。

とりあえずPostgreSQLまでつながっていることが知りたかったので、 ボタンは何もデザインせず、出力結果もコンソールにそのまま出してますが、 React + Prisma + PostgreSQL on Herokuの構成で、 フロントからAPIをコールして、RDBからデータを取得するところまで作れました。

今後はここをベースに、ちょっとしたアプリを作っていこうと考えています。

f:id:zoo666:20201025145945p:plain

試した背景

これを試してみた具体な背景ですが、Prismaを使ったみたかったことに加え、 ちょっとしたPoCで検証するときになるべく低コスト(利用料と管理面両方)な環境がほしい、というのがあります。

AWSGCPといったクラウドサービスでも良いものの、 MFAやIAMのポリシーなど、セキュリティ対策をちゃんとしないといけなかったり、 無料枠の制限も限定的なので、利用料を気にしつつ使ったりと、 小規模(エンジニアが1人、2人くらい)だとめんどくさいなと感じることが多かったので、 Herokuを使うことで、その辺りを効率化できないかと考えました。

今回はHerokuの無料版のアドオンのPostgreSQLを利用しているので、DBに関しても利用料は無料でした。 GraphQLの学習コストはありますが、Prismaクライアントが用意されており、 フロントアプリとしては、非同期ライブラリからエンドポイントを指定して呼び出しができますので、 Prisma以外の学習コストは発生せず、Prisma自体もORMを使ったことがある人なら理解しやすそうな感じでした。 (Prismaをもっと触ってみると、色々課題が出るかもしれません。。)

Prismaを使う前提なので、こういった構成を考えましたが、 少し調べた感じですと、低コストという意味では、 フロントはHerokuで、バックエンドはFaunaDBあたりも良いのかも知れません。 (このあたりも今後勉強したいところです。) bagelee.com

アプリについて

アプリ構成の概要は、以下の図のStatic HTMLの部分を、Reactで作成したSPAに置き換えているだけです。 f:id:zoo666:20201025145332p:plain 引用元: Deploying to Heroku | Prisma Documentation

今回のHerokuアプリのディレクトリ構造は、以下のようになっており、 heroku-app/src/spa配下に作成しているReact アプリをビルドする際に、 生成されたアプリを、heroku-app/public にコピーすることで、 デプロイ時に、SPAとAPIを同時にデプロイするようにしています。

heroku-app
   |- Procfile
   |- README.md
   |- node_modules
   |- package.json
   |- prisma // prismaのスキーマ定義などが格納されています
   |- public // src/spa配下のアプリを配置するディレクトリ
   -- src
         |- api   // prismaクライアントを使ったAPIのソースが格納されます
         |- spa  // spaアプリのソースが格納されます

PrismaAPIの構築やHerokuへデプロイする方法は、以下のページを参考に進めました。 SPA開発するところ以外は、すべてこのページでなんとかなります。

www.prisma.io

終わりに

まだまだPrismaを学び始めたばかりなので、これから実際にアプリを作っていき、この構成の評価していきたいと思います。

USJを劇的に変えた、たった1つの考え方 を読んだ

USJを劇的に変えた、たった1つの考え方 成功を引き寄せるマーケティング入門 (角川書店単行本)

最近仕事で、DXという文脈で発生する、新規事業企画・開発のプロジェクトに携わることが多くなってきました。 私は、デザイン思考やUXデザインといった方法論を使いながら、企画を作っていきプロトタイプ開発、PoCなどをしています。

サービスを作っていく中で、ユーザから、良いね!や面白そう!といった言葉をいただくことはあるものの、 ただやはり事業として企画するものなので、経営層やマネージャ層からは「これがビジネスとして成立するのか?」が、 とても気にされるポイントであり、単にユーザからウケている、という話をしても納得してもらえないことが有りました。

説明時には、ビジネスの根拠として、事業計画・収支計画、といったものも作るのですが、 目標売上やコストといった数字は、それなりに推定や競合分析などで算出することができるものの、 数字に対する説得感や納得感みたいなところは弱く、いまいち腑に落ちないようなことが有りました。 そのため、売り方やほんとに売れるのか?という疑問に対する説得感・納得感を生み出すべく、マーケティングを学んでみようと思いました。

そこで、社内のマーケティングをやっている方に、おすすめの本や研修があるか聞いてみたところ、本書籍を紹介されました。 (実際には、マーケティングの講座みたいのを受けたほうが良く、講座内でケーススタディなども有り実践的に学べるようです。) 本書籍では、USJのV字回復を成し遂げた森岡さんという方がP&Gで学んだマーケティング論をカスタマイズした内容を、 実際にUSJで実践したマーケティングの事例などを交え、紹介されていました。

私が本書籍を読んで思ったことは、 ・マーケティングは、デザイン思考やUXと関連することが多い ・エンジニアは、マーケティングも学んでおくべき という2点です。

前者は、マーケティングが、ユーザ(消費者)視点に立ってどう売れるようになるか仕組みを考えること、というこの本の説明があり、 ユーザ中心にサービスや製品開発を行うことするための方法論であるデザイン思考やUXデザインと、とても親和性の高いものを感じました。 書籍の中で挙げられた話とUXデザインとマーケティングの関連性としては、以下のようなことが挙げられます。

  • 予期的なUX=認知や興味を引くための体験設計
  • エピソード的UX=製品を如何にリピートさせるかの仕組みづくり
  • 累積的UX=売り場でどうユーザに製品を思い出してもらうかの仕組みづくり、コミュニケーション戦略

また、マーケティングでもペルソナといったターゲットの定義も行いますし、 ”売れる”サービスのユーザ体験をデザインする上で、マーケティングは欠かせないものだと感じました。

そして後者は、このユーザの理解を、UXとしても、マーケティングとしても理解したエンジニアがいることによって、 ビジネスサイドと同じ視点を持って開発をすすめることができ、よりチームとして理想に近づく思ったからです。

エンジニアは、技術視点でサービスを捉えがちですが、 実際にサービスを使うのはユーザであるため、UXによる使いやすさや前後の体験だけでなく、 どうしたら買ってもらえるか、お金を払って契約してもらえるか、というところにまで考えが巡らせられると、サービスはより良いものになります。

具体的なサービスに落とし込む場合に、技術的観点からもビジネスと乖離しない改善点や仕様提案だけでなく、 細かいところでは、使いやすいUIや画面遷移、情報設計ができます。 これは、サービスをより良いものにする一つのチームとして、あるべき姿だと思いました。

そのため、エンジニアとしての本分は失わないようにしつつ、 マーケティングをどうサービス開発や機能開発に活かせるか、引き続き勉強していこうと思いました。

プログラミングTypeScriptを読んだ(6章・7章)

6章では、TypeScriptの高機能な型が有効的な使い方とともに、紹介されています。

実際のアプリ開発で使う場面を想定したサンプルも載っているため、 実装時に「あ、ここでやったことか」とフックになるような配慮がされているかと思いました。

これらの高度な型機能は、TypeScriptを利用していると、勝手に補完してくれます(例えば、過剰プロパティチェックのような機能)が、 実装中に、なんでこのエラーが出ているんだろう?どう直したら安全だろうか?を考える上で、この章の内容はとても重要だと思いました。 ただ書くだけであれば不要ですが、そんなことをすると後述するアサーション盛りだくさんのコードになり可読性が下がり、コードの品質は上がりませんし、そんなプログラミングは無意味かと思います。

まだまだTypeScriptを学び始めたばかりなので、リファレンスとしてこれからとてもお世話になる章だと思います。

7章では、アプリケーションを実装する上で、必要なエラー処理のパターンを解説してくれる章です。

6章:高度な型

  • 複雑な型(オブジェクトのフィールドが複数あるものや関数の型)は、サブタイプかどうかの判断が難しく、割当可能かどうかを判断するのに変性が鍵となる
  • 変性は、不変性、共変性、反変性、双変性の4つが存在する
    • 不変性:オブジェクトT自体でなければいけないことを示す
    • 共変性:オブジェクトTのサブタイプであるものを示す
    • 反変性:オブジェクトTがサブタイプであるものを示す
    • 双変性:オブジェクトTがサブタイプもしくは、スーパータイプであることを示す
  • 基本的に、TypeScriptは複雑な型はすべて、そのメンバーに関して共変であり、これがサブタイプと判断する条件の一つになる
    • ただし、関数のパラメータの型に対してのみ、反変である(以下に記述)
    • そのため、ある関数Aがある関数Bのサブタイプであると判断するには、以下の条件を満たす必要がある
      • 関数Aが関数Bと同じかそれより少ないパラメータ数を持ち、Aのパラメータの型はBのパラメータのスーパータイプ(:反変性)
      • Aのthisの型が指定されていない、もしくは、Aのthisの型がBのthisの型のスーパータイプ(:反変性)
      • Aの戻り値の型がBの戻り値の型のサブタイプ(:共変性)
  • constアノテーションは、値の制限だけでなく、型の推論を制限させる役割を持つ
  • タグ付き合併型を利用し、合併型に対して、リテラル型を付与し、型の絞り込みを明確にできる
    • 異なるevent型をそれぞれある関数でハンドリングしたい場合に、event型のもつ各プロパティの型が推論できないため、処理内で型を明確にする必要があるが、タグ付き合併型を利用し、タグによって型を推論させることで、個別のプロパティの型推論が不要になる
  • 完全性は、すべてのケースが実装されているか、自動的にTypeScriptがチェックする機能
    • 例えば、ある合併型が戻り値の型であるときに、一部の型だけ戻り値として実装されていると、戻り値に不足があると教えてくれる
      • これによって、switch文のcase文の書き漏れなどが無くなる
  • ルックアップ型は、オブジェクトの一部を型として定義することができるので、ネストされた複雑な型の型定義を容易にしてくれる
    • APIで返されるオブジェクトなど、ネストされている場合に個別なオブジェクトとして扱いたくなるが、それぞれ複雑な型であるため、型定義が冗長になったりするが、ルックアップ型を利用することで簡潔に記述可能
  • keyof演算子は、オブジェクトのすべてのキーを文字列リテラル型の合併として取得できるもの
    • ルックアップ型と併用することで、あるネストされたオブジェクトに対して、型安全にgetter関数が実装可能
  • マップ型とRecord型は、それぞれオブジェクトにおいてどのキーがどの値の型と対応するかを強制させるもの
    • マップ型は、ルックアップ型と組み合わせることで、Recordよりも細かに対応づけを定義できる(基本はこちらを利用すれば良さそう)
    • Record型は、ある型の集合が、別な型の集合のいずれかであるか(N:N)もしくは、ある型の集合がなんの型かを定義(N:1)まで(1:1対応はできない)
  • ユーザ定義ガードは、ある関数がなんの型の戻り値を返すかだけでなく、ある戻り値の型のときに、渡されていた引数の型がなんだったかまでを、型チェッカーに教えるための機能
    • これによって、汎用的な関数を実装する場合に、TypeScriptの型推論がスコープ外でも機能することになり、実装時に型のチェックが不要となる
  • 条件型は、型を三項演算子のように記述し、ある条件に当てはまればAという型、当てはまらなければ、Bという型、というような宣言が可能
    • 条件型は、合併型にも適応可能で、適応時は分配法則に従って型チェックされる
  • 型安全を処理的に自明である際に、TypeScriptの型判定をショートカットしたいようなときには、型アサーションや非nullアサーション、割当アサーションを利用することができる
    • ただし、これはTypeScriptの型推論によるバグの作り込みを回避する機会を、意図的に回避しているため、できるだけ使わないことが望ましい
    • あくまで処理的に自明であるときに限るなど、ルール化は重要
  • TypeScriptは構造的な型システムであるが、名前的型システムを実装することもできる
    • ある型に対してunique symbol型の交差を行うことで、以下の型を別な型と定義できる
    • type CompanyID = string & {readonly brand: unique symbol}type UserID = string & {readonly brand: unique symbol}は別な型と判断される
    • string型だけで定義をすると、TypeScirpt上ではCompanyID型とUserID型は構造的に等しくなるため、実装において制約を課すことはできない

7章:エラー処理

  • 例外を処理する場合、Javaのようなthrowsがないため、関数の戻り値の型に対して、合併型で例外の型を宣言しておく
    • 戻り値を受け取った変数がなんの型なのか、下流のコードで判断つかないため、その判断をする必要がある(判断しない場合エラーになる)
    • この判断をする処理を書くことが、例外処理の記述を促すフックになる
    • 合併型で宣言することで、関数自体の実装時には、TypeScriptの完全性によってすべての戻り値の型が返されているかをチェックされている
  • Option型という関数型言語などに由来する仕組みを利用して、値があるかないかわからない処理に対応するケース
    • JavaScriptには標準的に実装されていないため、独自実装もしくはライブラリを利用することになる
    • 値を返すのではなく、値やメソッドが含まれたコンテナと呼ばれるオブジェクトを返すことが基本となる
      • この本では、配列をコンテナとして使うケースやクラスを定義することで実現している
    • Option型ではあくまで、「失敗したことを知らせるだけ」であり、なぜエラーが起きたのかといった情報は得られない

個人的にエラー処理は、例外を用いて、「なぜ起きたのか」や「安全に処理を継続させる」、といったことに重きを置くべきと思っているので、Option型は用途が違うため、使うことはなさそうです。 関数型言語ではこういうのを使ってエラー処理を実装することもある、ということを知れたのは新たな発見だったので、そこは面白かったです。

プログラミングTypeScriptを読んだ(5章)

前回記事(プログラミングTypeScriptを読んだ(4章) - zooo-log)からの続きです。 5章読み終わりました。

基本的にはオブジェクト指向言語と同じ考え方で、TypeScriptと他の言語とが異なりそうなポイントを確認しました。 また、型エイリアスとインターフェースの差異は、3章での型エイリアス登場時にとても気になっていた点だったので、この章で記載されていて、とても勉強になりました。

ただ、型と値の名前空間が違うというのは、個人的には意外な点でした。(ここは自分の解釈が正しいかちょっと怪しいです。。)

クラスとインターフェース

クラスと継承

  • アクセス修飾子を付与しない場合、デフォルトで public 扱いとなる
  • インスタンスプロパティを宣言するときに、readonly修飾子を付与することができる

super

React書いているとよく出てくるsuper呼び出しですが、詳しい記事があったので、合わせて読むと良いと思いました。 qiita.com

戻り値の型としてのthis

  • メンバ関数の戻り値として自身のインスタンスを返したい場合、そのサブクラスにおいて、オーバーライドする必要がある。サブクラスを作るごとに、メンバ関数の戻り値を再定義するためにオーバーライドするのは面倒なので、戻り値の型としてthisを用いることでサブクラス内でオーバーライドが不要になる
// オーバーライドが必要な書き方
class Parent {
    has(value: number): boolean{
        // 
    }
    add(value: number): Parent{
        // 
    }
}

class Child {
    delete(value: number): boolean{
        // 
    }
    add(value: number): Child{ // 自身のインスタンスを返したいので、オーバーライドが必要になる
        // 
    }
}

// thisを用いた、オーバーライドが不要になる書き方
class Parent {
    has(value: number): boolean{
        // 
    }
    add(value: number): this{
        // 
    }
}

class Child {
    delete(value: number): boolean{
        // 
    }
    // Childクラスにおいて、addのオーバーライドは不要となる★
}

インターフェース

  • インターフェースと型エイリアスはほとんど同じ機能を提供するが3つの違いがある
  • エイリアスは右辺に任意の型・型の式(合併や交差)を指定可能だが、インターフェースは指定できない
// 以下の記述は、インターフェースでは代替できない
type A = number
type B = A | string
  • インターフェースを拡張する場合に、拡張元のインターフェースが拡張先のインターフェースに割り当て可能か検査する
  • 同じスコープ内の同じ名前のインターフェースはマージされるが、同じスコープ内の同じ型エイリアスコンパイル時エラーになる(=宣言のマージ)
    • ただし、同じ名前の2つのインターフェースにそれぞれ同じ名前だが異なる型で定義されたものが存在すると、コンパイルエラーになる

クラスは構造的に型付けされる

  • TypeScriptはクラスを名前ではなく構造であるメンバ関数やプロパティによって比較される
    • 同じ名前のメンバ関数を持ったクラスであれば、別々なクラスのインスタンスであっても割り当て可能
    • 例えば、MaleクラスとFemaleクラスにgetAge(): numberという同名かつ戻り値の型が同じメンバ関数が実装されていれば、関数 isHuman(person: Male)に対してMaleクラスのインスタンスも、Femaleクラスのインスタンスも割り当てることが可能
    • 中身の実装は異なっていても、問題ないが構造はチェックしているので、戻り値と関数名が同じであることが条件

クラスにおける値と型の名前空間の違い

その他

  • クラスにおけるジェネリック型の宣言は、クラススコープで宣言し、それはクラススコープ内でどこでも利用可能
    • インスタンスメソッドに対しても個別に宣言できる(クラススコープで定義しているものと共存可能)
    • ただし、静的メソッドではクラスのジェネリックにアクセスできないので、独自に宣言する必要あり(同名のジェネリックも宣言可能)
  • ミックスインは、相応の機能は提供していないが、独自実装可能
  • finalは提供されていないため、プライベートコンストラクターを利用して実装する

書籍ではTypeScriptにおけるデザインパターンの実装も書かれており、こちらもとても参考になるので、ぜひ本を買って読んでいただきたいです。

UX JAM Online #3に参加してみた

UXに関してゆるく語るイベントである、UX JAM Online#3に参加してみました。

母体である、UX MILKの開催するオフラインイベントは、 いつも人気でなかなか当たらないことが多かったので、参加枠のとても多いUX JAM Onlineへ参加してみました。

当日ライブ配信されていた録画映像はこちら


UX JAM Online #03

大変親切なことに、ライブ中に出ていた質問や、当日の登壇者の方々へのリンクは、 上記Youtubeの説明欄に記載されているので、そちらをご参照ください。

個人的に刺さったLTは、以下の3つ

  • 組織がユーザの解像度を上げるための3つの実践
  • UXプロジェクトでWowってなる瞬間
  • "トップページ"のUXを考える

組織がユーザの解像度を上げるための3つの実践

プロダクトやサービスの企画を考える人と、それ以外の組織、例えばステークホルダーや開発者は、直接ユーザと関わる機会の絶対量が格段に違います。

これは、ユーザの理解に大きく関わってきます。
そのため、(意図しない)思い込みや、一般的なイメージから、ユーザ像がズレてしまい、 UX設計や機能の作り込み、UIデザインの要件もズレてしまいます。
そのズレをなくすためにどうしたらよいか?に取り組んだのがこちらのLT。
実際には、ユーザを理解するために行うワーク(インタビューやワークショップ)に、
関係する人たちを巻き込んでいき、ユーザ理解を揃えていく、というお話でした。

中でも良かったのは、「スモールステップで進めている点」と、「無理強いをしない点」です。 これまであまりUXに携わったことのない人に、ワークショップに急に入ってくれと言われても、 重要性がわからなかったり、既存業務があったりで、優先順位を上げてはくれません。

そのため、PUSH型のアプローチで、情報発信を行い、 興味を持ったらその人達を徐々に巻き込む、というアプローチは、 どんな組織においても使えるテクニックだと感じました。

UXプロジェクトでWowってなる瞬間

このLTが刺さったのは、 UXDプロジェクトのUXが悪い という、一文です。(UXD=UXデザイン)

そもそも、最近(といってももう結構経ちますが)UXという言葉が認知され始め、 まだまだレイトマジョリティな大企業の人たちは、UXという言葉を正しく認知されているわけではありません。

そのため、UXDプロジェクトや、プロジェクト中にUXDを行う、といったケースでは、 そのプロジェクトを始めるところから、予期的UXと呼ばれる、UXを適切に設計すると、 どういう価値があって、どんな嬉しいことがあるんだろうか、というところから始めるべきです。

LTの中では、「感動の瞬間」というふうに表現してますが、 これをステークホルダーに体験してもらうことは、すごい重要だと、共感できました。

私の一例ですが、
ユーザリサーチに少しでも入ってもらい、 ユーザからのフィードバックや実際の値を説明することで、自分の感覚との乖離を知ってもらう。
といった取り組みは、効果が高いかと思います。

"トップページ"のUXを考える

サービスごとに、UXは異なるが、どんなふうにトップページをデザインしたらよいか?を教えてくれるLTでした。

ちょうど、新規事業に携わることが多く、 最近はマーケティング(ここでいうマーケティングは、如何にページの直帰率を下げるか、利用頻度を上げるか、という意味です)、 に携わる機会をいただくことも多いので、トップページにどんな情報を盛り込むかな?といったことを考えることが多いので、 とても参考になったLTです。

考え方は、 * トップページにどんなシチュエーションで訪れるか、どんな気持ちで訪れるのか、仮説を立てる * そのとき、どんなトップページであれば、その気持ちをポジティブ(より使いたくなる、離脱しなくなる状態)にできるか、を考える という流れでした。

至極当たり前かもしれませんが、こういったデザインができていないアプリを見ることは多いです。

ごちゃごちゃした情報だらけのトップページや、ただコンセプトだけを並べたトップページなど、 なにを意図しているのか理解できないことがあると、それだけでサービス利用をやめてしまうことはよくある話かと思います。

まとめ

UXについて学んだあとも、意識しないと忘れてしまうことは多いので、 こういったLTを聞いて、改めて重要性を再認識することはとても大事なことだと思った回でした。

コロナが続くと、オンラインの勉強会が多くなると思いますが、参加しやすいので、どんどん参加していこうと思いました。 (コミュニケーションも無理に取らなくても良いので、ハードルもとても低いです。笑)

また、UX JAM Onlineは、オンラインでもとても音声品質が良かったり、 質問がMiroでリアルタイムに誰でも質問しやすい空気があったりと、とてもユーザビリティの高い勉強会でした。
これも、運営のみなさまのご尽力かと思います。ありがとうございました。
また参加させていただきます。

プログラミングTypeScriptを読んだ(4章)

前回記事(プログラミングTypeScriptを読んだ(2章・3章) - zooo-log)からの続きです。 4章終わりました。 ポリモーフィズムの話は、C++を大学の頃にやっていた時ぶりでした。 久しぶりに読むと、やはり色々忘れていて、すごい時間がかかってしまいました。。

4章(関数)

関数宣言と呼び出し

関数宣言

  • 5種類の書き方が存在する。そのうち、最後の書き方は、絶対に使わないこと(型安全のためのTypeScriptなのに、型安全が保証されなくなる)
// その1:名前付き関数
function greet(name: string) {
  return 'hello ' + name
} 

// その2:関数式
let greet2 =  function(name: string) {
  return 'hello ' + name
} 

// その3:アロー関数式
let greet3 = (name: string) => {
  return 'hello ' + name
}

// その4:アロー関数式の省略記法
let greet4 =  (name: string) => 
  'hello ' + name

// その5:関数コンストラクタ
let greet5 = new Function('name', 'return "hello " + name')
※これはFunction型として定義され、戻り値もパラメータも任意の型を与えることができてしまう
  型安全にならないので、利用しないこと
  • パラメータは、省略可能にするための?修飾子の利用、レストパラメータによる可変長引数が定義できる
  • 省略可能な引数や可変長引数を定義する場合は、引数を列挙する際に一番最後にすること
  • レストパラメータをもつ可変長引数は、関数ごとに一つしか定義できない
// ?修飾子の利用
function sample( firstName: string, lastName?: string ){
  if( lastName )
    return 'Hello ' + firstName + ' ' + lastName
  else
    return 'Hello ' + firstName
}

// 可変長引数の定義
function sample2( ...numbers: number[] ) {
  return numbers.reduce(( total, n ) => total + n, 0 )
}
  • 基本的に、パラメータの型推論は文脈的型付けのケースを除いて実施されない
  • 文脈的型付けが用いられるのは、呼び出しシグネチャが定義されているときと、コールバック関数の2パターン
  • 呼び出しシグネチャは、関数のパラメータと戻り値の型を定義するために用いる
type Log = (message: string, userId?: string) => void // string型の引数を二つ持ち、後者はオプショナル、戻り値はvoid型、という関数であるLog型
  • コールバック関数におけるパラメータは、コールバックを渡す先の関数から推論される

関数呼び出し

  • 関数の実行方式は、以下の4種類があり、それぞれ特徴がある
function add(a: number, b: number): number {
  return a + b
}

add(10, 20) // いつもの呼び出し方
add.apply(null, [10,20]) // applyは2つの引数を持ち、1番目はthisにバインドされ、2番目は関数のパラメータとして展開される
add.call(null, 10, 20) // callは1番目については同様で、2番目以降はaddで必要な引数をそれぞれ記述する必要がある
add.bind(null, 10, 20)() // bindはapplyやcallと違って、関数自体(ここでいうadd)は実行されず、新たな関数が返される
  • bindは、clickHandlerとかでイベント変数渡してそのコンポーネントごとのクリック処理をさせる、とかするためによく使う
  • thisはJavaScriptの世界では呼び出され方によって値が変化してしまう問題がある、TypeScriptは、関数内でthisを用いる場合に、引数時に期待するthisの型を宣言することで、実行時エラーを回避する
// thisがDate型であることを宣言している
function fancyDate(this: Date) {
  // 処理
}

// 以下はコンパイル時にエラーが出る
fancyDate() // voidのthisが与えられるため、コンパイラからDate型のthisを求められる
  • ジェネレータにおける型の定義は、function* 関数名とすることで宣言可能
  • 反復可能オブジェクト= Symbol.iterator というプロパティを持つオブジェクト
  • イテレーター = next というメソッドを定義しているオブジェクト。valueとdoneのプロパティを持つオブジェクトを返す

ポリモーフィズム

ジェネリック

  • <>を利用することで、ジェネリック型パラメータを宣言することができる
  • エイリアスに対してジェネリック型を宣言した場合と、呼び出しシグネチャの一部としてジェネリック型を宣言した場合で、バインドするタイミングが異なる
  • 前者は、型エイリアスの利用時、後者は、関数の実行時となる
  • 完全な呼び出しシグネチャ、省略記法の呼び出しシグネチャ、名前付き関数の呼び出しシグネチャそれぞれに対して宣言の仕方は異なる
  • 完全な呼び出しシグネチャ、省略記法の呼び出しシグネチャは、シグネチャ全体、シグネチャの一部、どちらでもジェネリック型を宣言することができる
  • 名前付き関数の呼び出しシグネチャは、シグネチャ全体に対してのみ宣言ができる
  • シグネチャ全体に行う場合は、シグネチャ名の横 type FIlter<T> = (・・・)function filter<T>(・・・)といった形式
  • シグネチャの一部に行う場合は、スコープの直前 type Filter = <T>(・・・) といった形式
  • ジェネリック型の推論は、すべてをアノテートするか、しないかの2択で、2つのジェネリック型を宣言している場合に、片一方のみアノテートすることは許可されない
  • Promiseを使った処理を書こうとすると、 promise.then( result => result * 4 ) としている場合に、resultの型はresult自身(=関数の引数)を見て推論されるので、unknown型と推論されてしまう(then関数で、引数はunknonw型で定義されているため)
    f:id:zoo666:20200421024220p:plain
    then関数の定義
  • 制限付きポリモーフィズムによって、ただジェネリック型を定義するのではなく、ある条件を満たすジェネリック型として定義する。これによって、すべての型を許容しないが、数種類の型に対して型安全を保証したいケースをサポートしている
  • 例えば、関数に対して、ある型とそのサブタイプの変数のみを許容したい場合に用いる 例:function mapNode<T extends TreeNode> // TreeNode型とそのサブタイプのみ許可
  • 複数の制限をつけたい場合は、型を交差させる 例: function mapNode<T extends TreeNode & Node // TreeNode型とNode型の交差を表現
  • 可変長引数でも、制限付きポリモーフィズムはTypeScriptの型推論によって宣言可能
  • 以下の例では、callが実行されるときに、引数に与えられた関数から「何個のどんな型の引数」と「戻り値の型」を関数定義から推論し、チェックしている
    f:id:zoo666:20200424004555p:plain
    型推論の強力さがわかる
  • ジェネリック型のデフォルトの方を定義することで、デフォルトの型は手動でバインドする必要がない
  • 例: type MyEvent<T extends string = string> // ジェネリック型Tのデフォルトの型をstring型で定義
  • ジェネリック型の配列の要素を個別に指定したい場合、次のように書く `` funciton call(){} // 2番目の要素がstring型の配列のジェネリック型Tを宣言している
  • (TypeScriptの話はないけれど、知らなかったので)b.every( _ => _ === a) // 配列bの要素が 配列aの要素とすべて一致したらtrue、そうでなければfalseを返す、という関数