10月メモ・リンク集

10月に調べたことのメモです。

Vue, Nuxt 関連

Migrate to Nuxt 2.0

参考:

Nuxt の options.ignore を利用して pages 内にコンテナやストアを配置

// nuxt.config.js
ignore: [
  // ...
  '**/pages/**/{containers,components}/*'
],

メリット:

  • 関係のあるコードを近くに配置する
  • pages, containers, store の関係をはっきりさせる
    • a page has a store and container(s)

変更前のディレクトリ構成:

.
├── containers
│    └── foo
│        ├── index.vue (<= コンテナコンポーネント)
│        ├── components
│        ├── store
│        └── utils
└── pages
    └── foo
        └── index.vue (<= ページコンポーネント)

変更後のディレクトリ構成:

.
└── pages
    └── foo
        ├── index.vue (<= ページコンポーネント)
        ├── components
        ├── containers
        │    └── index.vue (<= コンテナコンポーネント)
        ├── store
        └── utils

https://nuxtjs.org/api/configuration-ignore#the-ignore-property

ページ遷移時に権限確認

// middleware
// ...
// Get authorizations for matched routes (with children routes too)
const authorizationLevels = route.meta.map((meta) => {
  if (meta.auth && typeof meta.auth.authority !== 'undefined')
    return meta.auth.authority
  return 0
})
// Get highest authorization level
const highestAuthority = Math.max.apply(null, authorizationLevels)
// ...

不要なビルド処理を削減

  • API: build プロパティ - Nuxt.js
    • extend メソッドは一度はサーバーのバンドルのため、一度はクライアントのバンドルのため、つまり二度呼び出されます。

webpack.config.js (実装例):

module.exports = (config, options) => {
  // Add plugins
  config.plugins.push(
    // ...
  );
  // Do not run type checking twice. (This config is called twice,
  // one time for the server bundle, and one time for the client bundle.)
  if (options.isServer) {
    config.plugins.push(new ForkTSCheckerPlugin({
      vue: true
    }));
  }
  // ...
  return config;
};

文字コード正規表現

Unicode Property Escapes

補足:

文字コードの変換

['𩸽'.charCodeAt(0), '𩸽'.charCodeAt(1)].map(num => num.toString(16));
// => ["d867", "de3d"]

パスワード向け正規表現

例: 数字・アルファベット・記号を許容する

サンプル:

// 数字・小文字・大文字・記号を最低1文字含む, 8文字以上64文字以下
/^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[!-\/:-@[-`{-~])[!-~]{8,64}$/.test(value);

その他参考:

SVG 関連

SVG をアイコンフォントの代替にする

参考記事:

実装例 (Font Awesome):

<svg>
  <use xlink:href="fa-brands.svg#facebook"></use>
</svg>
<?xml version="1.0" encoding="UTF-8"?>
<!--
Font Awesome Free 5.5.0 by @fontawesome - https://fontawesome.com
License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
-->
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
  // ...
  <symbol id="facebook" viewBox="0 0 448 512">
    <title id="facebook-title">Facebook</title>
    <path d="..."></path>
  </symbol>
  // ...
</svg>

https://fontawesome.com/how-to-use/on-the-web/advanced/svg-sprites

書き出し (WIP):

  • svgo で不要タグなどを整理. 色情報消去(fill を空にする)
    • svgo *.svg --disable=removeTitle --config='{ "plugins": [ { "removeAttrs": { "attrs": "fill" } } ], "floatPrecision": xx }'
  • <symbol id="xxx" viewBox="0 0 xx xx"> を追加 (単体ファイルにする? or SVG Sprite にする?)
    • svgo で対応する or 別ツールを利用する

アウトプット例:

<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg"><title>title</title><symbol id="root" viewBox="0 0 24 24"><path d="..." fill-rule="evenodd"/></symbol></svg>

関連情報

SVG 基礎知識:

ツール・書き出し:

その他:

JIRA 芸

JIRA 使い始めました。

Form Events

書いた記事

その他

VeeValidate メモ

Vue のバリデーションライブラリ VeeValidate の使い方をまとめたもの。(VeeValidate 2.1.0-beta.11, Vue 2.5.17)

基本的な使い方

<input v-validate="'required|email'" type="email" name="email">
<span>{{ errors.first('email') }}</span>

バリデーションルールのシンタックス

<input v-validate="'required|min:6'" type="password" name="password">
<input v-validate="{ required: true, email: true }" type="email" name="email">

カスタムルール

// Function Form
Validator.extend('custom', (value, args) => {
  // Return a Boolean or a Promise that resolves to a boolean.
});

// Object Form
Validator.extend('custom', {
  getMessage(field, args) {
    // will be added to default locale messages.
    // Returns a message.
  },
  validate(value, args) {
    // Returns a Boolean or a Promise that resolves to a boolean.
  }
});
  • getMessage の引数 field はフィールド名 (String)
  • getMessage を利用すると現在のロケールの辞書に追加される。多言語対応をする場合は Localization API を利用する。

さらに詳しく

  • Custom Rules | VeeValidate
    • 引数を必要とするルール・他の入力フィールドと比較をするルール・Non-immediate なルール(immediate 修飾子が設定されていない場合、初期バリデーションをスキップする)・data プロパティについて、など

エラーメッセージ

エラーメッセージを変更/追加する

Validator.localize を利用して辞書 (rootDictionary) を登録する。

import { Validator } from 'vee-validate';

const dictionary = {
  en: {
    messages:{
      custom: () => 'Some English Message',
    }
  }
};
// Override and merge the dictionaries
Validator.localize(dictionary);

// メモ: メッセージを生成する関数の形式
// function rule (fieldName: string, params: any[], data?: any): string {
//   return `Some error message for the ${fieldName} field.`;
// }

Configuration | VeeValidate を利用して登録することも可。

入力フィールドの名前

エラーメッセージと同様に辞書を更新する。

const dictionary = {
  en: {
    attributes: {
      email: 'Email Address'
    }
  },
};

さらに詳しく

カスタムコンポーネントで使う

DEMO:

スコープ

f:id:ryotah:20181025225325p:plain

  • VeeValidate のバリデータスコープはコンポーネント毎につくられる
  • コンポーネントinject: ['$validator'] をすることで親のスコープを取得できる
  • メモリ消費量を抑えるため、バリデータスコープの自動生成をさせないことも可能方法
    • 設定変更に関しては Configuration | VeeValidate に詳細がある (inject: false)
    • (自動生成されなくなるので)自身のコンポーネントにバリデータスコープを生成したい場合は以下のように $_veeValidate を利用する
$_veeValidate: {
  validator: 'new'
}
  • 同じコンポーネント内(バリデータスコープ内)でフィールド名がコンフリクトする場合 data-vv-scope を利用してスコープを作成することができる(コンポーネント毎に生成されるバリデータスコープとは別物)
    • form タグに data-vv-scope を設定すると自動で入力フィールドにも同じスコープが設定される

DEMO:

参考:

  • Component Injections | VeeValidate
    • With SSR Frameworks like Nuxt, it is recommended to disable automatic injection since it may cause memory leaks due to all the validator instances being created for every component, which is not needed and may slow down your site.
  • Scopes | VeeValidate

VueI18n と一緒に使う

設定:

// ...
import en from '~/locales/en/validation';

Vue.use(VueI18n);

const i18n = new VueI18n();

Vue.use(VeeValidate, {
  // ...
  // i18nRootKey: 'validation', 'validation' is default
  i18n,
  dictionary: {
  en
  }
});

利用:

// 言語を変更する場合は `$i18n` から
this.$i18n.locale = 'ar';

言語ファイル:

import en from 'vee-validate/dist/locale/en';

// 最終的に VueI18n の言語データとして書き出される
export default {

  // VeeValidate の辞書形式
  attributes: {
    custom: 'Custom',
    // ...
  },
  messages: { 
    ...en.messages,
    custom: () => 'Some English Message',
  },
};

参考:

その他

Validator#validate と Validator#validateAll

validate に関しては Validator API | VeeValidate にまとまっているが validateAll に関しては情報が少ない。実際には、引数の形式は違うが機能としては同じものがいくつかある。validator.validateAll() より validator.validate() の方が実質の All だったりする。

// validate all fields.
validator.validate();

// validate a field that has a matching name with the provided selector.
validator.validate('field');

// validate a field within a scope.
validator.validate('scope.field');

// validate all fields within this scope.
// => 内部で validator.validateAll('scope') を呼ぶ
validator.validate('scope.*');

// validate all fields without a scope.
// => 内部で validator.validateAll() を呼ぶ
validator.validate('*');

DEMO:

Flags

DEMO:

export class Field {
  id: string;
  name: string;
  scope: string;
  flags: FieldFlags;
  isRequired: boolean;
  initial: boolean;
  el: any;
  value: any;
  rules: any;
  update(options:object): void;
}

export interface FieldFlags {
  untouched: boolean;
  touched: boolean;
  dirty: boolean;
  pristine: boolean;
  valid?: boolean;
  invalid?: boolean;
  validated: boolean;
  required: boolean;
  pending: boolean;
}

Validation Events

  • vee-validate はデフォルトで input イベントをリスニングしている
  • リスニング対象を変更したい場合、デフォルトの挙動を変更するか、フィールド毎に変更するか、2通りの方法がある
    • data-vv-validate-on

参考:

入力フィールドが動的に表示/生成される場合

Vue が要素を再利用しないように、ユニークな key を設定する必要がある。

参考:

Misc.

型定義ファイル

9月メモ・リンク集

9月に調べたことのメモです。

Vue 関連

ルーティング関連

i18n

言語データの整理方法メモ

{ 
  "common": { // 一般的な用語
     "action": {
       "close": "閉じる"
     },
     "label": {
       "calendar": "カレンダー"
     },
     // ...
  },
  "glossary": { // アプリケーション用語, Domain
    "task": {
      "label": {
        // ...
      },
    },
  },
  "components": { // Shared Components に対応
    "calendar": {
      // ...
    },
  },
  "modules": { // 各ルートに対応
    "todo": {
      // ...
    },
    "todos": {
      // ...
    }  
  },
}
  • Angular アプリでの例
  • 意味のグループと場所のグループ
    • common, glossary => 意味
    • components, modules => 場所 (View と強く結びつく)
  • common, glossary はどこの View からも呼び出しが可能。
    エイリアスが利用できるなら componentsmodules などから利用することも可。
  • 考慮したいこと
    • 翻訳依頼するときに翻訳者がコンテキストを理解しやすいか
    • 開発時に不要なメッセージが増えにくいか
      • 必要な言語を探しやすいか
      • 新しいメッセージをどこに追加するか迷わないか
    • 利用していないメッセージを発見しやすいか

その他

Vue.js vue-router Nuxt.js の各フック (など) がいつ実行されるかメモ

続編のようなものを書きました。 ryotah.hatenablog.com


GitHub - ryotah/vue-vue-router-nuxt-hooks

f:id:ryotah:20180924173906p:plain

ログに表示される内容:

  • [INFO] vue: [page] / [hook]
    • Vue.js のライフサイクルフック
    • created, beforeUpdate, destroyed, etc.
  • [INFO] vue-router: [page] / [guard]
    • vue-router のナビゲーションガード
    • beforeRouteEnter, beforeRouteUpdate, beforeRouteLeave
  • [INFO] nuxt: [page] / [method]
    • Nuxt.js の asyncData, fetch

確認できること:

  • 以下の遷移
    • /hooks/parents/1
    • /hooks/parents/2
    • /hooks/parents/1/child
    • /hooks/parents/2/child
    • /hooks/parents/2/child?q=query
  • リダイレクトや遷移失敗 (abort) 時の処理
  • asyncData / fetch でページ (state) を初期化、beforeRouteLeave でページ (state) をリセットした場合の問題点の把握
    • 今回のモチベーションはこれ

8月メモ・リンク集

8月に調べたことのメモです。

Vue 関連

基礎

ちょっと応用

Angular 関連

JavaScript その他

その他

ISO 形式 (ISO 8601) の文字列判定

ISO 8601 - Wikipedia

ISO 8601は日付と時刻の表記に関する国際規格。

// https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString

new Date().toISOString()
// => "2018-08-31T04:56:23.130Z"
const ISO8601_DATE_REGEX = /^\d{4}-?\d\d-?\d\d(?:T\d\d(?::?\d\d(?::?\d\d(?:\.\d+)?)?)?(?:Z|[+-]\d\d:?\d\d)?)?$/;

ISO8601_DATE_REGEX.test('20101010');
// => true
ISO8601_DATE_REGEX.test('2010-10-10');
// => true
ISO8601_DATE_REGEX.test('2010-10-10T00');
// => true
ISO8601_DATE_REGEX.test('2010-10-10T00:00');
// => true
ISO8601_DATE_REGEX.test('2010-10-10T00:00:00');
// => true
ISO8601_DATE_REGEX.test('2010-10-10T00:00:00.000');
// => true
ISO8601_DATE_REGEX.test('2010-10-10T00:00:00.000Z');
// => true
ISO8601_DATE_REGEX.test('2010-10-10T00:00:00.000+09:00');
// => true

ISO8601_DATE_REGEX.test('2010-1-10');
// => false
ISO8601_DATE_REGEX.test('2010-10-10T');
// => false
ISO8601_DATE_REGEX.test('2010-10-10T00:00:00000');
// => false

Vue のリアクティブシステム

勉強会で発表した資料です。
Vue のリアクティブシステム、その中でも依存関係のある関数の収集と再実行をどのように実現しているのかについて解説しています。Object.defineProperty, Dep Class and Watcher.

--- # 話すこと - Vue のリアクティブシステムの一部を説明 - リアクティブシステム
=> モデルの変更がDOMに反映される仕組み --- ![inline](model-dom.png) https://blog.thoughtram.io//angular/2016/02/22/angular-2-change-detection-explained.html --- ![inline](data.png) [リアクティブの探求 — Vue.js](https://jp.vuejs.org/v2/guide/reactivity.html) --- # 一旦サンプルを --- **値段**と**数**から**合計金額**を表示するアプリケーション
<!-- html -->
<div id="app">
  <div>price: {{ price }}</div>
  <div>quantity: {{ quantity }}</div>
  <div>total: {{ total }}</div>
</div>
// js
const app = new Vue({
  el: '#app',
  data: {
    price: 100,
    quantity: 2
  },
  computed: {
    total() { return this.price * this.quantity; }
  }
});
https://stackblitz.com/edit/vue-reactivity(https://stackblitz.com/edit/vue-reactivity) --- - `data` - Vue インスタンスのためのデータオブジェクト - Vue インスタンスが作成されるとリアクティブシステムに追加される - `computed` - Vue インスタンスに組み込まれる算出プロパティ - 算出プロパティは依存関係にもとづきキャッシュされる --- # 今日のゴールは ---
let data = { price: 100, quantity: 2 };
let total = data.price * data.quantity;

console.log(total);
// => 200

data.quantity = 3;
console.log(total);
// => 300 ?
--- # その1 ## 関数を保存 ---
let data = { price: 100, quantity: 2 };

// 再計算できるように関数を保存
let target = 
  () => data.total = data.price * data.quantity;

target();
console.log(data.total);
// => 200

data.quantity = 3;
target();
console.log(data.total);
// => 300

// https://stackblitz.com/edit/vue-reactivity-step-by-step?file=step-96.js
--- - 保存される関数は複数必要 - 依存関係のある関数だけを管理したい --- # その2 ## Dependency Class ---
export let target = null;
export class Dep {
  constructor() {
    this.subscribers= [];
  }
  depend() {
    if (target && !this.subscribers.includes(target)) {
      this.subscribers.push(target);
    }
  }
  notify() {
    this.subscribers.forEach(sub => sub());
  }
}
---
let data = { price: 100, quantity: 2 };
let watcher = 
  () => data.total = data.price * data.quantity;

const dep = new Dep();

target = watcher;
dep.depend();

target();
target = null;

console.log(data.total);
// => 200

data.quantity = 3;
dep.notify();
console.log(data.total);
// => 300

// https://stackblitz.com/edit/vue-reactivity-step-by-step?file=step-97.js
--- # Dependency Class の役割 --- ![inline](dep.png) https://medium.com/vue-mastery/the-best-explanation-of-javascript-reactivity-fea6112dd80d(https://medium.com/vue-mastery/the-best-explanation-of-javascript-reactivity-fea6112dd80d) --- ![inline](dep-2.png) https://medium.com/vue-mastery/the-best-explanation-of-javascript-reactivity-fea6112dd80d(https://medium.com/vue-mastery/the-best-explanation-of-javascript-reactivity-fea6112dd80d) --- ![inline](dep-3.png) https://medium.com/vue-mastery/the-best-explanation-of-javascript-reactivity-fea6112dd80d(https://medium.com/vue-mastery/the-best-explanation-of-javascript-reactivity-fea6112dd80d) --- - プロパティごとに `Dep` インスタンスが必要 - 依存関係のある関数だけを管理したい - (未解決) --- # その3 ## `Object.defineProperty` ---
Object.keys(data).forEach(key => {
  let _value = data[key];
  Object.defineProperty(data, key, {
    get() {
      console.log('get', key, _value);
      return _value;
    },
    set(value) {
      console.log('set', key, _value);
      _value = value;
    }
  });
});
---
Object.keys(data).forEach(key => {
  let _value = data[key];
  const dep = new Dep();
  Object.defineProperty(data, key, {
    get() {
      dep.depend();
      return _value;
    },
    set(value) {
      _value = value;
      dep.notify();
    }
  });
});

// ...

target = watcher;
target();
target = null;
--- # 完成
console.log(data.total);
// => 200

data.quantity = 3;
console.log(data.total);
// => 300
https://stackblitz.com/edit/vue-reactivity-step-by-step?file=step-99.js(https://stackblitz.com/edit/vue-reactivity-step-by-step?file=step-99.js) --- ![inline](data_with_annotations.png) https://medium.com/vue-mastery/the-best-explanation-of-javascript-reactivity-fea6112dd80d(https://medium.com/vue-mastery/the-best-explanation-of-javascript-reactivity-fea6112dd80d) --- # 振り返り - Vue のリアクティブシステムの一部を説明 - `Object.defineProperty` - `Dep` class - `Watcher` --- # 参考資料 - [The Best Explanation of JavaScript Reactivity 🎆 – Vue Mastery – Medium](https://medium.com/vue-mastery/the-best-explanation-of-javascript-reactivity-fea6112dd80d) --- # ご静聴ありがとうございました --- # おまけ1 ## Angular の場合 ---
export class AppComponent  {
  price: number;
  quantity: number;
  constructor() {
    this.price = 100;
    this.quantity = 2;
    (window as any).app = this;
  }
  get total() {
    return this.price * this.quantity;
  }
}

// コンソールから実行
window.app.quantity = 10;
// => DOMは更新されない
// => ボタンクリックなどをすると反映される
https://stackblitz.com/edit/vue-reactivity-case-of-angular(https://stackblitz.com/edit/vue-reactivity-case-of-angular) --- Angular のレンダリングに興味がある方は [日本語訳 Angular 2 Change Detection Explained | ](https://blog.lacolaco.net/post/translation-angular-2-change-detection-explained/) がおすすめです --- # おまけ2 ## Vue インスタンス作成時の実際のコードを追ってみる --- **instance/index** - https://github.com/vuejs/vue/blob/v2.5.17/src/core/instance/index.js#L8(https://github.com/vuejs/vue/blob/v2.5.17/src/core/instance/index.js#L8) `function Vue (options) {` - https://github.com/vuejs/vue/blob/v2.5.17/src/core/instance/index.js#L14(https://github.com/vuejs/vue/blob/v2.5.17/src/core/instance/index.js#L14) `this._init(options)` --- **instance/init** - https://github.com/vuejs/vue/blob/v2.5.17/src/core/instance/init.js#L16(https://github.com/vuejs/vue/blob/v2.5.17/src/core/instance/init.js#L16) `Vue.prototype._init = function (options?: Object) {` - https://github.com/vuejs/vue/blob/v2.5.17/src/core/instance/init.js#L57(https://github.com/vuejs/vue/blob/v2.5.17/src/core/instance/init.js#L57) `initState(vm)` --- **instance/state** - https://github.com/vuejs/vue/blob/v2.5.17/src/core/instance/state.js#L48(https://github.com/vuejs/vue/blob/v2.5.17/src/core/instance/state.js#L48) `export function initState (vm: Component) {` - https://github.com/vuejs/vue/blob/v2.5.17/src/core/instance/state.js#L54(https://github.com/vuejs/vue/blob/v2.5.17/src/core/instance/state.js#L54) `initData(vm)` - https://github.com/vuejs/vue/blob/v2.5.17/src/core/instance/state.js#L112(https://github.com/vuejs/vue/blob/v2.5.17/src/core/instance/state.js#L112) `function initData (vm: Component) {` --- - https://github.com/vuejs/vue/blob/v2.5.17/src/core/instance/state.js#L151(https://github.com/vuejs/vue/blob/v2.5.17/src/core/instance/state.js#L151) `observe(data, true /* asRootData */)` --- **observer/index** - https://github.com/vuejs/vue/blob/v2.5.17/src/core/observer/index.js(https://github.com/vuejs/vue/blob/v2.5.17/src/core/observer/index.js) `export function observe (value: any, asRootData: ?boolean): Observer | void {` - https://github.com/vuejs/vue/blob/v2.5.17/src/core/observer/index.js#L123(https://github.com/vuejs/vue/blob/v2.5.17/src/core/observer/index.js#L123) `ob = new Observer(value)` - https://github.com/vuejs/vue/blob/v2.5.17/src/core/observer/index.js#L37(https://github.com/vuejs/vue/blob/v2.5.17/src/core/observer/index.js#L37) `export class Observer {` --- - https://github.com/vuejs/vue/blob/v2.5.17/src/core/observer/index.js#L66(https://github.com/vuejs/vue/blob/v2.5.17/src/core/observer/index.js#L66) `defineReactive(obj, keys[i])` - https://github.com/vuejs/vue/blob/v2.5.17/src/core/observer/index.js#L134(https://github.com/vuejs/vue/blob/v2.5.17/src/core/observer/index.js#L134) `export function defineReactive (` - https://github.com/vuejs/vue/blob/v2.5.17/src/core/observer/index.js#L156(https://github.com/vuejs/vue/blob/v2.5.17/src/core/observer/index.js#L156) ` Object.defineProperty(obj, key, {` --- - https://github.com/vuejs/vue/blob/v2.5.17/src/core/observer/index.js#L162(https://github.com/vuejs/vue/blob/v2.5.17/src/core/observer/index.js#L162) `dep.depend()` - https://github.com/vuejs/vue/blob/v2.5.17/src/core/observer/index.js#L188(https://github.com/vuejs/vue/blob/v2.5.17/src/core/observer/index.js#L188) `dep.notify()`