Vuexって必要?(PF作成)

絶賛ポートフォリオ作成中で、つまづきポイントが出てきた。

状態管理どうする?と。

ほとんどがRuby on Rails で一部だけVue.jsで実装する場合、セッション情報の管理とかはRailsが行うからVuexは入れないでいいとよく聞くけど、実際どうなの?と。

整理してから考えてみる。

 

結論

自分が作るような小規模なポートフォリオにVuexに入れる必要はない。

Vuexについて

Vuexって何?

  • 状態管理のパターンを提供してくれるライブラリ
  • 単方向データフローを強制する。

view ⇨ action ⇨ mutation ⇨ state ⇨ getter ⇨ view ...

  • グローバルデータを一元管理。

ローカルとグローバル全てのデータやメソッドをVuexで一元管理させることはアンチパターンとされている。

Vuexの責務は?

グローバルデータを安全に一元管理すること。

どういう時に使うの?

アプリ開発を行なっている時に、階層が何層にも増えそうだな〜って予想される時に使う。

Vue.jsのようなコンポーネント指向のMVCライブラリは、一つのコンポーネントに一つの役割を持たせることがベターとされている。単一責任の法則っていうらしい。コンポーネントに分けることで、後から見た人がメンテナンスをしやすくなることに繋がるため、中〜大規模開発で大活躍する概念だそう。

一方で、それぞれのコンポーネントのメソッドの受け渡しがかなり面倒なことになるというデメリットもある。

例えば、通販サイトのホーム画面を実装したいときに、「通販サイトのホーム画面の中のnavバーの中の会計ボタンの中の購入ボタン」のように、コンポーネントの親子関係が複雑になることがしばしば起こるらしい。

このように、階層がかなり増えることが予想される中〜大規模開発において、状態管理ライブラリであるVuexが役に立つんだそう。

なんで?

階層が何層にも増えると、自分の現在書いているComponentが親や子から見てどの階層にいるか分からなくなってしまうから。そのため、$emitとpropsをどう使ってメソッドやイベントをやり取りすればいいかわからなくなってしまう。

再度、結論

だから、ページ数が15枚程度の自分のアプリに入れてもVuexの効果は発揮できないので入れないでヨシッ!

状態管理のことをセッション管理だと思っていた自分が恥ずかしい。。

セッション管理は変わらずsorceryで頑張ろ、、

vuexで何をするか、何をしないか / what should do or not with Vuex

Vue.js + Vuexでデータが循環する全体像を図解してみた - Qiita

Github Actionsと悪戦苦闘(未解決)

Github Actionsに苦戦中。

まだ解決はしていませんが、途中経過を残しておきます。

その過程でDockerさんと知り合ったので、特徴についても書きました。

Github Actionって何?

Githubが用意している便利なワークフロー。

ブランチにプッシュした時に自動でさまざまなコードが走るように設定できるので、脳死でデプロイなんかもできちゃう。

  • リポジトリには、さまざまなイベントに基づいてさまざまなジョブをトリガーする複数のワークフローを含めることができます。
  • ワークフローを使用してソフトウェアテストアプリをインストールし、GitHub のランナーでコードを自動的にテストすることができます。

記述例

name: auto_test_and_deploy
on: 
  pull_request:
    branches:
      - main

jobs:
    steps:
      # Checkout and install dependencie
      - name: Check out repository code
        uses: actions/checkout@v2

      - name: Prepare Ruby and Gems
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: 2.6.7
          bundler-cache: true

  build-test:
    name: auto build-test
    needs: prepare
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - name: yarn
        run: yarn --frozen-lockfile
      - name: Run Breakman
        run: |
          bundle exec bundler-audit --update
          bundle exec brakeman -q -w2
      # Run Linter
      - name: Run Rubocop
        run: |
          bundle exec rubocop --parallel
      # Run RSpec (added later)

  deploy:

name: ワークフロー名を記述する。

jobs: CD/CIで行いたい内容(rubocopを走らせたり、Lintチェックをしたり)を記述する。

steps: 具体的に行いたい内容を記述する。

Dockerって何?

DockerはDocker社が開発したコンテナという概念を管理するソフトウェアで、サーバを起動する方法がシンプルで、かつ起動や処理が速いことが特徴

一つの会社に一つのサーバーが置いてある時代

クラウドにサーバーがある時代

仮想クラウドのある時代

①システム導入の高速化

OSは既に共有して利用しているため、それらの設定は省き、システムを構築するために必要となる最低限のプログラムしかインストールする必要はありません。

そのため、すぐにプログラムを適用し、導入までのスピードを高速化することができます。

②起動時間の短縮化と多くの処理実装が可能

完全仮想化であれば、サーバを起動する際にOSレベルから立ち上げていく必要があり時間がかかります。

一方、DockerではOSは既に立ち上がっているため、その分サーバの起動時間を短縮化することができます。

また、リソースの使用量が少なく済むためサーバへの負荷が低く、1度に多くのプログラム処理を実装することが可能です。

③コンテナ設定の再利用が可能

1度作成したコンテナは、Dockerイメージを作成し、他のコンテナへ適用することにより再利用が可能となります。

検証してみたい時や、リソースを拡大したい時など、すぐに同じ環境設定が適用されたコンテナを準備することができます。

Dockerとは何かを入門者向けに解説!基本コマンドも|Udemy メディア

Docker構築に使ったファイル

Dockerの特徴の一つが、インフラをコード化できるところにあるそう。

これを、Infrastructure as a Codeというらしい。丁度下記に示すDockerfileなんかがそれに該当する。

Dockerfile

FROM ruby:2.6
RUN curl -sS <https://dl.yarnpkg.com/debian/pubkey.gpg> | apt-key add - \\
    && echo "deb <https://dl.yarnpkg.com/debian/> stable main" | tee /etc/apt/sources.list.d/yarn.list \\
    && apt-get update -qq \\
    && apt-get install -y nodejs yarn \\
    && mkdir /Preliquo
WORKDIR /Preliquo
COPY Gemfile /Preliquo/Gemfile
COPY Gemfile.lock /Preliquo/Gemfile.lock
RUN gem install bundler #これを忘れると、bundlerがインストールされてませんと英語で出る。
RUN bundle install
COPY . /Preliquo

COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000

CMD ["rails", "server", "-b", "0.0.0.0"]

docker-compose.yml

version: '3'
services:
  db:
    image: mysql:5.6
    environment:
      MYSQL_ROOT_PASSWORD: password
    ports:
      - '3306:3306'
    command: --default-authentication-plugin=mysql_native_password
    volumes:
      - mysql-data:/var/lib/mysql
  web:
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/Preliquo
    ports:
      - "3000:3000"
    depends_on:
      - db
    stdin_open: true
    tty: true
volumes:
  mysql-data:
    driver: local

entrypoint.sh

#!/bin/bash
set -e

# Remove a potentially pre-existing server.pid for Rails.
rm -f /Preliquo/tmp/pids/server.pid

# Then exec the container's main process (what's set as CMD in the Dockerfile).
exec "$@"

このDockerfileを実行するためには下記コードをターミナルで入力する必要がある。

$ docker-compose up

コードを打って出てきた下記エラーに手こずった。

`find_spec_for_exe': Could not find 'bundler'

記事を参考にして、解決。

bundlerが入っていないことが原因だった。Dockerfileに下記記述を追加

RUN gem install bundler

現状

DBをセッティングする場面でかなり時間がかかり、結局完成させることはできなかった。

とりあえず、set databaseは後に置いておいて、他の実装を進めている。

name: auto_test_and_deploy
on: 
  pull_request:
    branches:
      - main
env:
  RAILS_ENV: test
  MYSQL_USER: ${MYSQL_USER}
  MYSQL_PASSWORD: ${MYSQL_PASSWORD}
  MYSQL_HOST: ${DATABASE_HOST}
  MYSQL_PORT: 3306
jobs:
  prepare:
    name: prepare
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      # Checkout and install dependencie
      - name: Check out repository code
        uses: actions/checkout@v2

      - name: Prepare Ruby and Gems
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: 2.6.7
          bundler-cache: true
      # - name: Setup Database
      #   env:
      #     MYSQL_ROOT_PASSWORD: password
      #   run: |
      #     mysql.server start
      #     gem install bundler
      #     ./bin/rails db:create
      #     ./bin/rails db:migrate
     
      - name: Prepare Node
        uses: actions/setup-node@v2
        with:
          node-version: '12.x'
          cache: 'yarn'
      - name: yarn
        run: yarn --frozen-lockfile

  build-test:
    name: auto build-test
    needs: prepare
    runs-on: ubuntu-latest
    timeout-minutes: 10
    services:
      mysql:
        image: mysql:5.6
        ports:
          - 3306:3306
        env:
          MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
    
    steps:
      - name: Check out repository code
        uses: actions/checkout@v2

      - name: Prepare Ruby and Gems
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: 2.6.7
          bundler-cache: true

      - name: Prepare Node
        uses: actions/setup-node@v2
        with:
          node-version: '12.x'
          cache: 'yarn'
      - name: yarn
        run: yarn --frozen-lockfile

      # - name: Setup database
      #   env:
      #     RAILS_ENV: test
      #   run: |
      #     mysql.server start
      #     gem install bundler
      #     ./bin/rails db:create
      #     ./bin/rails db:migrate
      # Check vulnerabilit
      - name: Run Breakman
        run: |
          bundle exec bundler-audit --update
          bundle exec brakeman -q -w2
      # Run Linter
      - name: Run Rubocop
        run: |
          bundle exec rubocop --parallel
      # Run RSpec (added later)

  # deploy:
  #   name: auto deploy to heroku
  #   needs: [prepare, build-test]
  #   runs-on: ubuntu-latest
  #   timeout-minutes: 10
  #   steps:
  #     - uses: actions/checkout@v2
  #     - uses: akhileshns/heroku-deploy@v3.12.12 # This is the action
  #       with:
  #         heroku_api_key: ${{secrets.HEROKU_API_KEY}}
  #         heroku_app_name: ${{ secrets.HEROKU_APP_NAME }}
  #         heroku_email: ${{secrets.HEROKU_EMAIL}}
  #         # TODO: add a route for health check
  #         healthcheck: "<https://preliquo.herokuapp.com>"

現状打破策①

set databaseを削除。

MySQL関連のエラーが永遠に出現したので、今回はGithub Actionにデータベースをチェックする記述を入れることを断念した。

現状打破策②

デプロイは自分でやる。

下記エラーが出てきて、そのままコピペでググったところ、github のissueが立っていた。

Error: Command failed: git push heroku HEAD:refs/heads/main --force

読んだところ、デプロイは自分でやるのがベストと記載されていたので、github actionsに導入するのを諦めることにした。

Error: Error: Command failed: git push heroku HEAD:refs/heads/main --force · Issue #68 · AkhileshNS/heroku-deploy

今後の意気込み

絶対デプロイもGithub Actionのワークフローに入れたる!

参考文献

RailsアプリケーションでGithub Actionsを試す

戦ったエラー等

Can't connect to local MySQL server through socket '/tmp/mysql.sock'

'/tmp/mysql.sock'からMySQLに接続できません。

mysqlが起動できない(Can't connect to local MySQL server through socket '/tmp/mysql.sock' (2)) - Qiita

$ mysql -v ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO)

root@localhostでアクセスできません。

ERROR 1045 (28000): Access denied for userとなったときの対応方法 - Qiita

The server quit without updating PID file (/usr/local/var/mysql

  1. そもそもpidファイルが存在していない。
  2. pidファイルに適切な権限が設定されていない

mysql 起動時のThe server quit without updating PID file エラーの回避法 - Qiita

mysqld daemon not started

原因: 古いバージョンのmysqlデータが残っていた。

最初はmysql8をダウンロードしていて、色々不具合が生じて面倒だったので、グレードを落としてmysql5.6に変更した。

記事によると、sudoを使ってディレクトリを削除すればいけるとのこと。

実際にやってみた。

$ sudo rm -rf /usr/local/mysql
$ sudo rm -rf /Library/StartupItems/MYSQL
$ sudo rm -rf /Library/PreferencePanes/MySQL.prefPane
$ sudo rm -rf /Library/Receipts/mysql-.pkg
$ sudo rm -rf /usr/local/Cellar/mysql*
$ sudo rm -rf /usr/local/bin/mysql*
$ sudo rm -rf /usr/local/var/mysql*
$ sudo rm -rf /usr/local/etc/my.cnf
$ sudo rm -rf /usr/local/share/mysql*
$ sudo rm -rf /usr/local/opt/mysql*
$ echo 'export PATH="/usr/local/opt/mysql@5.6/bin:$PATH"' >> ~/.bash_profile
$ source ~/.bash_profile
$ /usr/local/opt/mysql@5.6/bin/mysql.server start

Starting MySQL
. SUCCESS!

いけた。

Please remove the file manually and start /usr/local/Cellar/mysql@5.6/5.6.51/bin/mysqld_safe again

ファイルを自分で消して、また/usr/local/Cellar/mysql@5.6/5.6.51/bin/mysqld_safeを打ってください。

Can't connect to local MySQL server through socket '/tmp/mysql.sock' (38)

「'/tmp/mysql.sock' (38)を経由してMySQLサーバーにアクセスできません。」

$ ps -ef | grep mysql
501 64915 60061   0  3:08PM ttys003    0:00.00 grep mysql
$ ls -la /usr/local/var/mysql
total 221248
drwxr-xr-x  10 _mysql  _mysql       320 10 13 15:07 .
drwxrwxr-x   9 subaru  admin        288 10 13 12:33 ..
-rw-r-----   1 _mysql  _mysql     28337 10 13 15:07 Subarunookpuro3.err
-rw-rw----   1 _mysql  _mysql        56 10 13 12:42 auto.cnf
-rw-rw----   1 _mysql  _mysql  50331648 10 13 15:07 ib_logfile0
-rw-rw----   1 _mysql  _mysql  50331648 10 13 12:33 ib_logfile1
-rw-rw----   1 _mysql  _mysql  12582912 10 13 15:07 ibdata1
drwx------  81 _mysql  _mysql      2592 10 13 12:33 mysql
drwx------  55 _mysql  _mysql      1760 10 13 12:33 performance_schema
drwx------   2 _mysql  _mysql        64 10 13 12:33 test
$ sudo chown -R $USER /usr/local
chown: /usr/local: Operation not permitted

解決方法

Mysqlをリスタートさせる。

$ mysql.server restart
 ERROR! MySQL server PID file could not be found!
 Starting MySQL
.. SUCCESS!

mysqlが起動できない - Qiita

MySQL起動エラーの対処の仕方【Can't connect to local MySQL server through socket '/tmp/mysql.sock' (38)】 - Qiita

解決法

再インストールする。

ターミナル

$ brew uninstall mysql@5.7
$ brew install mysql@5.7

現在起動しているmysqlを確認

$ ps ax | grep mysqld

mysqlをshutdown

mysqladmin --host=192.168.10.100 shutdown
$ docker-compose down --volumes
$ docker-compose up -d --build

GitHub Actionsを使ったRSpec実行環境をつくるときにはまったエラーと対処法

Mysql2::Error::ConnectionError: Access denied for user

Mysql2::Error::ConnectionError: Access denied for user 'Root'@'localhost' (using password: YES)

Mysql2::Error::ConnectionError: Access denied for user 'root'@'localhost' (using password: YES)への対処 - Qiita

ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)

ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)

MySQLでmysqld.sockのエラーが出た (Ubuntu16.04) - Libra Studio エンジニアブログ

MySQLに接続できません...|teratail

v-slotの使い方が知りたい。

早くGithub Actionsを実装して脳死デプロイができるようになりたいです。

今回は先人PFによく出てくるv-slotについて学習していきたいと思います。

 

v-slotとは

親となるコンポーネント側から、子のコンポーネントのテンプレートの一部を差し込む機能

また親子間の値のやり取りに関するディレクティブが出現してきた。親⇨子というこは、propsと同じなのかな?

propsとの違い

propsは基本的に値を渡すのに対して、slotでは描画内容を渡します。

(https://greko-engineer.hateblo.jp/entry/2019/11/30/195237)

値だけでは無く、タグそのものも渡すことができるってわけかな。

API - Vue.js

どんな旨みがあるの?

この部分に、こういう役割のものを表示したい。けれど、どのように表示するかは、このコンポーネントを使う側で決めてほしい」時に旨みがでる。

ユーザーのログイン状態に合わせてヘッダーの状態を変えるときに使える。

例えばv-ifを使ってアクションの状態に応じてボタンの状態を変えたい場合は、下記のように条件分岐を書くが、これだと条件分岐の数が多くなってしまう。

<template>
  <div v-if="currentScreen === 'home' ">
    <star-button/>
  </div>
  <div v-else-if="currentScreen === 'search' ">
    <setting-btn/>
  </div>
</template>

だから、slotを使って、親コンポーネントに描画する内容を任せたい場面が出てくる。

書き方は?

子に渡したい値を子コンポーネント名で囲む。

#src/Parent.vue
<template>
  <div class="home">
    <DefaultLayout>酒ケジュールを提供します。</DefaultLayout>
#子コンポーネントに書かれている「最強のアルゴリズムを採用しています。」が優先される。
  </div>
</template>

<script>
import DefaultLayout from '../components/DefaultLayout.vue'
export default {
  components: {
    DefaultLayout
  }
}
</script>

slotを使わないと、コンポーネントに書かれている値が優先される。

この値をフォールバックコンテンツと言う。

#src/components/DefaultLayout

<template>
  <div class="DefaultLayout">
   <p>最強のアルコリズムを採用しています。</p>
  </div>
</template>
<style>
</style>
#表示される内容
最強のアルコリズムを採用しています。

コンポーネント側のテンプレートに<slot>タグを記述すると、その場所ではスロットコンテンツが埋め込まれる。

#src/Parent.vue
<template>
  <div class="home">
    <DefaultLayout>酒ケジュールを提供します。</DefaultLayout>
#子でslotが使われているため、「酒ケジュールを提供します。」が優先される。
  </div>
</template>

<script>
import DefaultLayout from '../components/DefaultLayout.vue'
export default {
  components: {
    DefaultLayout
  }
}
</script>

親の状態に合わせて描画内容を変えたい場所をslotで囲む。

#src/components/DefaultLayout

<template>
  <div class="DefaultLayout">
   <p><slot>最強のアルコリズムを採用しています。</slot></p>
  </div>
</template>
<style>
</style>
#表示される内容
酒ケジュールを提供します。

親側でスロットコンテンツ(DefaultLayoutで囲まれている部分)が定義されていた場合は、

<slot>タグで囲まれたコンポーネント側のコンテンツ(「最強のアルコリズムを採用しています。」)は表示されず、親側のスロットコンテンツが表示される。

名前付きスロット

複数のスロットがあると便利なときもある。

<default-layout> コンポーネントが下記のようなテンプレートだった場合を考える。

#src/components/DefaultComponent.vue

<div class="container">
  <h3>
    <!-- ここに英語のコンテンツ -->
  </h3>
  <h3>
    <!-- ここに日本語のコンテンツ -->
  </h3>
</div>

複数のslotを追加することで、親コンポーネントで呼び出した時に、動的にコンテンツを描写することができる。

#src/components/DefaultComponent
<div class="container">
  <h3>
    <slot name="eng">I love you.</slot>
  </h3>
  <h3>
    <slot name="jpn">ありがとう、私もよ。</slot>
  </h3>
</div>

name のない <slot> 要素は、暗黙的に「default」という名前を持つ。

<template> に対して v-slot ディレクティブを使って、スロット名を引数として与える。

そうすることで、slotにコンテンツを渡すことができる。

#src/Parent.vue

<DefaultLayout>
</DefaultLayout>

<DefaultLayout>
  <template v-slot:eng>
    <h3>I hate you.</h3>
  </template>
</DefaultLayout>

<DefaultLayout>
  <template v-slot:jpn>
    <h3>悲しいわ。</h3>
  </template>
</DefaultLayout>

<script>
import DefaultLayout from '../components/DefaultLayout.vue'
export default {
  components: {
    DefaultLayout
  }
}
</script>

表示される内容

I love you.
ありがとう、私もよ。

I hate you.
ありがとう、私もよ。

I love you.
悲しいわ。

名前つきslotの省略記法

v-bind を 「:」、v-onを「@」で省略できるように、v-slotも「#」で省略して記述することができる。

#src/Parent.vue

<DefaultLayout>
</DefaultLayout>

<DefaultLayout>
  <template #eng>
    <h3>I hate you.</h3>
  </template>
</DefaultLayout>

<DefaultLayout>
  <template #jpn>
    <h3>悲しいわ。</h3>
  </template>
</DefaultLayout>

<script>
import DefaultLayout from '../components/DefaultLayout.vue'
export default {
  components: {
    DefaultLayout
  }
}
</script>

スコープ付きslot

スコープを使うと、スロットコンテンツから、子コンポーネントの中だけで利用可能なデータにアクセスできる。

コープ付きslotの記述方法

スコープ付きslotを利用するには、子コンポーネント側では、<slot>タグに対してv-bindをしようする必要がある。

コンポーネント内でスロットコンテンツとして user を使えるようにするために、<slot> 要素の属性として user をバインドする:

#src/components/CurrentUser.vue
<span>
  <slot v-bind:user="user">
    {{ user.lastName }}
  </slot>
</span>
<script>
export default {
  name: 'CurrentUser',
  data () {
    return {
      user: {
        lastName: 'nakano',
        firstName: 'suabaru' // ←slot内で参照したいデータ
      }
    }
  }
}
</script>

<slot> 要素にv-bindでバインドされた属性をスロットプロパティ と呼ぶ。

親スコープ内で v-slot の値として名前を指定することで、スロットプロパティを受け取ることができる。

<current-user>
  <template v-slot:default="slotProps">
    {{ slotProps.user.firstName }}
  </template>
</current-user>

slotProps は自分の好きな名前に変換することができる。

まとめ

  • v-slotを使うことで、描画したい内容を親コンポーネントに任せることができる。
  • 動的に値を変更したい箇所は<slot>で囲む
  • v-slotは#,v-onは@,v-bindは:で省略することができる。
  • 酒ケジュールとは、お酒の飲む順番の造語で、酒 + スケジュールを掛け合わせた言葉である。

スロット - Vue.js

https://greko-engineer.hateblo.jp/entry/2019/11/30/195237

Vue.jsのslotの機能を初心者にわかるように解説してみた | フューチャー技術ブログ

Vuexに関する用語集

状態を管理してくれるらしい。状態って何。分からない。

いつも通り、アウトプットありきのインプットをしていこうと思う。

Installation | Vuex

Vuexとは

状態管理ライブラリ。データフローが単方向になるように設計されている。

大きなアプリケーション開発に使われることが多いらしい。

例えばログインの状態を全てのページで管理したい場合、このライブラリが使えるらしい。

コンポーネントで定義していた算出メソッドやルーティング等をStoreという場所に集約している。

mapGettersやgettersを使うことで、コンポーネントの受け渡しをスムーズにしてくれている。

this.$store.getters.メソッド名

Store

Vuexの根幹部分。1アプリにつき最大1つ存在する。

new Vuex.Store({⇦ここ
	state{},
	getters{},
	mutations{},
	actions{},
})

この中に大きく4つのメソッドがある。

・state ・getter(値を返す) ・mutation(stateを更新する) ・action(非同期処理や外部APIとのやりとりを行う

この4つそれぞれが働きあってコンポーネント間で値の状態管理がうまい具合に行われているとのこと。

下記のような流れで値の状態管理が行われいる。

コンポーネント⇨②アクション⇨③ミューテーション⇨④ステート⇨⑤ゲッター①に戻る

コア機能

State

アプリケーションレベルの状態が全て含まれており、"信頼できる唯一の情報源 (single source of truth)" として機能します

使い方

// let's create a Counter component
const Counter = {
  template: `<div>{{ count }}</div>`,
  computed: {
    count () {
      return store.state.count
    }
  }
}

store.state.countが変化するたびに、computedにあるpropertyがリアクティブに変化する、それに連動して、DOMが更新される。

rootディレクトリ(storeファイル)に下記コードを記述することで、子コンポーネントの変化を全てのコンポーネントに自動で反映させることができる。this.はself.と同じ感じ?

const Counter = {
  template: `<div>{{ count }}</div>`,
  computed: {
    count () {
      return this.$store.state.count
    }
  }
}

this.$storeを前につけることで、Storeにアクセスできる。

Getters

Storeに入っている算出プロパティやルーティングにアクセスできる中継地点。その名の通り、ゲッターとしての役割を持つ。

例えばこの算出プロパティを他のコンポーネントで使用したい時があるとする。

computed: {
  doneTodosCount () {
    return this.$store.state.todos.filter(todo => todo.done).length
  }
}

ここでgettersの登場。

const store = new Vuex.Store({
  state: {
    todos: [
      { id: 1, text: '...', done: true },
      { id: 2, text: '...', done: false }
    ]
  },
  getters: {
    doneTodos: state => {
      return state.todos.filter(todo => todo.done)
    }
  }
})

他のコンポーネントは、computedの中にthis.$store.getters.doneTodosを書き込むだけで、doneTodesを使用することができる。

mapGetters

...mapGettersを使うことで、$store.gettersを省略することができる。

this.$store.getters.getter1 //通常
this.getter1 //mapGetters使用

実際の使い方。

import { mapGetters } from 'vuex'

export default {
  // ...
  computed: {
    // ゲッターを、スプレッド演算子(object spread operator)を使って computed に組み込む
    ...mapGetters([
      'doneTodosCount',
			#本来はthis.$store.getters.doneTodosCountと書く必要がある。
      'anotherGetter',
      // ...
    ])
  }
}

Mutations

特徴

  • 同期処理である必要がある。
  • stateを変更することができる。
  • 直接呼び出すことができないので、store.commitにミューテーション名や引数(ペイロードと呼びます)を与えて呼び出すことでstateを更新することができる。
const store = new Vuex.Store({
  state: {
    count: 10
  },
  mutations: {
    increment (state) {
      // 状態を変更する
      state.count++
    }
  }
})
console.log(store.state.count)//→10
store.commit('increment')//incrementミューテーションを呼び出す
console.log(store.state.count)//→11

store.commitの第二引数に値を与えると渡される。(この値をペイロードと呼ぶ)

mutations:{
    increment(state,payload){
        state.count = state.count + payload.amount
     }
}
console.log(store.state.count)//→10
store.commit('increment',{amount:5})//
console.log(store.state.count)// →15

Actions

下記の特徴を持ちます。

  • アクションは、状態を変更するのではなく、ミューテーションをコミットします。
  • アクションは任意の非同期処理を含むことができます。
  • 非同期処理や外部APIとの通信を行い、最終的にミューテーションを呼び出すために使う
//アクションを定義
actions:{
    incrementAction(ctx){
//incrementミューテーションを実行する
        ctx.commit('increment')
     }
 }

ミューテーションと同様に直接呼び出すことはできない。

だから、store.dispatchにアクション名を与えて呼び出す。

#初期の値
console.log(store.state.count)// -> 10
#直接アクセスすることができないので、dispatchを使用する。
store.dispatch('incrementAction')//incrementActionを呼び出す
#もう一度呼び出してみる。
console.log(store.state.count)// ->11

Redux,Flux

Flux(フラックス)はFacebook社が提唱している、クライアントサイドのWebアプリケーション開発のためのアプリケーション・アーキテクチャ(設計思想)です。 単方向のデータフローを構築できることが最大の特徴で、開発の規模が大きくなってもデータの流れを見失いづらいことが大きなメリットです。

ReduxはReactの状態管理ライブラリ。

もともとVuexはfacebook社が開発したFluxという技術からインスパイアされている。

まとめ

  • Vuexは状態管理ライブラリ。データフローが単方向になるように設計されている。
  • Storeという大きな箱の中に4つのプロパティを保管している。
  • Stateでアプリの状態を管理している。
  • GettersはStoreに入っている算出プロパティやルーティングにアクセスできる中継地点。
  • ...mapGettersを使うことで、$store.gettersを省略することができる。
  • Mutationsで値を更新することができる。
  • Actionsで外部APIを叩くことができる。

ステート | Vuex

ゲッター | Vuex

アクション | Vuex

ミューテーション | Vuex

Vuexをわかりやすく知りたい|あきな@旅、本、プログラミング。|note

親子コンポーネント間の受け渡し

コンポーネントを渡す流れが分からない、、

props?$emit?他のファイルに値を渡す作業って難しい。

どうやらpropsと$emitという概念があるらしい。

文献を参考にして頭に入った感を味わうために今日もVueの学習を進めていく。

 

props

配列でdataのプロパティを渡す

親⇨子にコンポーネントを渡すときはpropsを使う。

紛らわしいのが、親⇨子という流れなのに、コンポーネントをインポートしているのは親だということ。日本語的に、コンポーネントを「渡される」対象が子なら、子がインポートするのが普通なのではと思う。

#src/components/Child.vue

<template>
  <div>
    <p>{{ statement }}</p>
  </div>
</template>

<script>
export default {
  props: {
    statement: {
     type: String,
     default: 'hogehoge'
    }
  }
#propsの名前と型を子コンポーネント内で定義している。
}
</script>

補足:

propsの名前と型を子コンポーネント内で定義している。

#src/pages/App.vue
<template>
  <div>
    <h1>Hello from App.vue</h1>
    <Child statement='choose to go to the moon.'/>
#ChildのgreetをHello with propsという値に上書きしている。
  </div>
</template>

<script>
import Child from './components/Child.vue'
#子コンポーネントを親コンポーネントがインポートしている。
export default {
  components: {
    Child
#childにコンポーネントを渡している。
  }
}
</script>

補足:

ChildのgreetをHello with propsという値に上書きしている。

コンポーネントを親コンポーネントがインポートしている。

ポイント

$emit

コンポーネントのデータを親コンポーネントへ渡し、親コンポーネントのイベントを発火させることができる。

子⇨親にコンポーネントを渡している。

src/components/Child.vue
<template>
  <div>
    <p>subordinate_num: {{ subordinate_num }}</p>
    <button @click='send'>君主に値を渡す</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      subordinate_num: 0
    };
  },
  methods: {
    send() {
      this.$emit("my-click", this.subordinate_num);
 #親コンポーネントでは、子で定義していたsend()アクションをmy-clickという名前で実行できる。
    }
  }
};
</script>

補足:

	this.$emit("my-click", this.subordinate_num);

とすることで、親コンポーネントでは、子で定義していたsend()アクションをmy-clickという名前で実行できる。

#src/pages/App.vue
# パターン1:受け取った値をそのまま使う場合は$eventで受け取る
<template>
  <div>
    <h1>Hello from App.vue</h1>
    <p>lord_num: {{ lord_num }}</p>
    <Child @my-click='lord_num = $event'/>
#受け取った値を@に続いた形で記述。
#バインドさせた値 = $eventと書くことで、そのまま使用できる。
  </div>
</template>

<script>
import Child from './components/Child.vue'
export default {
  data() {
    return {
      lord_num: 100
    }
  },
  components: {
    Child
  }
}
</script>

補足:

受け取った値を@に続いた形で記述。 バインドさせた値 = $eventと書くことで、そのまま使用できる。

src/App.vue
# パターン2. 受け取った値を関数で使う場合は、適当な変数(value)を定義するとそこに値が入る
<template>
  <div>
    <h1>Hello from App.vue</h1>
    <p>lord_num: {{ lord_num }}</p>
    <Child @my-click='citedNum'/>
  </div>
</template>

<script>
import Child from './components/Child.vue'
export default {
  data() {
    return {
      lord_num: 100
    }
  },
  components: {
    Child
  },
  methods: {
    citedNum(value) {
      this.lord_num = value
    }
  }
}
</script>

ポイント

  • コンポーネント内で、$emitでカスタムイベント(clickとかchange的な使われ方をするやつ)を作る
    • 第2引数に送信するデータを渡す
  • コンポーネント内で子コンポーネントを呼び出す時に、作成したカスタムイベントを付与する
  • $eventや引数で送信されたデータを受け取る

まとめ

props

$emit

  • コンポーネント内で、$emitでカスタムイベント(clickとかchangeのような使われ方をするもの)を作る
    • 第2引数に親に送信するデータを渡す
  • コンポーネント内で子コンポーネントを呼び出す時に、作成したカスタムイベントを付与する
  • $eventや引数で送信されたデータを受け取る

propsと$emitでデータを引き渡す - Qiita

コンポーネントの基本 - Vue.js

A Guide to Vue $emit - How to Emit Custom Events in Vue

v-modelディレクティブ

v-〇〇の種類が多くて頭がパンクしそうです。脳をオンプレミスからクラウドに移行したいことスバルです。今回はv-modelについて学習していきます。

v-modelとは

dataオブジェクトのプロパティの値とinputタブの入力値や選択値のデータを連動することができる。

(参考: https://qiita.com/shizen-shin/items/d9f8c8c9a74618c0b326)

form の input 要素 や textarea 要素、 select 要素に双方向 (two-way) データバインディングを作成できる。双方向バインディングとも呼ばれる。

ユーザーの入力に応じて等のイベントに基づいて、バインディングしたデータをリアクティブデータに反映させる処理が肝。

注意点

任意の form 要素にある value、checked、または selected 属性の初期値を無視する。

使用例

case) textarea

HTML
<div id = "app">
<span>Multiline message is:</span>
<p style="white-space: pre-line;">{{ message }}</p>
<br>
<textarea v-model="message" placeholder="add multiple lines"></textarea>
</div>
js
var app = new Vue({
    el:'#app',
    data:{
        message: ""
    }
})

textareaに入力した内容が、messageの部分に表示される。

case) radio button

<input type="radio" v-model="プロパティ名" value="固有の値">

プロパティ名:'チェックしたい要素のvalue'

html
<div id="app">
        <div>
            ■ラジオボタンとv-model
            <br><input type="radio" id="good" v-model="radio" value="good">
            <label for="good">good</label>

            <input type="radio" id="normal" v-model="radio" value="normal">
            <label for="normal">normal</label>

            <input type="radio" id="bad" v-model="radio" value="bad">
            <label for="bad">bad</label>

            <p>プロパティの状態: {{radio}}</p>

        </div>
   </div>
var app = new Vue({
    el:'#app',
    data:{
        //ラジオボタン
        radio: 'bad'
    }
})

修飾子

.lazy

inputに入力して、エンターなどを押した後に値が反映される。

遅延評価するときは大体lazy

<input v-model.lazy="msg">

.number

ユーザの入力を Number として自動的に型変換したいときに使う。

<input v-model.number="age" type="number">
#上下矢印とともに、数字が際限なく表示される

.trim

ユーザーが入力した空白を取り除くことができる。

<input v-model.trim="msg">

v-bindとv-modelの違い

v-bind は Model の値を HTML コンポーネントに反映(出力)します。HTML コンポーネントの値が変わっても、Model の値は変わりません。Model から HTML への一方通行です。

一方、v-model は Model と View(HTML)の双方向に影響します。 HTML コンポーネントの値に変更があった場合、自動で Model の値を更新します。

入力内容がモデルに反映されるのがv-modelらしい。束縛か、相思相愛ってことか。

<div id="app">
        v-bind[{{ data1 }}]
        <div><input type="text" v-bind:value="data1"></div>
        <br><br>
        v-model[{{ data2 }}]
        <div><input type="text" v-model="data2"></div>
    </div>
var app = new Vue({
    el: '#app',
    data: {
        data1: "data1",
        data2: "data2"
    }
})

data1に何かを入力しても、初期値から変化はない。

一方で、data2に何かを入力したら、何かに沿って変化する。

ラジオボタン(input[type="radio"])を操作する [Vue.js]

【Vue.js】「フォーム入力バインディング」である「v-model」について

ライフサイクルフックとは

他の方のポートフォリオを見ていると、mountedやcreatedなど、見知らぬ用語が頻出してちんぷんかんぷん。調べてみると、Vueにはライフサイクルフックなる概念があり、理解したいと思ったから学習する。

ライフサイクルフックとは

Vueが初期化されてから削除されるまでの一連の流れのこと。

new Vueから始まり、destroyで終わる。Vueの一生のこと。Vue.js公式に載っている図を見ると理解しやすい。f:id:subaru-hello:20211008165452p:plain

ざっくりした流れ

new Vue()を作成

beforeCreateで、初期化前の処理を記入

initで初期化される

createdでAPIを叩いたり、ローディング画面を実装

elによって指定された箇所があるかを識別

elがない場合、mount関数が実行される

template部分があるかを識別し、renderとくっつける

or

templateがない場合、elオプションで定義されたDOMをtemplateとしてコンパイルする。

mountでインスタンスのelement(vm.$el)を作成し、elオプションに置換する

 

(Vue インスタンス — Vue.js)

主要項目の説明

new Vue()

vueの初期化を行う処理。

主に、hello_vue.jsのようなメインファイルに記述することで、virtual DOMの構築をする。下記のように書く。

import Vue from 'vue'
import App from '../app.vue'

document.addEventListener('DOMContentLoaded', () => {
  const app = new Vue({
    router,
    render: h => h(App)
  }).$mount()
  document.body.appendChild(app.$el)
})

created

初期化の後に実行される。

APIを呼んでサーバなどからのデータ取得処理を書くことが多い

  • インスタンスの初期化が済んで props や computed にアクセスできる
  • DOMにはアクセスできない

( 参考文献: https://www.kimullaa.com/entry/2019/12/01/132724)

使用例

new Vue({
  data: {
    a: 1
  },
  created: function () {
    // `this` は vm インスタンスを指します
    console.log('a is: ' + this.a)
  }
})
// => "a is: 1"
  1. new VueでVueインスタンスを作成(以降vmで表現できる?)
  2. createdでdataにアクセス
  3. console.logでメソッドを実行

mounted

VueインスタンスとDOMが紐付けされる。

this.$elによってDOMが作成される。createdだとまだDOMにはアクセスできないが、mountedの段階だとアクセスすることができる。

https://qiita.com/kaorina/items/bb261a119b9f02e02c2d

https://greko-engineer.hateblo.jp/entry/2019/12/21/183509

createdとmountedの違い

createdはDOMがまだ作られていない状態で、mountedではDOMが作成された直後の状態です。

頭に画像をイメージしながら実装できると、scriptが描きやすそう。

el

el: '#app'は、documentElementById('app')というDOM操作をしているのと同義

基本形

#appの中のデータを操作する。

var app = new Vue({
  el: '#app',
  data: {
  },
})

実際に使ってみる

<body>
  <div id="app">
    <button v-on:click="emargeEl">Push Here</button>
  </div>

<script src="<https://cdn.jsdelivr.net/npm/vue/dist/vue.js>"></script>
<script>
new Vue({
  el: '#app',
  data: {
  },
  methods:{
    emargeEl : function(){
     console.log(this.$el.innerHTML = "<h1>Good Night World</h1>")
    }
  }
})
</script> 
</body>
  1. v-on:click="emargeEl"は、クリックしたらemargeElメソッドが実行されることを指している。
  2. console.log(this.$el.innerHTML = "<h1>Good Night World</h1>")で指定した箇所のHTMLを<h1>Good Night World</h1>に変更している。this.$elは#appを指している。

Image from Gyazo

Vue インスタンス - Vue.js

vue.jsのcreatedとmountedの違いを目で見て理解 | アールエフェクト