Обработка событий

Async Update Queue

By default, Vue.js performs DOM updates asynchronously. Whenever a data change is observed, Vue will open a queue and buffer all the data changes that happens in the same event loop. If the same watcher is triggered multiple times, it will be pushed into the queue only once. Then, in the next event loop “tick”, Vue flushes the queue and performs only the necessary DOM updates. Internally Vue uses if available for the asynchronous queuing and falls back to .


For example, when you set , the DOM will not update immediately. It will update in the next “tick”, when the queue is flushed. Most of the time we don’t need to care about this, but it can be tricky when you want to do something that depends on the post-update DOM state. Although Vue.js generally encourages developers to think in a “data-driven” fashion and avoid touching the DOM directly, sometimes it might be necessary to get your hands dirty. In order to wait until Vue.js has finished updating the DOM after a data change, you can use immediately after the data is changed. The callback will be called after the DOM has been updated. For example:

<div id="example">{{msg}}</div>
var vm = new Vue({  el: '#example',  data: {    msg: '123'
  }})vm.msg = 'new message' 
vm.$el.textContent === 'new message' 
Vue.nextTick(function () {  vm.$el.textContent === 'new message' 
})

There is also the instance method, which is especially handy inside components, because it doesn’t need global and its callback’s context will be automatically bound to the current Vue instance:

Vue.component('example', {  template: '<span>{{msg}}</span>',  data: function () {    return {      msg: 'not updated'
    }  },  methods: {    updateMessage: function () {      this.msg = 'updated'
      console.log(this.$el.textContent) 
      this.$nextTick(function () {        console.log(this.$el.textContent) 
      })    }  }})

Модули

Vuex также предоставляет нам модули, с помощью которых мы можем дополнительно структурировать или разбить наше хранилище на модули. Каждый модуль будет иметь свое собственное состояние, геттеры, мутации и действия.

Это работает путем группировки связанных состояний, геттеров, мутаций и действий в модуль. Это особенно полезно, когда у нас есть крупномасштабное приложение, а в хранилище много кода.

Реорганизовав наше хранилище в модуль, мы создадим файл cart.js и продолжим разбивать все наши состояния, мутации и действия в нашем хранилище, относящиеся к корзине, как показано ниже:

// import Vue
import Vue from 'vue';

export default {
    state: {
        cart: 
    },
  
    getters: {
        // Fetch the total number of items in the cart
        totalNumberOfCartItems: state => {
            return state.cart.length;
        },
    },
    
    mutations: {
        // Add item to cart
        addItemToCart (state, payload) {
            state.cart.push(payload);
        },
        // Clear items in the cart
        emtpyCart (state) {
            state.cart = [];
        }
    },
    
    actions: {
        checkout({commit}, requestObject) {
            return new Promise((resolve, reject) => {
                
                // API Call to submit the items in the cart
                Vue.http.post('submit', requestObject).then((response) => {
                    // log success
                    console.log(response);
                    // Clear Cart by mutating the state
                    commit('emptyCart');
                    // return success
                    resolve(response);
                }).catch((error) => {
                    // log error
                    console.log(error);
                    // return error
                    reject(error);
                }
            })
        }
    }
}

Далее мы импортируем и регистрируем его в нашем главном хранилище.

import Vue from 'vue'
import Vuex from 'vuex'
import state from './state'
import getters from './getters'
import mutations from './mutations'
import actions from './actions'
import cart from './modules/cart'

Vue.use(Vuex)

export default new Vuex.Store({
  state,
  getters,
  mutations,
  actions,
  modules: {
      cart
  }
})

Наконец, наша структура кода будет выглядеть так:

store/
--| store.js
--| state.js
--| getters.js
--| mutations.js
--| actions.js
--| modules/
    --| cart.js

Заключение

Vuex создает хранилище, которое состоит из состояний, геттеров, мутаций и действий. Чтобы обновить или изменить состояние, вы должны совершить мутацию. Чтобы выполнить асинхронную задачу, вам нужно действие. Действия в случае успеха совершают мутацию, которая изменяет состояние, тем самым обновляя представление.

Оригинал: Nosa Obaseki Vuex—The Deep Dive

Spread the love

# コンポーネントをバインドするヘルパー

mapState

mapState(namespace?: string, map: Array | Object): Object ストアのサブツリーを返すコンポーネントの computed オプションを作成します。詳細 第1引数は、オプションで名前空間文字列にすることができます。詳細 第2引数のオブジェクトのメンバーには関数 function(state: any) を指定できます。

mapGetters

mapGetters(namespace?: string, map: Array | Object): Object ゲッターの評価後の値を返すコンポーネントの computed オプションを作成します。詳細 第1引数は、オプションで名前空間文字列にすることができます。詳細

mapActions

mapActions(namespace?: string, map: Array | Object): Object アクションをディスパッチするコンポーネントの methods オプションを作成します。詳細 第1引数は、オプションで名前空間文字列にすることができます。詳細 第2引数のオブジェクトのメンバーには関数 function(dispatch: function, …args: any[]) を指定できます。

mapMutations

mapMutations(namespace?: string, map: Array | Object): Object ミューテーションをコミットするコンポーネントの methods オプションを作成します。詳細 第1引数は、オプションで名前空間文字列にすることができます。詳細 第2引数のオブジェクトのメンバーには関数 function(commit: function, …args: any[]) を指定できます。

createNamespaceHelpers

createNamespacedHelpers(namespace: string): Object 名前空間付けられたコンポーネントバインディングのヘルパーを作成します。返されるオブジェクトは指定された名前空間にバインドされた mapState、mapGetters、mapActions そして mapMutations が含まれます。詳細はこちら

Computed property

Computed property có thể hiểu là một “thuộc tính được tính toán.” Để cho nhất quán, chúng tôi sẽ giữ nguyên cụm từ .

Viết biểu thức trực tiếp trong template rất tiện, nhưng chỉ dành cho những biểu thức có tính toán đơn giản. Những biểu thức phức tạp được viết theo cách đó sẽ khiến template cồng kềnh và khó bảo trì. Ví dụ:

<div id="example">  {{ message.split('').reverse().join('') }}</div>

Đến đây, template không còn đơn giản và mang tính khai báo (declarative) nữa. Bạn sẽ phải mất chút thời gian thì mới nhận ra được đã bị đảo ngược. Càng tệ hơn khi bạn sử dụng biến đảo ngược này nhiều lần trong code.

Đó là lí do tại sao đối với bất kì logic nào phức tạp, bạn nên sử dụng computed property.

Ví dụ cơ bản

<div id="example">  <p>Thông điệp ban đầu: "{{ message }}"</p>  <p>Thông điệp bị đảo ngược bằng tính toán (computed): "{{ reversedMessage }}"</p></div>
var vm = new Vue({  el: '#example',  data: {    message: 'người đông bến đợi thuyền xuôi ngược'  },  computed: {    reversedMessage: function () {      return this.message.split(' ').reverse().join(' ')    }  }})

Kết quả là:


Thông điệp ban đầu: «{{ message }}»

Thông điệp bị đảo ngược (computed): «{{ reversedMessage }}»

Ở đây chúng ta khai báo một computed property là . Hàm mà chúng ta đã cung cấp sẽ được sử dụng như một hàm getter cho thuộc tính :

console.log(vm.reversedMessage) vm.message = 'xa ngân tiếng hát đàn trầm bổng'console.log(vm.reversedMessage) 

Bạn có thể mở console và thử chạy đối tượng vm mẫu ở trên. Giá trị của luôn phụ thuộc vào giá trị của .

Bạn có thể ràng buộc dữ liệu (data-bind) cho computed property trong template một cách bình thường như những thuộc tính khác. Vue biết được phụ thuộc vào nên sẽ cập nhật bất kì ràng buộc (binding) nào phụ thuộc vào khi thay đổi. Điểm hay nhất ở đây là chúng ta tạo ra được mối liên hệ giữa các thành phần phụ thuộc (dependency): các hàm getter của computed thì không bị hiệu ứng phụ (side effect), chính điều đó giúp dễ hiểu và dễ kiểm tra.

Computed caching và phương thức

Bạn có lẽ đã nhận ra chúng ta cũng có thể đạt được cùng một kết quả bằng cách sử dụng một phương thức:

<p>Thông điệp bị đảo ngược: "{{ reverseMessage() }}"</p>
methods: {  reverseMessage: function () {    return this.message.split(' ').reverse().join(' ')  }}

Thay vì sử dụng computed property, chúng ta cũng có thể dùng một phương thức thay thế. Nếu xét về kết quả cuối cùng thì hai cách tiếp cận này thât ra chỉ là một. Tuy nhiên, sự khác biệt ở đây là computed property được cache lại dựa vào những những thành phần phụ thuộc (dependency). Một computed property chỉ được tính toán lại khi những thành phần phụ thuộc của chúng thay đổi. Điều này có nghĩa: miễn là giá trị của không thay đổi, thì những truy cập tới computed sẽ ngay lập tức trả về kết quả được tính toán trước đó mà không phải chạy lại hàm một lần nữa.

Điểu này cũng có nghĩa là computed property dưới đây sẽ không bao giờ cập nhật, bởi vì không phải là một thành phần phụ thuộc phản ứng (reactive dependency) :

computed: {  now: function () {    return Date.now()  }}

Để so sánh, một phương phương thức luôn được gọi khi có một sự kiện render lại (re-render) xảy ra.

Tại sao chúng ta lại cần phải cache? Thử tưởng tượng chúng ta có một computed property A có nhiều thao tác tính toán trên một mảng dữ liệu lớn. Chúng ta lại có nhiều computed property phụ thuộc vào A. Nếu không cache lại, chúng ta phải thực thi hàm getter của A nhiều hơn mức cần thiết rất nhiều! Trong trường hợp bạn không muốn cache, hãy sử dụng một phương thức thay thế.

Computed và watched

Vue cung cấp một cách khái quát hơn để quan sát và phản ứng (react) lại những thay đổi trên dữ liệu: watch property. Khi bạn có một số dữ liệu cần được thay đổi dựa trên những dữ liệu khác, bạn rất dễ lạm dụng — nhất là nếu bạn có nền tảng về AngularJS. Tuy nhiên, thường thì bạn nên dùng thay vì . Hãy xem ví dụ sau:

<div id="demo">{{ fullName }}</div>
var vm = new Vue({  el: '#demo',  data: {    firstName: 'Trần',    lastName: 'Lập',    fullName: 'Trần Lập'  },  watch: {    firstName: function (val) {      this.fullName = val + ' ' + this.lastName    },    lastName: function (val) {      this.fullName = this.firstName + ' ' + val    }  }})

Đoạn code phía trên theo hướng mệnh lệnh và lặp lại. Hãy so sánh với phiên bản dùng computed property:

var vm = new Vue({  el: '#demo',  data: {    firstName: 'Evan',    lastName: 'You'  },  computed: {    fullName: function () {      return this.firstName + ' ' + this.lastName    }  }})

Cách này tốt hơn nhiều đúng không?

Computed Setter

Những computed property mặc định chỉ có getter, nhưng bạn cũng có thể cung cấp setter nếu cần thiết:

computed: {  fullName: {    get: function () {      return this.firstName + ' ' + this.lastName    },    set: function (newValue) {      var names = newValue.split(' ')      this.firstName = names[]      this.lastName = names    }  }}

Bây giờ, khi bạn gán , thì setter sẽ được gọi, và sẽ được cập nhật tương ứng.

Content Distribution with Slots


When using components, it is often desired to compose them like this:

<app>  <app-header></app-header>
  <app-footer></app-footer>
</app>

There are two things to note here:

  1. The component does not know what content may be present inside its mount target. It is decided by whatever parent component that is using .

  2. The component very likely has its own template.

To make the composition work, we need a way to interweave the parent “content” and the component’s own template. This is a process called content distribution (or “transclusion” if you are familiar with Angular). Vue.js implements a content distribution API that is modeled after the current Web Components spec draft, using the special element to serve as distribution outlets for the original content.

Compilation Scope

Before we dig into the API, let’s first clarify which scope the contents are compiled in. Imagine a template like this:

<child-component>  {{ msg }}</child-component>

Should the be bound to the parent’s data or the child data? The answer is parent. A simple rule of thumb for component scope is:

A common mistake is trying to bind a directive to a child property/method in the parent template:

<child-component v-show="someChildProperty"></child-component>

Assuming is a property on the child component, the example above would not work as intended. The parent’s template should not be aware of the state of a child component.

If you need to bind child-scope directives on a component root node, you should do so in the child component’s own template:

Vue.component('child-component', {  
  template: '<div v-show="someChildProperty">Child</div>',  data: function () {    return {      someChildProperty: true
    }  }})

Similarly, distributed content will be compiled in the parent scope.

Single Slot

Parent content will be discarded unless the child component template contains at least one outlet. When there is only one slot with no attributes, the entire content fragment will be inserted at its position in the DOM, replacing the slot itself.

Anything originally inside the tags is considered fallback content. Fallback content is compiled in the child scope and will only be displayed if the hosting element is empty and has no content to be inserted.

Suppose we have a component with the following template:

<div>  <h1>This is my component!</h1>
  <slot>
    This will only be displayed if there is no content    to be distributed.  </slot>
</div>

Parent markup that uses the component:

<my-component>  <p>This is some original content</p>
  <p>This is some more original content</p>
</my-component>

The rendered result will be:

<div>  <h1>This is my component!</h1>
  <p>This is some original content</p>
  <p>This is some more original content</p>
</div>

Named Slots


elements have a special attribute, , which can be used to further customize how content should be distributed. You can have multiple slots with different names. A named slot will match any element that has a corresponding attribute in the content fragment.

There can still be one unnamed slot, which is the default slot that serves as a catch-all outlet for any unmatched content. If there is no default slot, unmatched content will be discarded.

For example, suppose we have a component with the following template:

<div>  <slot name="one"></slot>
  <slot></slot>
  <slot name="two"></slot>
</div>

Parent markup:

<multi-insertion>  <p slot="one">One</p>
  <p slot="two">Two</p>
  <p>Default A</p>
</multi-insertion>

The rendered result will be:

<div>  <p slot="one">One</p>
  <p>Default A</p>
  <p slot="two">Two</p>
</div>

The content distribution API is a very useful mechanism when designing components that are meant to be composed together.

What our users are saying

Although I was already using Vue.js professionally for a while, I decided to sign up with Vue School — and I am glad I did.

I had so many Aha! moments as I watched each video and I can say that my knowledge has skyrocketed from where it was before I joined Vue School.

Before I joined Vue School, I found it hard to deeply understand Vuejs.

e.g how should I implement High Order Functions, Modularize the Vuex Store, Vuelidate features, Vue Config Webpack.

I really recommend to join Vue School.

Finally a platform to really learn vue.js that’s not only for beginners!

I love how Vue School implements ES6 in real cases and how active the Slack community is. From time to time the instructors from the CORE team answering crucial questions!

Using Components

Registration

We’ve learned in the previous sections that we can create a component constructor using :

var MyComponent = Vue.extend({  
})

To use this constructor as a component, you need to register it with :

Vue.component('my-component', MyComponent)

Note that Vue.js does not enforce the for custom tag-names (all-lowercase, must contain a hyphen) though following this convention is considered good practice.

Once registered, the component can now be used in a parent instance’s template as a custom element, . Make sure the component is registered before you instantiate your root Vue instance. Here’s the full example:

<div id="example">  <my-component></my-component>
</div>
var MyComponent = Vue.extend({  template: '<div>A custom component!</div>'
})Vue.component('my-component', MyComponent)
new Vue({  el: '#example'
})

Which will render:

<div id="example">  <div>A custom component!</div>
</div>

# 컴포넌트 바인딩 헬퍼

mapState

mapState(namespace?: string, map: Array | Object): Object Vuex 저장소의 하위 트리를 반환하는 컴포넌트 계산 옵션을 만듭니다. 상세 처음 argument는 string 타입의 namespace가 될 수 있습니다. 상세 두번째 오브젝트 argument는 함수가 될 수 있습니다. function(state: any)

mapGetters

mapGetters(namespace?: string, map: Array | Object): Object getter의 평가된 값을 반환하는 컴포넌트 계산 옵션을 만듭니다. 상세 처음 argument는 string 타입의 namespace가 될 수 있습니다. 상세

mapActions

mapActions(namespace?: string, map: Array | Object): Object 액션을 전달하는 컴포넌트 메소드 옵션을 만듭니다. 상세 처음 argument는 string 타입의 namespace가 될 수 있습니다. 상세 두번째 오브젝트 argument는 함수가 될 수 있습니다. function(dispatch: function, …args: any[])

mapMutations

mapMutations(namespace?: string, map: Array | Object): Object 변이를 커밋하는 컴포넌트 메소드 옵션을 만듭니다. 상세 처음 argument는 string 타입의 namespace가 될 수 있습니다. 상세 두번째 오브젝트 argument는 함수가 될 수 있습니다. function(commit: function, …args: any[])

createNamespacedHelpers

createNamespacedHelpers(namespace: string): Object namespace가 적용된 컴포넌트 바인딩 helper를 만듭니다. 주어진 namespace가 적용된 mapState, mapGetters, mapActions mapMutations들을 가지고 있는 오브젝트를 반환합니다. 상세


С этим читают