読者です 読者をやめる 読者になる 読者になる

Motomichi Works Blog

その日学習したことについて書いている日記です。誰かの役に立ったらそれはそれで嬉しいです。

vuex2.xその0004 modulesによるstoreの分割と、mapGetters()でmoduleを跨いだgettersやstateの参照をしやすくする

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

はじめに

babelは使用せずに、Chromeで動作確認をしています。 参考ページのmapGetters()のサンプルで、オブジェクトのスプレッド演算子(Spread Operator)が使用されていますが、エラーが出たのでObject.assign()を使用しています。

storeがすごく大きくなったりしないようにとか、機能ごとに分割する方が管理がしやすいだろうということでmodulesとかmapGetters()の使い方を学習しました。

今回のバージョン

  • Vue.js v2.1.10
  • vuex v2.3.0

サンプルソースコード

以下の通りです。

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta charset="utf-8">
<title></title>
<meta name="description" content="">
<meta name="author" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="css/style.css">
<link rel="shortcut icon" href="">
</head>
<body>

<div id="app">
</div>

<script src="js/vue.js"></script>
<script src="js/vuex.js"></script>
<script>
const mapGetters = Vuex.mapGetters;

/**
 * Store
 */
const moduleA = {
  strict: true,
  state: {
    count: 0
  },
  mutations: {
    incrementA: state => state.count++ ,
    decrementA: state => state.count-- ,
  },
  getters: {
    moduleAState: state => state ,
  }
};

const store = new Vuex.Store({
  strict: true,
  modules: {
    a: moduleA,
  },
})

/**
 * RootComponent
 */
const RootComponent = {
  template: `
    <div>
      <div>mapState使用 : {{ moduleAState.count }}</div>
      <div>this.$state.getters.moduleAStateの方 : {{ count }}</div>
      <button @click="increment">+</button>
      <button @click="decrement">-</button>
    </div>
  `,
  methods: {
    increment() {
      this.$store.commit('incrementA')
    },
    decrement() {
      this.$store.commit('decrementA')
    }
  },
  computed: Object.assign(mapGetters([
      'moduleAState',
    ]),
    {
      count() {
        return this.$store.getters.moduleAState.count;
      }
    }
  ),
  created() {
    console.log('this.moduleAState.count : ' + this.moduleAState.count);
  }
};

/**
 * VueModel
 */
const app = new Vue({
  el: '#app',
  store,
  components: { RootComponent },
  template: `
    <div class="app">
      <root-component />
    </div>
  `,
});

</script>

</body>
</html>

modulesとmapGettersを使ってみて個人的要点

  • modulesに分割しても、this.$store.commit('hoge')で全てのコンポーネントから全てのmutationsが呼び出せる
  • actions、gettersについても同様にthis.$store.dispatch('hoge')this.$store.getters.hogeなどで全てのコンポーネントから呼び出せる
  • this.$store.getters.moduleAState.countと記述すると冗長ですが、mapGetters()を使用するとそのコンポーネントのcomputedのgetterとして簡潔な記述で呼び出せるようになる

公式ドキュメントに書いてありますが、グローバルな空間にactionsなどもろもろの名前が定義されるので、どのコンポーネントからでもthis.$storeのプロパティとして呼び出せます。 namespaced: trueを設定すると名前空間を切ることができます。

CLIP STUDIO PAINT DEBUT その0001 タブレットPCで線の入り抜きができない問題を解消する raytrektab DG-D08IWP

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

解決したいこと

ドスパラで raytrektab DG-D08IWP を購入して、CLIP STUDIO PAINT DEBUT が付属していたので使ってみたら線の入り抜きができない設定でした。

機種にかかわらずタブレットPCだと同様の事象が発生するようですね。

解決方法

私は以下の方法で解決できました。

  1. [ファイル]メニュー → [環境設定]を選択します。
  2. [タブレット]のカテゴリを選択します。
  3. [TabletPC]にチェックを入れてOKします。

非常に基本的かつ重要なことなのに、意外と必要な情報にたどり着けませんでしたが公式ページで解決されていました。

ついでに調整の方法

上記で入り抜きができるようになったら以下のページにある手順で細かな調整ができます。

vuex2.xその0003 vuex入門的な感じでToDo管理アプリケーションを作る

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

はじめに

APIにリクエストする非同期な処理を想定して、actionsからcommitしています。 実際にAPIとか作ったらもう少し違う実装が適切かもしれないです。

ES2015が使えない環境下でのことを考慮するなど個人的事情からlodashを使用していたり、とはいえ全体的にはES2015で書いていますがそこはそっとスルーでお願いします。

今回のバージョン

  • Vue.js v2.1.10
  • vuex v2.3.0

サンプルソースコード

例として以下の通りです。

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta charset="utf-8">
<title></title>
<meta name="description" content="">
<meta name="author" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="css/style.css">
<link rel="shortcut icon" href="">
</head>
<body>

<style>
.is-done {
  color: #999999;
  text-decoration: line-through;
}
</style>

<h1>タスク管理アプリケーション</h1>
<div id="app">
</div>

<script src="js/lodash.js"></script>
<script src="js/vue.js"></script>
<script src="js/vuex.js"></script>
<script>
/**
 * store
 */
const store = new Vuex.Store({
  strict: true,
  state: {
    latestItemId: null,
    items: []
  },
  getters: {

  },
  actions: {
    addItem({commit, rootState}, content) {
      // 実際はここでAPIにリクエストしてDBのレコード追加が成功したらcommit
      const newItemId = rootState.latestItemId + 1;
      commit('addItem', {id: newItemId, content: content, isDone: false});
    },
    toggleItem({commit, rootState}, id) {
      // 実際はここでAPIにリクエストしてDBの当該レコードの更新が成功したらcommit
      const index = _.findIndex(rootState.items, function(o) { return o.id == id; });
      commit('toggleItem', index);
    },
    deleteItem({commit}, id) {
      // 実際はここでAPIにリクエストしてDBの当該レコードの削除が成功したらcommit
      commit('deleteItem', id);
    },
    getItems({commit}) {
      // 実際はここでAPIにリクエストしてDBからタスクリストを取得します
      const items = [
        {id: 0, content: '起きる', isDone: false},
        {id: 1, content: '朝食を食う', isDone: false},
        {id: 2, content: '昼食を食う', isDone: true},
        {id: 4, content: '夕食を食う', isDone: true},
        {id: 5, content: '風呂に入る', isDone: false},
        {id: 10, content: '寝る', isDone: false},
      ];
      const latestItemId = items[items.length - 1].id;
      commit('getItems', {latestItemId: latestItemId, items: items});
    }
  },
  mutations: {
    addItem(state, newItem) {
      state.latestItemId = newItem.id;
      state.items.push(newItem);
    },
    toggleItem(state, index) {
      state.items[index].isDone = !state.items[index].isDone;
    },
    deleteItem(state, id) {
      state.items = _.filter(state.items, function(item) {
        return item.id !== id;
      });
    },
    getItems(state, newState) {
      _.assign(state, newState);
    }
  }
});

/**
 * addItem
 * タスク新規追加フィールドのコンポーネント
 */
const addItem = {
  data() {
    return {
      content: '',
    }
  },
  template: `
    <section>
      <h2>タスクを新しく追加</h2>
      <div>
        <input type="text" v-model="content">
        <input type="submit" value="追加" @click="addItem">
      </div>
    </section>
  `,
  methods: {
    addItem() {
      if (!this.content) return;
      this.$store.dispatch('addItem', this.content);
      this.content = '';
    }
  }
};

/**
 * item
 * タスク一つ分のliタグのコンポーネント
 */
const item = {
  props: ['item'],
  template: `
    <li>
      <span :class="{'is-done': isDone}">id {{item.id}} : {{item.content}}</span>
      <button @click="toggleItem" v-text="buttonLabel"></button>
      <button @click="deleteItem">削除する</button>
    </li>
  `,
  computed: {
    id() {
      return this.item.id;
    },
    isDone() {
      return this.item.isDone;
    },
    buttonLabel() {
      return this.item.isDone ? '未完了に戻す' : '完了にする';
    },
  },
  methods: {
    toggleItem() {
      this.$store.dispatch('toggleItem', this.id);
    },
    deleteItem() {
      if (!confirm("削除しますか?")) return;
      this.$store.dispatch('deleteItem', this.id);
    },
  }
};

/**
 * itemList
 * タスク一覧のulタグのコンポーネント
 */
const itemList = {
  components: {item},
  template: `
    <section>
      <h2>登録されているタスク</h2>
      <ul>
        <item v-for="item in items" v-bind:item="item"></item>
      </ul>
    </section>
  `,
  computed: {
    items() {
      return this.$store.state.items;
    }
  },
  created() {
    this.$store.dispatch('getItems');
  }
};

/**
 * app
 * rootのコンポーネント
 */
const app = new Vue({
  el: '#app',
  store,
  components: {
    addItem,
    itemList,
  },
  template: `
    <div class="app">
      <add-item />
      <item-list />
    </div>
  `
});
</script>

</body>
</html>

Vuexとは

Storeを提供し、以下のようなルールに則った開発をすることでコードの見通しがよくなります。

  • Storeが持っているstateはmutationsによってのみ更新される
  • mutaionsは同期的でなくてはならず、非同期な処理はactionsが担う

正確な情報は公式ドキュメントを参照してください。

個人的まとめ

あくまでも個人的なもので、ES2015などを使用しない環境下のせいもあると思いますが、参考になればと思います。

  • VuexはStoreを提供するもので、コンポーネントは普通にVue.js単体で使っているときと同じ要領で書く
  • rootコンポーネントにstoreオプションを設定すると、子コンポーネントthis.$storeが参照できるようになる
  • コンポーネント内でstoreを参照するときはthis.$storeを使う
  • Storeはstrict: true,を設定した方がいい
  • Storeが持っているstateはmutationsによってのみ更新される
  • mutaionsは同期的でなくてはならず、非同期な処理はactionsが担う
  • mutationsが持っているメソッドは第一引数にstateを受け取る
  • actionsが持っているメソッドは第一引数にcontextまたは分割で{state, rootState, commit, dispatch, getters}を受け取る
  • mutaionを呼ぶときはcommitで、actionを呼ぶときはdispatch
  • getters, modules, plugins というキーもあるけど今回は使っておらず、いずれも大きい規模になるほど有用そう
  • actionのdispatchとpromiseについては「アクション · GitBook

Vuexに限らずVue.jsのコンポーネントのこと

  • コンポーネント内において、Storeが持っているstateの更新が反映されてほしい箇所はcomputedでreturnする
  • コンポーネント内において、親からもらったpropsの更新が反映されてほしい箇所は直接templateに書くか、computedでreturnする
  • アロー関数を使うとthisがコンポーネント自身を参照しないので理解して使う

vuex2.xその0002 mapStateのこと

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

はじめに

storeを複数使うような規模のものを書いていないのでmapStateはまだ自分には必要なさそうですが、少し書いてみました。

サンプルソースコード

こんな感じかなー。と書いてみました。

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta charset="utf-8">
<title></title>
<meta name="description" content="">
<meta name="author" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="css/style.css">
<link rel="shortcut icon" href="">
</head>
<body>

<div id="app">
</div>

<script src="js/vue.js"></script>
<script src="js/vuex.js"></script>
<script>
const mapState = Vuex.mapState;

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment: function(state){
      return state.count++;
    },
    decrement: function(state){
      return state.count--;
    }
  }
});

const Counter = {
  template: `
    <div>
      <div>{{ count }}</div>
      <button @click="increment">+</button>
      <button @click="decrement">-</button>
    </div>
  `,
  computed: mapState({
    // アロー関数は、コードをとても簡潔にできます!
    count: state => state.count,
    // 文字列を渡すことは、`state => state.count` と同じです
    countAlias: 'count',
    // `this` からローカルステートを参照するときは、通常の関数を使わなければいけません
    countPlusLocalState (state) {
      return state.count + this.localCount
    }
  }),
  methods: {
    increment () {
      this.$store.commit('increment')
    },
    decrement () {
        this.$store.commit('decrement')
    }
  }
};

const app = new Vue({
  el: '#app',
  store,
  components: { Counter },
  template: `
    <div class="app">
      <counter></counter>
    </div>
  `
});
</script>

</body>
</html>

このコードについて

上記のサンプルでは、公式ドキュメントにあったオブジェクトスプレッド演算子は使っていませんが、ローカルなcomputedも定義する場合はそのような記述が必要になるようです。

オブジェクトスプレッド演算子が使えない環境下では、Object.assign()とかlodashの_.assign()とか使う感じでしょうか。

vuex2.xその0001 公式ドキュメントにあるカウンターアプリを作ってみる

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

公式ドキュメント

ソースコード

はじめに

色々な事情でwebpackなどを使用できない環境下でスタンドアローンビルドのvuexを使用するような想定で書いています。

サンプルソースコード

例として以下の通りです。 storeにはstrict: true,を設定した方が良さそうだなと思っています。

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta charset="utf-8">
<title></title>
<meta name="description" content="">
<meta name="author" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="css/style.css">
<link rel="shortcut icon" href="">
</head>
<body>

<div id="app">
</div>

<script src="js/vue.js"></script>
<script src="js/vuex.js"></script>
<script>
/**
 * store
 */
const store = new Vuex.Store({
  strict: true,
  state: {
    count: 0
  },
  mutations: {
    increment: state => state.count++ ,
    decrement: state => state.count-- ,
  }
});

/**
 * Counterコンポーネント
 */
const Counter = {
  template: `
    <div>
      <div>{{ count }}</div>
      <button @click="increment">+</button>
      <button @click="decrement">-</button>
    </div>
  `,
  computed: {
    count(){ return this.$store.state.count; },
  },
  methods: {
    increment({commit}) {
      this.$store.commit('increment')
    },
    decrement({commit}) {
        this.$store.commit('decrement')
    }
  }
};

/**
 * rootコンポーネント
 */
const app = new Vue({
  el: '#app',
  store,
  components: { Counter },
  template: `
    <div class="app">
      <counter />
    </div>
  `
});
</script>

</body>
</html>

railsその0002 vagrant+rails5の環境でmodelやcontrollerのファイルを編集しても反映されない問題を解決する

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

vagrantrsyncのエラーについて

vagrant + rails のときに起こる問題について

今回の環境(インストーラーなど)

ホストマシン環境

ゲストマシン環境

rsyncが無いのでMSYS2をインストールしてpacmanrsyncをインストールする

Vagrantfileの中で type: "rsync",を設定するにはrsyncをインストールする必要があり、

これまで git for windows の Git BASHvagrant を使ったりしていましたが、このままだと都合が悪いので、

表題の問題を解決するためにMSYS2をインストールしたり以下のことをしました。

MSYS2については別途「Windows10にmsys2をインストールして使ってみる - Motomichi Works Blog」に今回の件以外のことも含めて書きました。

Vagrantfileのconfig.vm.synced_folderの設定をする

例として以下の通りですが、同期するディレクトリのパスはご自分の環境に合わせて読み替えてください。

config.vm.synced_folder "./my_app", "/var/www/rails_project/my_app",
  type: "rsync",
  rsync__args: %w(--verbose --archive --delete -z --copy-links --times),
  rsync__exclude: %w(.git/ log/ tmp/ vendor/)

vagrant upしてみるとエラー

以下のようなエラーが出ました。同期せずに止まっているようなので仮想環境内のディレクトリには何もありません。

There was an error when attempting to rsync a synced folder.
Please inspect the error message below for more info.

Host path: /c/Users/motomichi/Desktop/all/git_repos_all/github/MotomichiWorks/rails_practice_0001/my_app/
Guest path: /var/www/rails_project/my_app

Command: "rsync" "--verbose" "--archive" "--delete" "-z" "--copy-links" "--times" "--chmod=ugo=rwX" "--no-perms" "--no-owner" "--no-group" "--rsync-path" "sudo rsync" "-e" "ssh -p 2222 -o ControlMaster=auto -o ControlPath=C:/Users/Public/Documents/Wondershare/CreatorTemp/ssh.926 -o ControlPersist=10m -o StrictHostKeyChecking=no -o IdentitiesOnly=true -o UserKnownHostsFile=/dev/null -i 'C:/Users/motomichi/.vagrant.d/boxes/centos67box-20170505/0/virtualbox/vagrant_private_key'" "--exclude" ".vagrant/" "--exclude" ".git/" "--exclude" "log/" "--exclude" "tmp/" "--exclude" "vendor/" "/c/Users/motomichi/Desktop/all/git_repos_all/github/MotomichiWorks/rails_practice_0001/my_app/" "vagrant@127.0.0.1:/var/www/rails_project/my_app"

Error: This rsync lacks old-style --compress due to its external zlib.  Try -zz.

Continuing without compression.

Warning: Permanently added '[127.0.0.1]:2222' (RSA) to the list of known hosts.
mux_client_request_session: read from master failed: Connection reset by peer
Failed to connect to new control master
rsync: connection unexpectedly closed (0 bytes received so far) [sender]
rsync error: error in rsync protocol data stream (code 12) at io.c(226) [sender=3.1.2]

これに関しては、「VirtualBox - ローカルとvargrentをrsyncしたくvagrantfileを編集したところ、vagrant up中に以下のエラーメッセージが発生しました。何が原因かアドバイスを頂けないでしょうか?(36200)|teratail」を参考にさせて頂いて解決しました。

私の環境では以下のファイルを編集しました。

C:\HashiCorp\Vagrant\embedded\gems\gems\vagrant-1.8.4\plugins\synced_folders\rsync\helper.rb

以下の3行をコメントアウトしました。

          "-o ControlMaster=auto " +
          "-o ControlPath=#{controlpath} " +
          "-o ControlPersist=10m " +

念のためいつでも元に戻せるようにしておくのが良さそうですね。

ホストマシンのmodelやcontrollerを編集しても仮想環境内に反映されない問題を解決する

まず vagrant reload をすると仮想環境内に同期はされるようになりました。

まだこれだけではrailsサーバーを一度止めてから、rails sしなおさないと編集内容が反映されません。

Rails developmentモードなのにコードの変更が反映されない – KeruuWeb」を参考にさせて頂いて解決しました。

vagrant reload

をしたあとで、一度以下を実行します。

vagrant rsync

次に以下を実行するとリアルタイムに同期されるようになります。

vagrant rsync-auto

このウィンドウはそのままにして、別ウィンドウでMSYS2を起動して、

vagrant ssh

railsのあるディレクトリでいつも通り

rails s

して確認すると解決されていました。

終了するときは、

ctrl + c で rsync-auto を止めて、いつも通り vagrant halt で止めます。

Windows10にmsys2をインストールして使ってみる

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

ダウンロード

環境変数を継承する

その他

インストール

64bit版の現在の最新版である以下のインストーラーを使用した

  • msys2-x86_64-20161025.exe

Windows環境変数を継承させる

スタートメニューの「MSYS2 64bit > MSYS2 MSYS」を選択すると起動できますが、Windowsコマンドプロンプトなどから使えるコマンドが見つからないので、環境変数を継承させる必要があるみたいです。

スタートメニューを右クリックして「システム > システムの詳細設定 > 環境変数」からユーザー環境変数の方の「新規」を選択して、変数名と変数値を設定して、MSYS2を起動しなおしたら色々とコマンドが使えるようになりました。

MSYS2 で PATH が引き継がれない」を参考にさせて頂いて、以下のように設定しました。

  • 変数名 : MSYS2_PATH_TYPE
  • 変数値 : inherit

Desktopに移動する

cd /c/Users/xxxxxx/Desktop/

rsync vim git opensshをインストール

pacman -S rsync vim git openssh