hysryt / wiki

https://hysryt.github.io/wiki/
0 stars 0 forks source link

Vue.js #23

Open hysryt opened 6 years ago

hysryt commented 6 years ago

var vm = new Vue({ data: d });

vm.a === d.a; // true

vm.a = 2 data.a // => 2

data.a = 3 vm.a // => 3

- `reactivity system`となるプロパティは、Vueインスタンスを生成した時に存在していたもののみ
インスタンスを生成した後に`vm.b = 3`や`data.b = 3`などしても同期されない
- Vueインスタンス生成時にはいくつかのステップを踏むが、そのステップごとに処理を追加できる
`lifecycle hook`という
例えばインスタンスを生成時に処理を追加したい場合は`created`フックを使う
```js
new Vue({
  data: { message: "hello" },
  created: function() {
    console.log('インスタンスを生成しました');
    console.log('「' + this.message + '」');
  }
});

// インスタンスを生成しました
// 「hello」


<div v-bind:class="{ active: isActive }"></div>

上記の場合、isActivetrueなら<div>activeクラスが付与される falseならクラスなし

<div class="active"></div>
<!-- または -->
<div></div>
hysryt commented 6 years ago

Install

$ npm install vue

または

$ yarn add vue
hysryt commented 6 years ago

Binding

var d = {
  message: 'hello'
}
new Vue({
  el: '#app1',
  data: d,
});
<div id="app1">
  {{ message }}
</div>

Vueインスタンスによってeldataをバインドする d.message = 'bye'とすると自動的にメッセージが変わる

デメリット

解決方法

コンポーネントを使用する

hysryt commented 6 years ago

Component

コンポーネントは、テンプレートとデータを持つ。

new Vue(...)で指定したel要素内にコンポーネントのカスタムタグがあると、コンポーネントのテンプレートからDOMを生成し、コンポーネントのデータとバインドする。 カスタムタグは生成したDOMに置き換えられる。

var d = { message: 'hello' };

// コンポーネント定義
// テンプレートとデータを記述する
// テンプレートから実際のDOMが生成され、データとバインドされる
Vue.component('my-message', {
  template: '<span>{{ message }}</span>',
  data: function() {
    return d;
  }
});

// `#app1`内のコンポーネントを有効化する
new Vue({
  el: '#app1',
});
<div id="app1">
  <my-message></my-message> <!-- ← カスタムタグ -->
</div>

コンポーネントはVue.component(...)で定義する

new Vue(...)からdataは消す

HTMLにはコンポーネント名の要素を記述する templateは一つの要素でなければならない。子要素はいくつあっても良い。

hysryt commented 6 years ago

Local Component

Vue.component(...)でコンポーネントを定義するとどこでも使えるグローバルなコンポーネントとなる。 それに対し、new Vue(...)内でコンポーネントを定義すると、特定の要素内のみで使用できるローカルなコンポーネントとなる

var d = { message: 'hello' };

new Vue({
  el: '#app1',

  // ローカルなコンポーネントの定義
  components: {
    'my-message': {
      template: '<span>{{ message }}</span>',
      data: function() {
        return d;
      }
    }
  }
});
<div id="app1">
  <my-message></my-message> <!-- ← カスタムタグ -->
</div>

my-messageコンポーネントは#app要素内でしか使用できない

hysryt commented 6 years ago

Custom Tag Property

カスタムタグに付与した属性をコンポーネントから使用できる

<div id="app1">
  <my-message name="alice"></my-message>
  <my-message name="bob"></my-message>
</div>

name属性をコンポーネントから使用する

new Vue({
  el: '#app1',

  // ローカルなコンポーネントの定義
  components: {
    'my-message': {
      props: ['name'],
      template: '<span>{{ message }}, {{ name }}</span>',
      data: function() {
        return d;
      }
    }
  }
});

propsプロパティに使用する属性名を指定することで、templateに属性値を使用できる

hysryt commented 6 years ago

new Vue(...)でデータとカスタムタグの属性値をバインドする

<my-message v-bind:name="name[0]"></my-message>
<my-message v-bind:name="name[1]"></my-message>

v-bind:属性名="データ名"で属性値とデータをバインドできる この例ではカスタムタグのname属性にname[0]をバインドする name配列はnew Vue(...)で記述する

new Vue({
  el: '#app1',
  data: {
    name: [ 'alice', 'bob' ],  // name[0] と name[1]
  }

  // ローカルなコンポーネントの定義
  components: {
    'my-message': {
      props: ['name'],
      template: '<span>{{ message }}, {{ name }}</span>',
      data: function() {
        return d;
      }
    }
  }
});

v-forで繰り返しできる

<my-message v-for="n in name" v-bind:name="n"></my-message>

name配列の要素数分<my-message>が繰り返される <my-message>name属性にはn in namenが入るため、 一つ目の<my-message>name属性にはalice、 二つ目の<my-message>name属性にはbobが入ることになる

hysryt commented 6 years ago

もしクライアントでテンプレートをコンパイルする必要がある (例えば、 template オプションに文字列を渡す、もしくは DOM 内の HTML をテンプレートとして利用し要素にマウントする) 場合は、コンパイラすなわち完全ビルドが必要です。

hysryt commented 6 years ago

new Vue(...)は一つのページで一つのみ?

Vue アプリケーションは、 new Vue で作成されたルート Vue インスタンス(root Vue instance)で構成され、必要に応じてネストされたツリーや再利用可能なコンポーネントで形成されます。

https://jp.vuejs.org/v2/guide/instance.html


コンポーネントへデータを渡すには明示的な記述が必要 item in listitemも同じ v-forの動作とコンポーネントを分離するため v-bind:ele="item"props: ['ele'] eleは任意の文字列で、これがv-forとコンポーネントをつなぐ

しかしながら、これはいかなるデータもコンポーネントへ自動的に渡すことはありません。なぜなら、コンポーネントはコンポーネント自身の隔離されたスコープを持っているからです。反復してデータをコンポーネントに渡すためには、プロパティを使うべきです

https://jp.vuejs.org/v2/guide/list.html#コンポーネントと-v-for


コンポーネント

コンポーネントは Vue.js の最も強力な機能の 1 つです。基本的な HTML 要素を拡張して再利用可能なコードのカプセル化を助けます。高いレベルでは、コンポーネントは Vue.js のコンパイラが指定された振舞いを加えるカスタム要素です。


v-model

<input v-model="something">

は、以下の糖衣構文です

<input
  v-bind:value="something"
  v-on:input="something = $event.target.value">
hysryt commented 6 years ago

コンポーネントとは

new Vue(...)で指定するelがルートコンポーネントと言える しかしカスタムタグ名はつけられないのでIDでの指定が必要

コンポーネントの登録

Vue.component('my-component', {
  template: '<div>私のコンポーネント</div>'
});

<div>私のコンポーネント</div>をひとつのコンポーネントとしてmy-componentという名前で登録している もちろん入れ子にもできる

Vue.component('my-component', {
  template: '<ul><li>リスト1</li><li>リスト2</li></ul>'
});

ただしルート要素はひとつでなければならない

// ダメな例
Vue.component('my-component', {
  template: '<div>ルート要素が</div><div>2つある</div>'
});

//これなら良い
Vue.component('my-component', {
  template: '<div><div>ルート要素</div><div>で囲う</div></div>'
});

コンポーネント内に他コンポーネントを含むこともできる

Vue.component('my-inner', {
  template: '<div>内部</div>'
});

// my-innerコンポーネントを使用する
Vue.component('my-outer', {
  template: '<div><my-inner></my-inner></div>'
});

データ

コンポーネントはデータも持てる

Vue.component('my-component', {
  template: '<div>{{ message }}</div>'
  data: function() {
    return {
      message: 'hello',
    };
  }
});

dataにはオブジェクトではなくオブジェクトを返す関数を指定することに注意 なぜかというと、コンポーネントが複数回使用された時、データへの参照が共有されるのを防ぐため 関数であればコンポーネント使用の度にデータを生成するため、共有される心配がなくなる


親から子へはプロパティでデータを渡す 子から親へはイベントでデータを渡す

すべてのプロパティは、子プロパティと親プロパティの間の単方向 (one-way-down) バインディングを形成します: 親プロパティが更新したとき、それは子プロパティに伝わり、その反対はありません。

親から子へはプロパティでデータを渡す

親側

<my-component v-bind:something="親が持つデータ変数名"></my-component>

子側

  Vue.component('my-component', {
    props: ['something'],
  }

子から親へはイベントでデータを渡す

親側

<my-component v-on:someevent="onSomeevent"></my-component>
new Vue({
  methods: {
    onSomeevent(param) {
      console.log(param);
    }
  }
});

子側

Vue.component('my-component', {
  created: function() {
    this.$emit('someevent', 'aaaaa');
  }
});

created以外にもmethodsないの関数などからも呼び出せる

hysryt commented 6 years ago

素のjavascript構文で配列やオブジェクトに追加するとVue.jsが値の変更を検知できないため、Vue.jsの用意した構文を使用して追加及び変更をする必要がある

配列へのリアクティブなデータの追加

data: {
  option: [],
}

// pushを使う
this.option.push(...);

オブジェクトへのリアクティブなデータの追加

data: {
  option: {},
}

// $setを使う
this.$set(this.option, キー, 値);

pushthis.option$setthisから呼び出すことに注意

これで追加すれば配列内やオブジェクト内のデータを変更するとVue.jsが検知できるようになる

hysryt commented 6 years ago

ライフサイクル

{{ val }}v-bind:value="val"など、テンプレートに書いた変数へはいつアクセスするか computedプロパティが実行されるタイミングを調べる

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

<script type="text/x-template" id="template-my-app">
  <span>{{ val }}</span>
</script>
Vue.component('my-app', {
  template: '#template-my-app',

  beforeCreate: function() {
    console.log('beforeCreate');
  },

  created: function() {
    console.log('created');
  },

  beforeMount: function() {
    console.log('beforeMount');
  },

  mounted: function() {
    console.log('mounted');
  },

  computed: {
    val: function() {
      console.log('*** computed ***');
      return 0;
    }
  },
});

new Vue({
  el: '#app',
});

結果

beforeCreate
created
beforeMount
*** computed ***
mounted

beforeMountmountedの間でアクセスされる 変数に必要なものはbeforeMountまでに用意しておく


<script type="text/x-template" id="template-my-app">
  <span>{{ option.val }}</span>
</script>
Vue.component('my-app', {
  template: '#template-my-app',

  data: function() {
    return {
      option: {},
    };
  },

  created: function() {
    this.$set(this.option, 'val', 100);
  },
});

dataプロパティのoptionにはvalは存在しないが、createdの時点でvalを追加しているため、テンプレートの{{ option.val }}はちゃんと表示される

hysryt commented 6 years ago

親と子のライフサイクル

初期化時

コード

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

<script type="text/x-template" id="template-my-app">
  <span></span>
</script>
Vue.component('my-app', {
  template: '#template-my-app',

  beforeCreate: function() {
    console.log('beforeCreate - child');
  },

  created: function() {
    console.log('created - child');
  },

  beforeMount: function() {
    console.log('beforeMount - child');
  },

  mounted: function() {
    console.log('mounted - child');
  },
});

var vm = new Vue({
  el: '#app',

  beforeCreate: function() {
    console.log('beforeCreate - parent');
  },

  created: function() {
    console.log('created - parent');
  },

  beforeMount: function() {
    console.log('beforeMount - parent');
  },

  mounted: function() {
    console.log('mounted - parent');
  },
});

結果

beforeCreate - parent
created - parent
beforeMount - parent
beforeCreate - child
created - child
beforeMount - child
mounted - child
mounted - parent
   Parent            Child

[beforeCreate]
      |
  [created]
      |
[beforeMount]
      |
      |           [beforeCreate]
      |                 |
      |             [created]
      |                 |
      |           [beforeMount]
      |                 |
      |              [mount]
      |
   [mount]

親は子を全てマウントしてから自らをマウントする

変更時

コード

<div id="app">
  <my-app :val="val"></my-app>
</div>

<script type="text/x-template" id="template-my-app">
  <span>{{ val }}</span>
</script>
Vue.component('my-app', {
  props: ['val'],
  template: '#template-my-app',

  beforeUpdate: function() {
    console.log('beforeUpdate - child');
  },

  updated: function() {
    console.log('updated - child');
  }
});

結果

vm.val = 1 // データを変更

beforeUpdate - parent
beforeUpdate - child
updated - child
updated - parent
   Parent            Child

[beforeUpdate]
      |
      |           [beforeUpdate]
      |                 |
      |             [updated]
      |
  [updated]
hysryt commented 6 years ago

Vue.js

Vue.jsはデータバインディングを得意とするフレームワーク。 Vue.jsによって用意された変数はDOMにバインドされており、変数の値を書き換えるだけで自動的にDOMも書き換わる。

変数の値の書き換えからDOMの書き換えまで、素のJavascriptでは以下のステップを踏む必要がある

  1. 変数の書き換え
  2. 書き換え対象のDOMの取得
  3. DOMの書き換え

Vue.jsを使えば上記の1.を行うだけで自動的に2.,3.をVue.jsが行ってくれる。 書き換え対象のDOMの数が多くなるほどVue.jsのメリットを享受できる。

ライブラリではなくフレームワークというだけあって、Vue.jsの機能を使う際はVue.jsの用意した記法にのっとって記述する必要がある。

hysryt commented 6 years ago

コンポーネント

Vue.jsで作成したアプリケーションをVueアプリケーションという。 Vueアプリケーションは1つ以上のVueコンポーネントの組み合わせで構成される。

Vueコンポーネントの組み合わせは、ルートコンポーネントを元にしたツリー構造で構成される 全てのVueコンポーネントは複数の子を持つことができ、ルートコンポーネント以外のVueコンポーネントは必ず1つの親を持つ

ルートコンポーネントの作成

ルートコンポーネントはnew Vue({...})で作成する。

new Vue({
  el: '#app'
})

引数のオブジェクトは複数のプロパティを持つことができる。 https://jp.vuejs.org/v2/api/#オプション-データ

elプロパティはマウントするDOM要素を指定する。 マウントすることでデータバインディングが可能となる

一番シンプルなVueアプリケーション

<div id="app">
  {{ message }}
</div>
new Vue({
  el: '#app',
  data: {
    message: 'hello'
  }
})

data.messageプロパティが{{ message }}に出力される

hysryt commented 6 years ago

v-model

双方向バインディングを行うディレクティブ フォームタイプ(テキストボックス、ラジオボタンなど)によって動作が異なる

text

<input type="text" v-model="something">
hysryt commented 6 years ago

トランジション

<div id="app">
  <div>
    <input type="checkbox" v-model="isActive">
  </div>
  <transition name="fade">
    <p v-if="isActive">
      hello
    </p>
  </transition>
</div>  
.fade-enter-active,
.fade-leave-active {
  transition: opacity .5s
}

.fade-enter,
.fade-leave-to  {
  opacity: 0
}
var vm = new Vue({
  el: '#app',
  data: {
    isActive: false,
  }
});

CSSクラス名

以下のクラスがアニメーションの状態によって自動的に付与される

xxx<transition>に指定したname属性値

hysryt commented 6 years ago

jsファイル

開発用

UMD CommonJS ES Module
完全 vue.js vue.common.js vue.esm.js
ランタイム限定 vue.runtime.js vue.runtime.common.js vue.runtime.esm.js

プロダクト用

UMD CommonJS ES Module
完全 vue.min.js - -
ランタイム限定 vue.runtime.min.js - -

https://jp.vuejs.org/v2/guide/installation.html

hysryt commented 6 years ago

Vue.js + webpack

https://jp.vuejs.org/v2/guide/installation.html

準備

$ yarn init -y
$ yarn add vue
$ yarn add webpack -D

webpackの設定

$ vi webpack.config.js
var path = require('path');
var webpack = require('webpack');

module.exports = {
  entry: './script',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'output.js',
  },
  resolve: {
    alias: {
      'vue': 'vue/dist/vue.esm.js',
    },
  },
  plugins: [
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: JSON.stringify('production')
      },
    }),
  ],
};

スクリプト

$ vi script.js
import Vue from 'vue';

new Vue({
  el: '#app'
});
hysryt commented 6 years ago

描画関数

https://jp.vuejs.org/v2/guide/render-function.html https://qiita.com/kazupon/items/2cc9a3427f468866d6dd templateの代わりにrenderを使うことでテンプレートのコンパイル処理を無くすことができ、全体的な処理速度が早くなる。

template

Vue.component('my-elem', {
  data: function() {
    return {
      message: 'hello',
    };
  },
  template: '<h1>{{ message }}</h1>',
});

render

Vue.component('my-elem', {
  data: function() {
    return {
      message: 'hello',
    };
  },
  render: function(createElement) {
    return createElement('h2', this.message);
  },
});

createElement関数

createElement(arg1, [arg2,] arg3)
引数 説明
arg1 {String \| Object \| Function}
タグ名、コンポーネントオプション
arg2 {Object}
データオブジェクト
arg3 {String \| Array}
子のVNode

例1

<div>
  <h1>Hello</h1>
</div>
render: function(createElement) {
  return createElement('div', [
    createElement('h1', 'Hello'),
  ]);
}

例2

<ul>
  <li>apple</li>
  <li>banana</li>
  <li>lemon</li>
</ul>
render: function(createElement) {
  return createElement('ul', [
    createElement('li', 'apple'),
    createElement('li', 'banana'),
    createElement('li', 'lemon'),
  ]);
}

例3

<ul id="foo">
  <li>apple</li>
  <li>banana</li>
  <li>lemon</li>
</ul>
render: function(createElement) {
  return createElement('ul', {
    attr: {
      id: 'foo'
    }
  }, [
    createElement('li', 'apple'),
    createElement('li', 'banana'),
    createElement('li', 'lemon'),
  ]);
}

例4

classstyleは特別 classのBoolean値はデータとバインドが可能

<ul id="foo" class="bar">
  <li>apple</li>
  <li>banana</li>
  <li>lemon</li>
</ul>
render: function(createElement) {
  return createElement('ul', {
    class: {
      bar: true
    },
    attr: {
      id: 'foo'
    }
  }, [
    createElement('li', 'apple'),
    createElement('li', 'banana'),
    createElement('li', 'lemon'),
  ]);
}

attrとprops

'my-elem', {
  attrs: {
    test: 'hello'
  }
}
<my-elem test="hello"></my-elem>

'my-elem', {
  props: {
    test: 'hello'
  }
}
<my-elem :test="'hello'"></my-elem>
hysryt commented 6 years ago

rollupjs で Vue.js

Vue.js + Babel

必要なパッケージ

$ yarn add -D rollup vue rollup-plugin-node-resolve rollup-plugin-replace rollup-plugin-vue vue-template-compiler rollup-plugin-babel @babel/core @babel/preset-env

rollup.config.js

import resolve from 'rollup-plugin-node-resolve'
import replace from 'rollup-plugin-replace'
import vue from 'rollup-plugin-vue'
import babel from 'rollup-plugin-babel';

export default {
  input: 'src/script.js',
  output: {
    file: 'script.js',
    format: 'iife'
  },
  plugins: [
    resolve(),
    replace({
      'process.env.NODE_ENV': JSON.stringify('production'),
    }),
    vue(),
    babel({
      'presets': [
        '@babel/preset-env'
      ],
      'extensions': ['.js', '.vue']
    })
  ]
}

Rollup では process.env.NODE_ENV を使わないので置換する必要がある。

script.js

import Vue from 'vue';
import App from './components/App.vue';

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

ランタイム限定版( vue.runtime.esm.js )を使うため #app への描画を render でおこなう。

App.vue

<template>
  <div>Hello</div>
</template>

index.html

<!doctype html>
<html>
  <head>
    <script src="./script.js" defer></script>
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>
hysryt commented 3 years ago

Vite

https://github.com/vitejs/vite Vue.js用の開発環境、ビルドツール

$ npm init vite-app vue-todo-list
$ cd vue-todo-list
$ npm install
$ npm run dev
hysryt commented 3 years ago

Composition API

Vue.js 3から導入されたコンポーネントの定義方法。 関数ベース。

import { reactive } from 'vue'
export default {
  setup() {
    const state = reactive({
      count: 0,
    })
    const onCountUpClick = () => state.count++
    return {
      state,
      onCountUpClick,
    }
  }
}

@vue/composition-api プラグインを使用すれば Vue.js 2 でも使用できる。 https://www.npmjs.com/package/@vue/composition-api

https://composition-api.vuejs.org/ https://qiita.com/azukiazusa/items/1a7e5849a04c22951e97

Options API

Vue.js 3 以前から使用されていたコンポーネントの定義方法。 Vue.js 3 以降でも使用可能。 オブジェクトベース。

export default {
  data() {
    return {
      state: {
        count: 0,
      }
    }
  },
  methods: {
    onCountUpClick() {
      this.state.count++
    }
  }
}