Motomichi Works Blog

モトミチワークスブログです。その日学習したことについて書いている日記みたいなものです。

TypeScript 学習日記 その0002 キーが未確定なobjectにデータを追加する方法について考える

参考にさせていただいたページ

はじめに

注意して使用しましょう。

「キーが未確定なobjectを先に作って後からデータを追加する」という処理が本当に必要なのかよく考える必要があると思います。

できるだけ先にキーを決めて、データの追加はその型に沿って追加するのが良いと思います。

良くない例

以下のコードでは「キーが未確定なobjectにデータを追加する方法」を使用していますが、良くない例です。

参考にさせていただいた「TypeScriptの型入門 - Qiita」のインデックスシグネチャの項にあるように、以下の例ではMyObj型の定義が曖昧になってしまい、TypeScriptの良さが活かせていません。

    // MyObj型を定義
    type MyObj = {
      [key: string]: number // ここでnumberだけにしているのが良くない
    }

    // 変数myObjをMyObj型で定義
    const myObj: MyObj = {}

    // データを追加できる
    myObj['keyNameA'] = 0
    myObj['keyNameB'] = 1

    // 変数keyに'example'を代入
    const key: string = 'example'

    // myObj.exampleは存在しないキーなので、変数myVariableにはundefinedが入る
    const myVariable = myObj[key]

    // 実際にはmyVariableはundefinedだが、この後の処理でnumberとして型推論がされるので不具合の元になってしまう
    console.log(myVariable) // undefined

少し改善する

以下のように改善できます。

    // MyObj型を定義
    type MyObj = {
      [key: string]: number | undefined // ここでnumber | undefinedにしておくと、myVariableが後の処理で正しく型推論される
    }

「キーが未確定なobjectを先に作って後からデータを追加する」という処理が本当に必要なのか考える

以下のようにできるのであれば、より安全です。

    // MyObj型を定義
    type MyObj = {
      keyNameA: number
      keyNameB: number
    }

    // 変数myObjをMyObj型で定義
    const myObj: MyObj = {
      keyNameA: 0,
      keyNameB: 1,
    }

    // 変数keyに'example'を代入
    const key: string = 'example'

    // MyObj型が予め厳格に定義できているため、ここでコンパイルエラーになり、不具合が未然に防げる
    const myVariable = myObj[key]

    // この後の処理も安心
    console.log(myVariable)

結局のところ

上記した「良くない例」のように [key: string]を使用したり、[index: number]を使用することで、objectのキーやarrayのインデックスを抽象的にすることができますが、使用方法を誤ると型推論が正しく行われなくなってしまいます。

「キーが未確定なobjectを先に作って後からデータを追加する」という処理が本当に必要なのか、それが有効な方法なのかをよく考える必要があると思います。

型推論が正しく行われるように書きましょう。

TypeScript 学習日記 その0001 objectのキーを元にStringLiteralTypesの型を定義する

参考にさせていただいたページ

今日のバージョン

  • TypeScript 4.2.3

文字列の型について

以下のようにすると、変数keyの型はstringになります。

const key: string = 'keyNameA'

以下のようにすると、変数keyの型は'keyNameA'または'keyNameB’になり、この2種類の文字列しか代入できなくなります。

type Key = 'keyNameA' | 'keyNameB'
const key: Key = 'keyNameA'

keyofキーワードを使ってobjectのキーを元に型を設定する

例えば以下のようなオブジェクトがあるとします。

const myObj = {
  keyNameA: '',
  keyNameB: '',
}

以下のようにすると、さきほどの例のように変数keyの型は'keyNameA'または'keyNameB’になり、この2種類の文字列しか代入できなくなります。

type Key = keyof typeof myObj
const key: Key = 'keyNameA'

Nuxt.js 2.x + TypeScript 4.x その0007 storeの型を設定する

参考にさせていただいたページ

今日の開発環境

  • Nuxt 2.14.12
  • TypeScript 4.2.3

定義する

基本的なタイピング」をよく読むとよい。

  • ReturnType<typeof state> を使って、stateの型を定義する
  • GetterTree, ActionTree, MutationTree を使う

GetterTreeについて

例えばgettersの場合は、以下のような感じでGetterTreeを使って、getterの引数に入ってくるstateとrootStateの型を設定している。gettersとrootGettersはanyで入ってくる。

export const getters: GetterTree<AnotherModuleState, RootState> = {
  evenMore: state => state.more + 5,
  nameAndMore: (state, getters, rootState) => `${rootState.name}: ${state.more}`,
}

RSpec+Capybaraその0005 aタグやbuttonタグのkeydownイベントを発火させる

参考にさせていただいたページ

keydownイベントを発火させる方法

もっと良い方法があるかもしれないけど、たぶんこんな感じ

button = find('[data-test-id="example"]')
button.native.send_key(:enter)

RSpec+Capybaraその0004 要素にfocusする

参考にさせていただいたページ

要素にfocusする方法

もっと良い方法があるかもしれないけど、たぶんこんな感じ。

page.execute_script("document.querySelector('[data-test-id="example"]').focus()")

Vue.js 2のエラーを解決する [Vue warn]: You are using the runtime-only build of Vue where the template compiler is not available.

はじめに

特にwebpackなどを使用して、Vue.jsをこれからはじめようという方に多いとは思いますが、以下のようなエラーがコンソールに出力されることがあります。

[Vue warn]: You are using the runtime-only build of Vue where the template compiler is not available. Either pre-compile the templates into render functions, or use the compiler-included build.

翻訳すると以下の通りです。

テンプレートコンパイラが利用できないVueのランタイムのみのビルドを使用しています。 テンプレートをレンダリング関数にプリコンパイルするか、コンパイラに含まれているビルドを使用します。

Vue.jsには「ランタイム + コンパイラ」のパッケージと「ランタイムのみ」のパッケージがあり、上記のエラーは「ランタイムのみ」のパッケージを使用しているときに出力されます。

「ランタイムのみ」のパッケージを使用すると、「ランタイム + コンパイラ」のパッケージを使用する場合に比べて3割程度ファイルサイズが軽くなります。

このエラーの解決方法としては、以下のいずれかの対応が必要です。

  1. 「ランタイムのみ」の方の使用を続けてrender関数を使う
  2. 「ランタイム + コンパイラ」の方のVue.jsを使用する

参考: ランタイム + コンパイラとランタイム限定の違い

一般的に新規構築の場合は「ランタイムのみ」の方を使用すると思いますし、プロジェクトの途中からVue.jsを導入する場合には「ランタイム + コンパイラ」の方を使用することもあると思います。

エラーが出ているコードの例

例えば以下のようなhtmlとjsを書いてエラーが出ていたとします。

example.html

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta charset="utf-8">
<title>vue-tutorial</title>
</head>
<body>
  <div id="app"></div>
  <script type="text/javascript" src="packed/javascripts/index.bundle.js"></script>
</body>
</html>

example.js

以下のようにtemplateオプションを使用していて、ランタイムのみのVue.jsを使用していると表題のエラーメッセージが表示されます。

import Vue from 'vue';

new Vue({
  el: '#app',
  template: `<div>example</div>`,
});

解決する

1. render関数を使う

解決方法のひとつめを紹介します。

新規構築する場合はこちらの方法を使用するのが良いと思います。

ランタイムのみのVue.jsパッケージを使用する場合は、render関数を使用して以下のように記述するように公式ドキュメントに記載があります。

import Vue from 'vue';

new Vue({
  el: '#app',
  render (h) {
    return h('div', 'example')
  }
});

ただ、公式ドキュメントに記述してあるこのままでは、単一Vueコンポーネントを使用して書くにはどうしたら良いかわからない方もいるかもしれません。
単一Vueコンポーネントを使用するには以下のように記述します。

import Vue from 'vue';
import App from './path/to/App.vue';

new Vue({
  el: '#app',
  render (h) {
    return h(App);
  },
});

2. 「ランタイム + コンパイラ」の方のVue.jsを使用する

解決方法のふたつめを紹介します。

webpack.config.jsのaliasを設定して、「ランタイム + コンパイラ」の方をimportするようにします。

webpack.config.jsを以下のように設定することで、「ランタイム + コンパイラ」の方をimportできるようになります。

module.exports = {
  〜略〜
  resolve: {
    alias: {
      // import Vue from 'vue'; と記述したときの 'vue' が表すファイルパスを設定
      'vue$': 'vue/dist/vue.esm.js'
    },
  },
}

実際にはwebpack.config.jsで他にも色々と設定していると思いますが、省略してaliasの部分だけ記述しています。

Nuxt.js 2.x + TypeScript 4.x その0005 ビルド時にJavaScript heap out of memoryなってしまう問題の解決

参考にさせていただいたページ

Vue.extendとnameオプションについて

メモリの割り当てを増やす

eslintの対象を絞る

standaloneをtrueにする

はじめに

JavaScript heap out of memoryになってしまう原因は色々なものがあると思います。

結局凡ミスだったのですが、今回の私の例について紹介します。

自分のケースの問題と解決

example.vueで以下のように、HogeとFooという2つのコンポーネントをimportしていました。

import Hoge from '~/components/common/hoge/index.vue'
import Foo from '~/components/common/foo/index.vue'

hoge/index.vueの方には以下のように、name: 'Hoge' を設定していました。これは意図したものです。

export default Vue.extend({
  name: 'Hoge',
  〜略〜
})

foo/index.vueの方にも以下のように、name: 'Hoge' を設定してしまっていました。これは意図しないものです。

export default Vue.extend({
  name: 'Hoge',
  〜略〜
})

コンストラクタ名が重複することによって、ビルド時に問題が起こっていました。

こんなミスとは気づかず、しばらく「JavaScript heap out of memory」とかでWeb検索してハマっていました...。

foo/index.vueの方のnameを以下のように、name: 'Foo' とすることで「JavaScript heap out of memory」の問題が解決されました。

export default Vue.extend({
  name: 'Foo',
  〜略〜
})

その他のケースについて

タイミングとしては、test実行時、eslint実行時、ビルド実行時など色々なタイミングでメモリ不足になっているようです。

冒頭に書いたように、様々な理由でメモリ不足が起こるのですが、今回のことでいくつかの対応方法を試しました。
メモリ不足になる原因によっては以下の対応によって問題に対処できるかもしれません。

メモリの割り当てを増やす

例えば yarn dev コマンドで開発サーバーを起動させていたときに「JavaScript heap out of memory」になった場合についてです。

まずnuxt.config.jsのtypescript.memoryLimitを設定します。

  typescript: {
    typeCheck: {
      typescript: {
        memoryLimit: 8192, // ここ
      },
    },
  },

そのうえで、package.jsonのscriptsを以下のように変更します。

"dev": "NODE_OPTIONS='--max-old-space-size=8192' nuxt-ts",

これで、改めて yarn dev すると今度はメモリ不足にはならないかもしれません。

メモリの値はご自身の端末で割り当て可能な値を適度に設定してみてください。

eslintの対象を絞る

eslintの実行時にメモリ不足になる場合は、eslintの対象範囲を絞ることで問題が解決するかもしれません。

例えばnuxt.config.jsの以下のようになっている箇所を

  typescript: {
    typeCheck: {
      eslint: {
        files: './**/*.{ts,js,vue}',
      },
    },
  },

以下のように変更したり、

  typescript: {
    typeCheck: {
      eslint: {
        files: './src/**/*.{ts,js,vue}',
      },
    },
  },

.eslintrc.jsのignorePatternsの設定を以下のように変更するなどで問題が解決するかもしれません。

  ignorePatterns: [
    './node_modules/',
    '.nuxt/',
  ],

これは、.eslintignoreファイルを作成することでも同じ効果が得られます。

standaloneをtrueにする

revert: revert node-externals disabling in dev mode by clarkdo · Pull Request #5452 · nuxt/nuxt.js · GitHub」に書いてあって、効果がよくわかっていないのですが、一応これも貼っておきます。

nuxt.config.jsのstandaloneの項目を以下の通り変更するようです。

  build: {
    standalone: true,
  },