Nuxt.js 学習メモ(公式ドキュメントの雑な抜粋)
これは何か
Nuxt.js の公式ドキュメントを読んだときのメモです。
気になったところ、後でもう一度確認が必要そうなもの、など。
Nuxt.js のバージョンは v1.4.2。
Nuxt.js ガイド
プロローグ
- サーバーサイドレンダリング
nuxt コマンドを実行すると開発サーバーが起動されます。このサーバーはホットリローディング及び vue-server-render を備えており、vue-server-render は自動的にアプリケーションをサーバーサイドレンダリングするよう設定されています。
nuxt --spa
- 静的ファイルの生成
- (必要になったら読む)
はじめる
インストール
ディレクトリ構造
- ディレクトリ
assets
,components
,layouts
,middleware
,pages
,plugins
,static
,store
,nuxt.config.js
,nuxt.config.js
,package.json
- エイリアス
~
or@
=> API: srcDir プロパティ~~
or@@
=> API: rootDir プロパティ
設定
- nuxt.config.js
- (必要になったら読む)
ルーティング
Nuxt.js は pages ディレクトリ内の Vue ファイルの木構造に沿って、自動的に vue-router の設定を生成します。
- 動的なルーティング
users/_id.vue
=>/users/:id?
,_slug
=>path: '/:slug',
user-id と名付けられたルートに :id? というパスがありますが、これはこの :id が必須ではないことを表します。もし必須にしたい場合は users/_id ディレクトリ内に index.vue ファイルを作成してください。
- ルーティングのパラメータのバリデーション
<script> export default { validate ({ params }) { // 数値でなければならない return /^\d+$/.test(params.id) } } </script>
- ネストされたルート
- 動的でネストされたルート
- (必要になったら読む)
- SPA フォールバック
- (必要になったら読む)
- トランジション
- (必要になったら読む)
- ミドルウェア
ビュー
- ドキュメント
HTML テンプレートを拡張するために、プロジェクトのルートディレクトリに app.html を作成します。
- レイアウト
- デフォルトレイアウト
layouts/default.vue
ファイルを追加することでメインレイアウトを拡張できます。必ず
コンポーネントを入れておくことを覚えておいてください。
- エラーページ
layouts/error.vue
ファイルを追加することでエラーページをカスタマイズできます。<nuxt/>
を含めてはならない
- カスタムレイアウト
- デフォルトレイアウト
- ページ
asyncData
,fetch
,head
,layout
,transition
,scrollToTop
,validate
,middleware
- HTML ヘッド
Nuxt.js は headers とアプリケーションの html attributes を更新するために vue-meta を使用しています。Nuxt.js はこれらのオプションで vue-meta を設定します:
Nuxt.jsでは、nuxt.config.js 内にデフォルトの タグを全て定義することができます。
非同期なデータ
Nuxt.js はコンポーネントのデータをセットする前に非同期の処理を行えるようにするために
asyncData
メソッドを追加しています。- asyncData メソッド
- promise, async/await, callback のどれか
asyncData の結果はコンポーネントのデータと マージされ ます
- コンテキスト
- context 内で利用できるキーの一覧
- 動的なルートデータへのアクセス
たとえば、動的ルートパラメータには、コンテキストオブジェクトを構成したファイルまたはフォルダの名前を使用してアクセスできます。 したがって、_slug.vue という名前のファイルを定義する場合、context.params.slug を介してアクセスできます。
- クエリの変化のリスニング
デフォルトでは、クエリストリングの変化で asyncData メソッドは呼ばれません。ページネーションコンポーネントのビルド時などにこの振る舞いを変更したい場合は、ページコンポーネントの watchQuery プロパティを見るパラメータを設定することができます。
- watchQuery プロパティ
- エラー処理
Nuxt.js は、context に error (パラメータ)メソッドを追加し、エラーページを表示するためにそれを呼び出すことができます。
params.statusCode は、サーバーサイドから適切なステータスコードを表示するためにも使用されます。
callback({ statusCode: 404, message: 'ページが見つかりません' })
- 第一引数を利用する
アセット
- Webpack で取り扱う
- Webpack で扱わない静的ファイル
プラグイン
Nuxt.js では js プラグインを定義することができ、それはルートの Vue.js アプリケーションがインスタンス化される前に実行されます。
- 外部パッケージの利用
こうすれば、バンドルファイルが膨れ上がることなく、どの場所にも import axios と書くことができます。
module.exports = { build: { vendor: ['axios'] } }
モジュール
モジュールは、Nuxt 起動時に順番に呼び出される、シンプルな関数です
Webpack の Tapable に基づいた Nuxt のモジュール設計のおかげで、モジュールは例えばビルドの初期化のような特定のエントリーポイントに、フックを簡単に登録できるのです。
- 基本的なモジュールを書く
- 非同期モジュール
- 共通のスニペット
- (後で読む)
- 特定のフックでタスクを実行する
単に Nuxt の初期化処理時だけではなく、特定の条件下でのみ、モジュールにある処理を実行させたいこともあるでしょう。
Vuex ストア
Nuxt.js は store ディレクトリを探索し存在するときには以下を実行します: 1. Vuex をインポートします 2. vuex モジュールを vendor のバンドルファイルに追加します 3. store オプションをルートの Vue インスタンスに追加し
- クラシックモード
ストアをクラシックモードで有効にするには store/index.js ファイルを作成し、ストアインスタンスをエクスポートします:
- モジュールモード
このオプションを使いたいときは、ストアインスタンスの代わりに、store/index.js 内でステート、ミューテーション、アクションをエクスポートします:
fetch メソッドは、ページがレンダリングされる前に、データをストアに入れるために使われます。
nuxtServerInit
アクションnuxtServerInit というアクションがストア内に定義されているときは、Nuxt.js はそれをコンテキストとともに呼び出します(ただしサーバーサイドに限ります)。サーバーサイドからクライアントサイドに直接渡したいデータがあるときに便利です。
コンテキストは、asyncDataや fetch メソッドと同様に nuxtServerInit に第二引数として渡されます。
- Vuex Strict モード
Strict モードは dev モードではデフォルトで有効化されており、production モードでは無効化されています。strict モードを dev で無効化するには、以下の例を参照してください。
コマンド
- コマンド一覧
nuxt
- 開発サーバー起動
nuxt build
- Webpack でビルド、ミニファイ
nuxt start
- プロダクションモードでサーバーを起動(nuxt build 後に実行)
nuxt generate
- 静的ファイルのホスティングに用いる
- プロダクションのデプロイ
Nuxt.js では 3つのモードからアプリケーションのデプロイを選択できます。サーバレンダリング、SPA、そして静的生成です。
- (必要になったら読む)
開発ツール
- エンドツーエンドテスト
- (必要になったら読む)
- ESLint と Prettier
- (必要になったら読む)
その他
Vuex 学習メモ
これは何か
Vuex の公式ドキュメントを読んだときのメモです。
気になったところ、後でもう一度確認が必要そうなもの、など。
Vuex のバージョンは v3.0.1。
Vuex ガイド
Vuex とは何か?
他のパターンと異なるのは、Vuex は効率的な更新のために、Vue.js の粒度の細かいリアクティビティシステムを利用するよう特別に調整して実装されたライブラリだということです。
Vuex 入門
- Vuex ストアはリアクティブです。Vue コンポーネントがストアから状態を取り出すとき、もしストアの状態が変化したら、ストアはリアクティブかつ効率的に更新を行います。
- ストアの状態を直接変更することはできません。明示的にミューテーションをコミットすることによってのみ、ストアの状態を変更します。これによって、全ての状態の変更について追跡可能な記録を残すことが保証され、ツールでのアプリケーションの動作の理解を助けます。
store.state.count を直接変更する代わりにミューテーションをコミットする理由は、状態の変更を明確に追跡したいからです。このシンプルな規約は、あなたのコードの意図をさらに明確にし、コードを読んだ時にアプリケーションの状態の変更について、論理的に考えることができるようにします。加えて、私たちに全ての変更のログを取ったり、状態のスナップショットを取ったり、タイムトラベルデバッグを行うようなツールを実装する余地を与えてくれます。
コアコンセプト
ステート
Vuex は 単一ステートツリー (single state tree) を使います。
mapState
ヘルパー
ゲッター
算出プロパティと同様に、ゲッターの結果はその依存関係に基づいて計算され、依存関係の一部が変更されたときにのみ再評価されます。
- プロパティスタイルアクセス
ゲッターは
store.getters
オブジェクトから取り出され、プロパティとしてアクセスすることができます:ゲッターは第2引数として他のゲッターを受け取ります:
プロパティとしてアクセスされるゲッターは Vue のリアクティブシステムの一部としてキャッシュされるという点に留意してください。
- メソッドスタイルアクセス
関数を返り値にすることで、ゲッターに引数を渡すこともできます。
メソッドによってアクセスされるゲッターは呼び出す度に実行され、その結果はキャッシュされない点に留意してください。
mapGetters
ヘルパー
ミューテーション
store.commit('increment')
store.commit('increment', 10)
store.commit({ type: 'increment', amount: 10 })
- Vue のリアクティブなルールに則ったミューテーション
- あらかじめ全ての必要なフィールドによって、ストアの初期状態を初期化することが望ましいです
- 新しいプロパティをオブジェクトに追加するとき、以下のいずれかが必要です:
- Vue.set(obj, 'newProp', 123) を使用する。あるいは
- 全く新しいオブジェクトで既存のオブジェクトを置き換える。
- ミューテーション・タイプに定数を使用する
- ミューテーションは同期的でなければならない
アクション
アクションは、状態を変更するのではなく、ミューテーションをコミットします。 アクションは任意の非同期処理を含むことができます。
したがって
context.commit
を呼び出すことでミューテーションをコミットできます。あるいはcontext.state
やcontext.getters
で、状態やゲッターにアクセスできます。なぜコンテキストオブジェクトがストアインスタンスそのものではないのかは、後ほどモジュールで説明します。
mapActions
ヘルパー- vuex-101-promise - StackBlitz
- Promise, async / await を利用したデモ
実践的な例として、ショッピングカートをチェックアウトするアクション
actions: { checkout ({ commit, state }, products) { // 現在のカート内の商品を保存する const savedCartItems = [...state.cart.added] // チェックアウトのリクエストを送信し、楽観的にカート内をクリアする commit(types.CHECKOUT_REQUEST) // shop API は成功時のコールバックと失敗時のコールバックを受け取る shop.buyProducts( products, // 成功時の処理 () => commit(types.CHECKOUT_SUCCESS), // 失敗時の処理 () => commit(types.CHECKOUT_FAILURE, savedCartItems) ) } }
モジュール
- vuex-101-module - StackBlitz
- デモ
context.state
,context.rootState
- 名前空間
デフォルトでは、モジュール内部のアクション、ミューテーション、そしてゲッターはグローバル名前空間の元で登録されます - これにより、複数のモジュールが同じミューテーション/アクションタイプに反応することができます。
namespaced: true
- 名前空間付きモジュールでのグローバルアセットへのアクセス
アクションをディスパッチするか、グローバル名前空間にミューテーションをコミットするには、dispatch と commit の3番目の引数として {root: true} を渡します。
- 名前空間によるバインディングヘルパー
...mapState({ a: state => state.some.nested.module.a,
...mapState('some/nested/module', {
,const { mapState, mapActions } = createNamespacedHelpers('some/nested/module')
- 動的にモジュールを登録する
store.registerModule
- (あとで読む)
- モジュールの再利用
- (あとで読む)
アプリケーションの構造
プラグイン
- vuex-101-plugins - StackBlitz
- デモ
Vuex ストア は、各ミューテーションへのフックを公開する plugins オプションを受け付けます。 Vuex プラグインは、単一の引数としてストアを受けつけるただの関数です:
- プラグイン内でのミューテーションのコミット
ミューテーションのコミットによるストアとデータソースの同期をプラグインで実現できます。
- (あとで読む)
- 状態のスナップショットを撮る
- (あとで読む)
- ビルトインロガープラグイン
createLogger
厳格モード
strict: true
本番環境で厳格モードを有効にしてデプロイしてはいけません! 厳格モードでは不適切なミューテーションを検出するためにステートツリーに対して深い監視を実行します。パフォーマンスコストを回避するために本番環境では無効にしてください。
フォームの扱い
- vuex-101-forms - StackBlitz
- デモ
厳格モードでは、この変更は明示的に Vuex のミューテーションハンドラ内部で処理されていないため、エラーを投げます。
テスト
- (あとで読む)
ホットリローディング
- (あとで読む)
Vue.js 学習メモ
これは何か
Vue.js の公式ドキュメントを読んだときのメモです。
気になったところ、後でもう一度確認が必要そうなもの、など。
Vue.js のバージョンは v2.5.17。
Vue.js ガイド
基本的な使い方
- テンプレート構文
- 算出プロパティとウォッチャ
- クラスとスタイルのバインディング
- 条件付きレンダリング
- vue-101-conditional - StackBlitz
- デモ
- key による再利用可能な要素の制御
しかしこれは必ずしも望ましいことでないかもしれないので、Vue は”この 2 つの要素は完全に別個のもので、再利用しないでください” と伝える方法を提供します。それは、一意の値を持つ key 属性を追加するだけです:
v-show は
<template>
要素をサポートせず、v-else とも連動しないということに注意してください。ー般的に、v-if はより高い切り替えコストを持っているのに対して、 v-show はより高い初期描画コストを持っています。
- vue-101-conditional - StackBlitz
- リストレンダリング
- vue-101-list - StackBlitz
- デモ
繰り返される DOM の内容が単純な場合や、性能向上のために標準の動作に意図的に頼る場合を除いて、可能なときはいつでも v-for に key を与えることが推奨されます。
- 配列の変化を検出
- オブジェクトの変更検出の注意
- フィルタ/ソートされた結果の表示
- 範囲付き v-for
<template>
での v-for- v-for と v-if
それらが同じノードに存在するとき、 v-for は v-if よりも高い優先度を持ちます。これは v-if がループの各繰り返しで実行されることを意味します。
- コンポーネントと v-for
- vue-101-list - StackBlitz
- イベントハンドリング
- vue-101-events - StackBlitz
- デモ
- イベント修飾子
.stop
,.prevent
,.capture
,.self
,.once
,.passive
.passive 修飾子は特にモバイルでのパフォーマンスを改善するのに有用です。
- passive なリスナー を用いたスクロールのパフォーマンスの改善
- キー修飾子
- システム修飾子キー
.exact 修飾子
- マウスボタンの修飾子
ViewModel が消去されるときに、すべてのイベントリスナーは自動で削除されます。手動でそれらの消去をおこなうことを気にする必要はありません。
- vue-101-events - StackBlitz
- フォーム入力バインディング
- コンポーネントの基本
Components In-Depth
- vue-101-components-in-depth - StackBlitz
- デモ
- コンポーネントの登録
- 基底コンポーネントの自動グローバル登録
require.context
- (後でよむ)
- 基底コンポーネントの自動グローバル登録
- プロパティ
多くの場合、プロパティの値を変化させたい場合には、 2 つのケースがあります:
- プロパティのバリデーション
- プロパティでない属性
- カスタムイベント
- スロット
- 動的 & 非同期コンポーネント
動的コンポーネントの再生成は通常は便利な挙動です。しかし、このケースでは最初に作成されたタブコンポーネントのインスタンスがキャッシュされるのが好ましいでしょう。この解決策として、動的コンポーネントを
<keep-alive>
要素でラップすることができます:- 非同期コンポーネント
1つの推奨される方法は、 非同期コンポーネントと Webpack の code-splitting の機能 を使用することです:
- ロード状態のハンドリング
- 特別な問題に対処する
- 要素 & コンポーネントへのアクセス
$root
,$parent
,ref
属性- 依存性の注入
provide
,inject
- https://jp.vuejs.org/v2/api/#provide-inject
- プログラム的なイベントリスナー
$on
,$once
,$off
- 循環参照
- 代替テンプレート定義
inline-template
text/x-template
- 更新をコントロールする
$forceUpdate
$v-once
- 要素 & コンポーネントへのアクセス
トランジションとアニメーション
- (必要になったら)
再利用と構成
- vue-101-reusability-and-composition - CodeSandbox
- デモ
- ミックスイン
コンポーネントがミックスインを使用するとき、ミックスインの全てのオプションはコンポーネント自身のオプションに”混ぜられ”ます。
- オプションのマージ
- グローバルミックスイン
多くのケースでは、上記の例のような、カスタムオプションを処理するようなものに使用すべきです。また、重複適用を避けるため プラグイン としてそれらを作成することをお勧めします。
- カスタムオプションのマージストラテジ
カスタムオプションがマージされるとき、それらは単純に既存の値を上書きするデフォルトのストラテジを使用します。カスタムロジックを使用してカスタムオプションをマージする場合、Vue.config.optionMergeStrategies をアタッチする必要があります:
- カスタムディレクティブ
- 基本
- フック関数
bind
,inserted
,update
,componentUpdated
,unbind
- ディレクティブフック引数
el
,binding
,vnode
,oldVnode
el を除いて、これらの全てのプロパティは読み込みのみ (read-only) で変更しないものとして扱わなくてはいけません。フックを超えてデータを共有する必要がある場合は, 要素の
dataset
を通じて行うことが推奨されています。
- フック関数
- 関数による省略記法
- オブジェクトリテラル
あなたのディレクティブが複数の値を必要ならば、JavaScript オブジェクトリテラルも渡すことができます。
- 基本
- 描画関数とJSX
- ノード、ツリー、および仮想 DOM
全ての要素はノードです。全てのテキストはノードです。コメントさえもノードです!
- 仮想 DOM
Vue は、実際の DOM に加える必要がある変更を追跡する仮想 DOM を構築することで、これを達成します。
return createElement('h1', this.blogTitle)
- createElement 引数, 素の JavaScript によるテンプレートの書き換え, JSX, 関数型コンポーネント, テンプレートのコンパイル
- (後で読む)
- ノード、ツリー、および仮想 DOM
- プラグイン
- フィルター
ツール
- (後でよむ)
スケールアップ
内部
移行
- (必要になったら)
その他
- (必要になったら)
その他のドキュメントへのリンク
- スタイルガイド — Vue.js
- はじめに — Vue.js
- クックブック
(WIP) Learn Angular
これは何か
- Angular を習得するために必要な情報をまとめてみようとしたもの
- Angular の 公式ドキュメントからいくつか抜粋したもの + α です。Qita や 「Angular デベロッパーズガイド」で代替するのも :ok_hand:
- (もとは会社用資料です。多少業務内容の影響をうけているので、優先度が少し一般的ではなかったりするかもしれません。)
始める前に
- 情報源
- Online IDE
最初に
- チュートリアル
- アーキテクチャ
- https://angular.io/guide/architecture
- Architecture Overview, Intro to Modules, Intro to Components, Intro to Services and DI
- コンポーネント、モジュール、サービス、DIについてざっくりと説明されている
- https://angular.io/guide/architecture
Angular4さわるhttp://ryotah.hatenablog.com/entry/2017/06/21/174019自分が最初にさわった時のメモCLIについての基礎、Angular CLIを利用したチュートリアルの一覧など
- スタイルガイド
- https://angular.io/guide/styleguide
- (後回しにしてもいいかも)
- これを読んでおくと現アプリの構成もわかりやすい
- Angular CLI を利用すると大体この通りになる(命名規則、ディレクトリ構造など)
- NgModules
- https://angular.io/guide/ngmodules
- (これも後回しにしてもいいかも。少なくとも FAQs は僕も読んでい
基礎
実際のコードを書く前に一通り目を通したほうが、つまずきにくくなりそうなドキュメント。
- Components & Templates
基礎(適宜)
実際のコードを書きながら気になった時に適宜読むといいかも。
- angular/angular-cli: CLI tool for Angular
- Components & Templates
- (読んでないドキュメントを)
- Forms
- https://angular.io/guide/user-input
- https://angular.io/guide/forms
- Template-driven Forms
- 個人的にまとめたもの
http://ryotah.hatenablog.com/entry/2017/08/31/210437- angular-form-basic-template-driven - StackBlitz
- Template-drivenサンプル(基本的なバリデーション、入れ子構造フォーム)
- https://angular.io/guide/reactive-forms
- Reactive Forms
- 個人的にまとめたもの
http://ryotah.hatenablog.com/entry/2017/08/31/210723- angular-form-basic-reactive - StackBlitz
- Reactiveサンプル(基本的なバリデーション、入れ子構造フォーム)
- Routing & Navigation
- HttpClient
- DI
- Testing
応用
- 日本語訳:Angular 2 Change Detection Explained
- データの変更をどのように View に反映しているか、それを踏まえてパフォーマンスのためにどうしたらいいか、など
- 応用的な View 操作(動的に View をいじる、など)
- (あとで書く。必要なタイミングでまだ空だったら声かけてください)
- Form
- FormControl Freaks: Redux Edition - Daniel Figueiredo Caetano & Renee Vrantsidis - YouTube
- Reduxを使ったフォームの構築
- Template-driven Formsを利用
- ngrxでも考えは一緒
- validはデータとして持つのではなく、selectorで
- formcontrol-freaks/chapter.md at master · renvrant/formcontrol-freaks · GitHub
- スライド版
- Reduxを使ったフォームの構築
- AngularのForm(応用) - ryotah’s blog
- AngularConnect 2017のセッション「Angular Forms - Advanced Topics」を自分用にまとめたものです。
- (実際のセッションの、動画とスライドへのリンクもあります)
- FormControl Freaks: Redux Edition - Daniel Figueiredo Caetano & Renee Vrantsidis - YouTube
RxJS
- https://angular.io/guide/observables
- 以前はなかった
- いい記事(だと思う。しっかり読んではいない)
- 【翻訳】あなたが求めていたリアクティブプログラミング入門 - ninjinkun’s diary
- 有名な記事(だと思う)
- 最初にこれを読んだ
- Introduction · learn-rxjs
- リファレンス的に
- RxMarbles: Interactive diagrams of Rx Observables
- たまにみると面白い
- RxJSのconcatMap, mergeMap, switchMapの違いを理解する(中級者向け)
ngrx
- オフィシャル
- A Comprehensive Introduction to @ngrx/store - Companion to Egghead.io Series · GitHub
- RxJSを利用して簡易的な ngrx をつくる
- 良記事
- Using NgRx 4 to Manage State in Angular Applications
- NgRxを使った状態管理について
- 良記事
- 個人的にまとめたもの
Using NgRx 4 to Manage State in Angular Applicationsのメモ #angular #rx #ngrx #redux · GitHub
- NgRx: Patterns and Techniques – nrwl
- アクションとエフェクトの実践的な使い方、分類について
- 良記事
- 個人的にまとめたもの
NgRx: Patterns and Techniquesのメモ #angular #ngrx #redux #rx · GitHub
TypeScript
VS Code エクステンション
for Angular
for TypeScript
for Lint and Code format
- TSLint - Visual Studio Marketplace
- Prettier - Code formatter - Visual Studio Marketplace
- EditorConfig for VS Code - Visual Studio Marketplace
Util
(WIP) Angular Testing メモ
- これは何か
- 前提知識
- Service Tests
- Component Test Basics
- Component Test Scenarios
- Component binding
- Component with external files
- Component with a dependency
- Component with async service
- Component marble tests
- Component with inputs and outputs
- Component inside a test host
- Routing component
- Routed components
- Nested component tests
- Components with RouterLink
- Use a page object
- Setup with module imports
- Override component providers
これは何か
Angular の公式ドキュメント(Angular - Testing)とテストに関する前提知識(用語、Jasmine使い方など)をまとめようとしたもの。
前提知識
Jasmine
spyOn(オブジェクト, 'メソッド名') // 本来の実装も実行 spyOn(obj, 'method').and.callThrough(); // 戻り値を変更 spyOn(obj, 'method').and.returnValue('stub'); // 処理を任意の関数に置き換える spyOn(obj, 'method').and.callFake(...) calls.count(); calls.first(); calls.mostRecent(); // 裸のスパイ関数 var spyFunction = jasmine.createSpy(); // 裸のスパイオブジェクト var spyObj = jasmine.createSpyObj('spyName', ['hoge', 'fuga', 'piyo']);
- Jasmine を使用したユニットテストで便利な「fdescribe・fit」「xdescribe・xit」 - Corredor
fdescribe
,fit
,xdescribe
,xit
用語
- テストダブル - Wikipedia
テストダブル (Test Double) とは、ソフトウェアテストにおいて、テスト対象が依存しているコンポーネントを置き換える代用品のこと。ダブルは代役、影武者を意味する
- テストスタブ (テスト対象に「間接的な入力」を提供するために使う。)
- テストスパイ (テスト対象からの「間接的な出力」を検証するために使う。出力を記録しておくことで、テストコードの実行後に、値を取り出して検証できる。)
- モックオブジェクト (テスト対象からの「間接的な出力」を検証するために使う。テストコードの実行前に、あらかじめ期待する 結果を設定しておく。検証はオブジェクト内部で行われる。)
- フェイクオブジェクト (実際のオブジェクトに近い働きをするが、より単純な実装を使う。例として、実際のデータベースを置き換えるインメモリデータベースが挙げられる。)
- ダミーオブジェクト (テスト対象のメソッドがパラメータを必要としているが、そのパラメータが利用されない場合に渡すオブジェクト。
-
スタブ (stub) とは、コンピュータプログラムのモジュールをテストする際、そのモジュールが呼び出す下位モジュールの代わりに用いる代用品のこと。
- FakeとMockとStubの違い - kkamegawa’s weblog
Stub: Prevent the dependency from obstructing the method-under-test and torespond in a way that helps it proceed through its logical steps.
Mock: Allow the test code to verify that the code-under-test’s interaction with the dependency is proper, valid, and expected.
Service Tests
https://angular.io/guide/testing#service-tests
コンポーネントのテストは複雑になりがちなので、まずはサービスのテストから。
依存がない場合
service = new ValueService();
他のサービスに依存している場合
// 1. 実際のサービスをInject masterService = new MasterService(new ValueService()); // 2. FakeサービスをInject masterService = new MasterService(new FakeValueService()); // 3. Fake objectをInject const fake = { getValue: () => 'fake value' }; masterService = new MasterService(fake as ValueService); // 4. Spy serviceをInject const valueServiceSpy = jasmine.createSpyObj('ValueService', ['getValue']); const stubValue = 'stub value'; valueServiceSpy.getValue.and.returnValue(stubValue); masterService = new MasterService(valueServiceSpy);
実際のアプリケーションと同じように Angular の DI を利用する必要があるのであれば TestBed
を利用。
https://angular.io/guide/testing#angular-testbed
let service: ValueService; beforeEach(() => { TestBed.configureTestingModule({ providers: [ValueService] }); }); it('should use ValueService', () => { service = TestBed.get(ValueService); expect(service.getValue()).toBe('real value'); });
Testing without beforeEach()
https://angular.io/guide/testing#testing-without-beforeeach
beforeEach
を必ずしも利用する必要はない
function setup() { const valueServiceSpy = jasmine.createSpyObj('ValueService', ['getValue']); const stubValue = 'stub value'; const masterService = new MasterService(valueServiceSpy); valueServiceSpy.getValue.and.returnValue(stubValue); return { masterService, stubValue, valueServiceSpy }; } it('#getValue should return stubbed value from a spy', () => { const { masterService, stubValue, valueServiceSpy } = setup(); // ... });
Testing HTTP services
https://angular.io/guide/testing#testing-http-services
HttpClient
のSpyをつくる
beforeEach(() => { httpClientSpy = jasmine.createSpyObj('HttpClient', ['get']); TestBed.configureTestingModule({ providers: [ ApiService, { provide: HttpClient, useValue: httpClientSpy }, ], }); }); // or beforeEach(() => { httpClientSpy = jasmine.createSpyObj('HttpClient', ['get']); service = new ApiService(<any>httpClientSpy); });
// 正常系 httpClientSpy.get.and.returnValue(asyncData(json)); // エラー系 httpClientSpy.get.and.returnValue(asyncError(errorResponse));
asyncData
,asyncError
Component Test Basics
https://angular.io/guide/testing#component-test-basics
大抵の場合、DOM関与なしでコンポーネントクラスだけをテストした方が、動作の多くを簡単に検証できる。実際、単純なコンポーネントならnew FooComponent()
のようにインスタンスを生成するだけでテストができる。
// component export class DashboardHeroComponent { @Input() hero: Hero; @Output() selected = new EventEmitter<Hero>(); click() { this.selected.emit(this.hero); } } // testing it('raises the selected event when clicked', () => { const comp = new DashboardHeroComponent(); const hero: Hero = { id: 42, name: 'Test' }; comp.hero = hero; comp.selected.subscribe(selectedHero => expect(selectedHero).toBe(hero)); comp.click(); });
依存関係がある場合はTestBedを利用する。
beforeEach(() => { TestBed.configureTestingModule({ // provide the component-under-test and dependent service providers: [ WelcomeComponent, { provide: UserService, useClass: MockUserService } ] }); // inject both the component and the dependent service. comp = TestBed.get(WelcomeComponent); userService = TestBed.get(UserService); }); // ... // 手動で ngOnInit it('should welcome logged in user after Angular calls ngOnInit', () => { comp.ngOnInit(); expect(comp.welcome).toContain(userService.user.name); });
Component DOM testing
https://angular.io/guide/testing#component-dom-testing
DOMのテストも必要な場合。コンポーネントを生成してテストする必要がある。
コンポーネントを生成するテストコード
describe('BannerComponent (minimal)', () => { it('should create', () => { TestBed.configureTestingModule({ declarations: [ BannerComponent ] }); const fixture = TestBed.createComponent(BannerComponent); const component = fixture.componentInstance; expect(component).toBeDefined(); }); });
- createComponent
- ComponentFixture
- 作成されたコンポーネントおよび対応する要素と対話するためのハーネス
- https://angular.io/api/core/testing/ComponentFixture
nativeElement
https://angular.io/guide/testing#nativeelement
DOMのAPIを利用したい場合。
const bannerElement: HTMLElement = fixture.nativeElement; const p = bannerElement.querySelector('p');
DebugElement
https://angular.io/guide/testing#debugelement
The DebugElement offers query methods that work for all supported platforms.
Component Test Scenarios
https://angular.io/guide/testing#component-test-scenarios
Component binding
https://angular.io/guide/testing#component-binding
- テンプレートにデータを反映させるには
detectChanges()
を実行させる必要がある - 自動で
fixture.detectChanges()
させる方法もある- https://angular.io/guide/testing#automatic-change-detection
{ provide: ComponentFixtureAutoDetect, useValue: true }
- https://angular.io/guide/testing#automatic-change-detection
Change an input value with dispatchEvent()
input elementの場合はdispatchEvent
も実行させる。
const nameInput: HTMLInputElement = hostElement.querySelector('input'); // ... // simulate user entering new name into the input box nameInput.value = inputName; // dispatch a DOM event so that Angular learns of input value change. nameInput.dispatchEvent(newEvent('input')); // Tell Angular to update the display binding through the title pipe fixture.detectChanges();
Component with external files
ng testなら不要
Component with a dependency
https://angular.io/guide/testing#component-with-a-dependency
依存関係があるコンポーネント
// providers: [ UserService ] // NO! Don't provide the real service! // Provide a test-double instead
Provide service test doubles
https://angular.io/guide/testing#provide-service-test-doubles
A component-under-test doesn't have to be injected with real services. In fact, it is usually better if they are test doubles (stubs, fakes, spies, or mocks).
let userServiceStub: Partial<UserService>; userServiceStub = { isLoggedIn: true, user: { name: 'Test User'} };
- Get injected services
// UserService actually injected into the component userService = fixture.debugElement.injector.get(UserService); // UserService from the root injector userService = TestBed.get(UserService);
- TestBed.get()
- Always get the service from an injector
- Final setup and tests
Component with async service
https://angular.io/guide/testing#component-with-async-service
Testing with a spy
const twainService = jasmine.createSpyObj('TwainService', ['getQuote']); // Make the spy return a synchronous Observable with the test data getQuoteSpy = twainService.getQuote.and.returnValue( of(testQuote) );
Synchronous tests
https://angular.io/guide/testing#synchronous-tests
Async test with fakeAsync()
https://angular.io/guide/testing#async-test-with-fakeasync
fakeAsync(() => { /* test body */ })
- Angular - fakeAsync
The tick() function
Calling tick() simulates the passage of time until all pending asynchronous activities finish. In this case, it waits for the error handler's setTimeout();
Async observables
Async observable helpers
RxJS defer() returns an observable. It takes a factory function that returns either a promise or an observable. When something subscribes to defer's observable, it adds the subscriber to a new observable created with that factory.
RxJS defer() transform the Promise.resolve() into a new observable that, like HttpClient, emits once and completes. Subscribers will be unsubscribed after they receive the data value.
以下を用意
import { defer } from 'rxjs/observable/defer'; /** Create async observable that emits-once and completes * after a JS engine turn */ export function asyncData<T>(data: T) { return defer(() => Promise.resolve(data)); } /** Create async observable error that errors * after a JS engine turn */ export function asyncError<T>(errorObject: T) { return defer(() => Promise.reject(errorObject)); }
More async tests
Async test with async()
https://angular.io/guide/testing#async-test-with-async
fakeではないasync
whenStable
The test must wait for the getQuote() observable to emit the next quote. Instead of calling tick(), it calls fixture.whenStable().
tickの代わりにfixture.whenStableを実行する必要がある
Jasmine done()
https://angular.io/guide/testing#jasmine-done
もちろん一般的な done も使える
But it is occasionally necessary. For example, you can't call async or fakeAsync when testing code that involves the intervalTimer() or the RxJS delay() operator.
Component marble tests
https://angular.io/guide/testing#component-marble-tests
Component with inputs and outputs
https://angular.io/guide/testing#component-with-inputs-and-outputs
@Input に対応
// ... comp = fixture.componentInstance; // ... // mock the hero supplied by the parent component expectedHero = { id: 42, name: 'Test Name' }; // simulate the parent setting the input property with that hero comp.hero = expectedHero; // trigger initial data binding fixture.detectChanges();
Clicking
https://angular.io/guide/testing#clicking
- click() helper
- 対象が
HTMLElement
ならclick()
メソッドを利用(通常はこっちでおk) HTMLElement
以外ならちょっと工夫が必要
- 対象が
Component inside a test host
https://angular.io/guide/testing#component-inside-a-test-host
Component with inputs and outputs
と違うやり方。ホストのコンポーネントから操作する。TestHostComponent
を作ってその中で制御
Routing component
https://angular.io/guide/testing#routing-component
- Routerをもっているコンポーネントのテスト
- 「Routerのテスト」をする必要はないのでspyをつくる
const routerSpy = jasmine.createSpyObj('Router', ['navigateByUrl']); // ... const spy = router.navigateByUrl as jasmine.Spy; const navArgs = spy.calls.first().args[0];
Routed components
https://angular.io/guide/testing#routed-components
- Router Navigation の遷移先
- ActivatedRoute をどうテストするか
paramMap returns an Observable that can emit more than one value during a test. You need the router helper function, convertToParamMap(), to create a ParamMap. Other routed components tests need a test double for ActivatedRoute.
Nested component tests
https://angular.io/guide/testing#nested-component-tests
不要なコンポーネントをどう扱うか。
Stubbing unneeded components
@Component({selector: 'app-banner', template: ''}) class BannerStubComponent {} TestBed.configureTestingModule({ declarations: [ AppComponent, BannerStubComponent ] });
NO_ERRORS_SCHEMA
tells the Angular compiler to ignore unrecognized elements and attributes.
Use both techniques together
実際には両方のテクニックを利用するはず
Components with RouterLink
https://angular.io/guide/testing#components-with-routerlink
RouterLinkのスタブを作成
@Directive({ selector: '[routerLink]', host: { '(click)': 'onClick()' } }) export class RouterLinkDirectiveStub { @Input('routerLink') linkParams: any; navigatedTo: any = null; onClick() { this.navigatedTo = this.linkParams; } }
By.directive and injected directives
beforeEach(() => { fixture.detectChanges(); // trigger initial data binding // find DebugElements with an attached RouterLinkStubDirective linkDes = fixture.debugElement .queryAll(By.directive(RouterLinkDirectiveStub)); // get attached link directive instances // using each DebugElement's injector routerLinks = linkDes.map(de => de.injector.get(RouterLinkDirectiveStub)); });
Use a page object
Setup with module imports
https://angular.io/guide/testing#setup-with-module-imports
モジュールインポートについて解説
One approach is to configure the testing module from the individual pieces as in this example:
- Import a shared module でも可能
- 実は自身を含む Feature module を読み込んでもおk
Override component providers
https://angular.io/guide/testing#override-component-providers
どうやってコンポーネントのproviders
で登録されたサービスをおきかえるか
(すでに中途半端な内容になっていますが、一旦ここまで。ここからさらに、DirectiveやPipeのテストの説明、よくある質問などがAngular - Testingには記述されています。)
6月メモ・リンク集
6月に調べたことのメモです。
Angular 関連
- (WIP) Angular Testing メモ - ryotah’s blog
- Angular のドキュメント + テストに関する前提知識(用語、Jasmine使い方など)をまとめようとしたもの
ngrx 関連
ngrx 実装メモ
store をつくるとき、どのような順で整理して実装しているか、についてのメモです。
例えば「ID順で表示される顧客一覧画面(テキストによる検索機能と追加読み込み機能)」をつくる場合は以下のように整理します。
- その機能 (Feature)で「できること」のリストアップ
- 顧客一覧表示
- 顧客検索
- 一覧を追加読み込み
- 検索条件を編集
- 別画面(別 Feature)に移動
- 必要な情報は何か
- 取得した顧客情報
- 顧客一覧のローディング状況
- 現在のページ番号
- 検索条件
- 各 state を作成
customer-list.reducre.ts
customerList: Customer[];
loading: boolean
page: number
query: string
- action になりそうなものをリストアップ
- 検索開始(検索条件が空の場合はID順で取得)
- 検索成功
- 検索失敗
- 追加読み込み開始
- 追加読み込み成功
- 追加読み込み失敗
- 検索条件を更新
- ページを離れる
- action と reducer の関係を考える
- 検索開始
- loading = true
- (その他のデータは一旦リセット)
- 検索成功
- customerList = 検索結果
- loading = false
- page = 1
- 検索失敗
- loading = false
- 追加読み込み開始
- loading = true
- 追加読み込み成功
- customerList += 検索結果
- loading = false
- page += 1
- 追加読み込み失敗
- loading = false
- ページを離れる
- データをリセット
- 検索開始
- 無駄が多そうな場合調整する
今回は「検索」と「追加読み込み」がほぼ同じなので共通化できそう- 検索開始 => effects で 「データリセット」+ 「追加読み込み開始」にスプリットする
- 参考:
結果
Actions
// store/actions/customer-lits.actions.ts export enum CustomerListActionTypes { LoadFirstList = '[CustomerList] Load First List', LoadList = '[CustomerList] Load List', LoadListFail = '[CustomerList] Load List Fail', LoadListSuccess = '[CustomerList] Load List Success', ResetList = '[CustomerList] Reset List', UpdateQueryFromParamMap = '[CustomerList] Update Query From Param Map', } export class LoadFirstList implements Action { readonly type = CustomerListActionTypes.LoadFirstList; } export class LoadList implements Action { readonly type = CustomerListActionTypes.LoadList; } export class LoadListFail implements Action { readonly type = CustomerListActionTypes.LoadListFail; } export class LoadListSuccess implements Action { readonly type = CustomerListActionTypes.LoadListSuccess; constructor(public payload: Customer[]) {} } export class ResetList implements Action { readonly type = CustomerListActionTypes.ResetList; } export class UpdateQueryFromParamMap implements Action { readonly type = CustomerListActionTypes.UpdateQueryFromParamMap; constructor(public payload: ParamMap) {} } export type CustomerListActions = | LoadFirstList | LoadList | LoadListFail | LoadListSuccess | ResetList | UpdateQueryFromParamMap;
Reducers
// store/reducers/customer-lits.reducer.ts export interface State { customerList: Customer[]; loading: boolean; page: number; query: string; } const initialState: State = { customerList: [], loading: false, page: 0, query: '', }; export function reducer( state = initialState, action: CustomerListActions ): State { switch (action.type) { case CustomerListActionTypes.LoadList: return { ...state, loading: true }; case CustomerListActionTypes.LoadListFail: return { ...state, loading: false }; case CustomerListActionTypes.LoadListSuccess: return { ...state, customerList: [...state.customerList, ...action.payload], loading: false, page: state.page + 1, }; case CustomerListActionTypes.ResetList: return initialState; case CustomerListActionTypes.UpdateQueryFromParamMap: return { ...state, query: action.payload.get('query') || '' }; default: return state; } } export const getCustomerList = state => state.customerList; export const getLoading = state => state.loading; export const getPage = state => state.page; export const getQuery = state => state.query;
Effects
// store/effects/customer-lits.effects.ts @Injectable() export class CustomerListEffects { @Effect() loadFirst$ = this.actions$.pipe( ofType<LoadFirstList>(CustomerActionTypes.LoadFirstList), mergeMap(action => [new ResetList(), new LoadList()]) ); @Effect() load$ = this.actions$.pipe( ofType<LoadList>(CustomerActionTypes.LoadList), withLatestFrom( this.store.select(fromStore.getQuery), this.store.select(fromStore.getPage) ), switchMap(([action, query, page]) => this.service.getList(query, page + 1).pipe( map(customerList => new LoadListSuccess(customerList)), catchError(error => of(new LoadListFail())) ) ) ); constructor( private actions$: Actions, private service: CustomerListService, private store: Store<fromStore.State> ) {} }
Container
// コンテナコンポーネント export class CustomerListContainer { constructor( private route: ActivatedRoute, private router: Router, protected store: Store<fromStore.State> ) { route.paramMap.pipe(takeUntil(this.unsubscribe)).subscribe(queries => { this.store.dispatch(new UpdateQueryFromParamMap(queries)); this.store.dispatch(new LoadFirstList()); }); // ... }
CSS 関連
「flex-basis」と「width」の違いは何でしょうか? 「flex-basis」は、flexコンテナの主軸の方向に相当するということです。
…
「flex-basis」は2つとも同じ値です。つまり、十分なスペースがあれば(コンテナは600pxと余白とパディングの余裕があります)、どちらも幅が300pxになります。
しかし、コンテナが大きくなるにつれて、「.square#one」(flex-grow :2)は2倍の速さで広がります。逆に、コンテナが縮むにつれて、「.square#two」(flex-shrink :2)は2倍の速さで縮小します。
…
「.square#one」が広がる時に、「.square#two」の2倍のサイズには広がりません。同様に縮む時も1/2のサイズに縮むのではありません。
それぞれの値で指定した「2 1」、「1 2」はサイズではありません。広がる時と縮む時の率です。
5月メモ・リンク集
5 月に調べたことのメモです。
Angular 関連
third party lib
- stories global scripts · angular/angular-cli Wiki · GitHub
- 例えばグローバルに jQuery を
- import はしてはいけない
Once you import a library via the scripts array, you should not import it via a import statement in your TypeScript code (e.g.
import * as $ from 'jquery';
). If you do that you'll end up with two different copies of the library: one imported as a global library, and one imported as a module.
- stories third party lib · angular/angular-cli Wiki · GitHub
tsconfig.app.json
のtypes
に追加
Testing in ngrx
// error expect(action).toEqual({ type: LOAD_PIZZAS }); // => Expected object to be a kind of Object, but was LoadPizzas({ type: '[Products] Load Pizzas' }) // もうちょい expect(action.type).toEqual(LOAD_PIZZAS); // good expect({ ...action }).toEqual({ type: LOAD_PIZZAS });
Reducers play a few key roles for us:
- Accept old state, and an action
- Respond to actions and compose/return new state
- Handle changes via immutable patterns ... To go with it, my reducer - which uses an entity pattern to flatten my data structure into object keys for performance:
Version 6
- Angular Update Guide
- ng update @angular/core fails · Issue #10621 · angular/angular-cli · GitHub
- Version 6 of Angular Now Available – Angular Blog
Rx 関連
- RxJS: Avoiding takeUntil Leaks – Angular In Depth
- takeUntil の利用(タイミング)に気をつけないと unsub されない場合があるので気をつけましょうという記事
- rx-avoiding-takeuntil-leaks - StackBlitz
- 自分で用意したデモ
catchError
- catch しても stream は終了する
- javascript - RxJs catch error and continue - Stack Overflow
- Continue RxJS Streams When Errors Occur: The Quest for Meatballs
If an error is thrown within an operator, the stream will always complete.
Rails 関連
double というメソッドを使うと、モックオブジェクトを作れます。 引数で渡す文字列は任意です。好きな文字列を渡しても構わないですし、省略することもできます。 ... RSpec では allow(モックオブジェクト).to receive(メソッド名) の形で、モックに呼び出し可能なメソッドを設定できます。
その他
- javascript - How can I spy on a getter property using jasmine? - Stack Overflow
- getter の場合どうする =>
spyOnProperty
- getter の場合どうする =>
- node.js - How to clone a javascript ES6 class instance - Stack Overflow
- instance のコピー
- Describing an Interface where unknown properties are of a specific type · Issue #20597 · Microsoft/TypeScript · GitHub
- 複数のオプショナルなプロパティが存在する interface を定義したいときに
type Foo = { name: string; age: number; } & { [prop: string]: string; };