State management processes within components

The two core functions of Vue are data driven and componentized.

Componentized development brings us:

  • Faster development efficiency
  • Better maintainability

Each component has its own components, such as state, view, and behavior.

State management consists of the following parts:

  • State, the data source driving the application;
  • View, which maps state to the view declaratively;
  • Actions, which responds to state changes caused by user input on the view.

The arrows in the diagram are the direction of the data, and the direction of the data is one-way. State is what we call data, which is bound to the view, presented to the user, and when the user interacts with the view, changes the data through actions, and then rebinds the changed data to the view. A one-way flow of data is particularly clear, but this simple structure can be broken when multiple components share data.

Communication between components

In most scenarios, the components do not stand alone, but work together to form a complex business function. Different communication rules are provided in Vue for different component relationships.

The parent component passes values to the child component

  • The child component receives data via props
  • The parent component passes values to the child component through the corresponding property
/ / child component
<template>
  <div>
    <h1>Props Down Child</h1>
    <h2>{{ title }}</h2>
  </div>
</template>

<script>
export default {
  // props: ['title'],
  props: {
    title: String}}</script>

<style>

</style>
Copy the code
/ / the parent component
<template>
  <div>
    <h1>Props Down Parent</h1>
    <child title="My journey with Vue"></child>
  </div>
</template>

<script>
import child from './Child'
export default {
  components: {
    child
  }
}
</script>

<style>

</style>
Copy the code

The child component passes values to the parent component

Publish a custom event in the child component using $Emit

/ / child component
<template>
  <div>
    <h1 :style="{ fontSize: fontSize + 'em' }">Props Down Child</h1>
    <button @click="handler">Text increase</button>
  </div>
</template>

<script>
export default {
  props: {
    fontSize: Number
  },
  methods: {
    handler () {
      this.$emit('enlargeText'.0.1)}}}</script>

<style>

</style>
Copy the code

When using this component, use v-ON to listen for this custom event

/ / the parent component
<template>
  <div>
    <h1 :style="{ fontSize: hFontSize + 'em'}">Event Up Parent</h1>The text doesn't need to change here<child :fontSize="hFontSize" v-on:enlargeText="enlargeText"></child>
    <child :fontSize="hFontSize" v-on:enlargeText="enlargeText"></child>
    <child :fontSize="hFontSize" v-on:enlargeText="hFontSize += $event"></child>
  </div>
</template>

<script>
import child from './Child'
export default {
  components: {
    child
  },
  data () {
    return {
      hFontSize: 1}},methods: {
    enlargeText (size) {
      this.hFontSize += size
    }
  }
}
</script>

<style>

</style>
Copy the code

Pass values between unrelated components

The value passing between unrelated components also uses custom Event passing, but unlike the child component passing value to the parent component, because there is no parent-child relationship, the child component cannot trigger Event passing value. In this case, the Event Bus is needed to pass value, which is to create a common VUE instance. This vUE instance serves as an event component or event center. Method: eventbus.js:

export default new Vue()
Copy the code

Then at both ends of the communication:

Subscribe with $ON:

// No arguments
bus.$on('Custom event name'.() = > {
  // Execute the operation
})

/ / a parameter
bus.$on('Custom event name'.data= > {
  // Execute the operation
})
Copy the code

Use $EMIT to post:

// There is no custom pass parameter
bus.$emit('Custom event name');

// There are custom pass parameters
bus.$emit('Custom event name', data);Copy the code

Example:

// eventbus.js
// This file only exports a vue instance
// This instance is not used to display content, the purpose is to call its $emit and $on, which are used to fire and register events
import Vue from 'vue'
export default new Vue()
Copy the code
<template>
  <div>
    <h1>Event Bus Sibling01</h1>
    <div class="number" @click="sub">-</div>
    <input type="text" style="width: 30px; text-align: center" :value="value">
    <div class="number" @click="add">+</div>
  </div>
</template>

<script>
import bus from './eventbus'

export default {
  props: {
    num: Number
  },
  created () {
    this.value = this.num
  },
  data () {
    return {
      value: -1}},methods: {
    sub () {
      if (this.value > 1) {
        this.value--
        bus.$emit('numchange'.this.value) // Triggers the event
      }
    },
    add () {
      this.value++
      bus.$emit('numchange'.this.value)
    }
  }
}
</script>

<style>
.number {
  display: inline-block;
  cursor: pointer;
  width: 20px;
  text-align: center;
}
</style>
Copy the code
<template>
  <div>
    <h1>Event Bus Sibling02</h1>

    <div>{{ msg }}</div>
  </div>
</template>

<script>
import bus from './eventbus'
export default {
  data () {
    return {
      msg: ' '
    }
  },
  created () {
    // Register the event
    bus.$on('numchange'.(value) = > {
      this.msg = 'You have chosen${value}Goods `}}})</script>

<style>

</style>
Copy the code

Parent direct access to child components: Get child components through ref

This should only be used when the project is small or when developing custom components; Still use VUEX for large projects.

Ref has two functions:

  • If you apply it to a normal HTML tag, you get the DOM
  • If you apply it to a component label, you get the component instance
/ / child component
<template>
  <div>
    <h1>ref Child</h1>
    <input ref="input" type="text" v-model="value">
  </div>
</template>

<script>
export default {
  data () {
    return {
      value: ' '}},methods: {
    focus () {
      this.$refs.input.focus()
    }
  }
}
</script>

<style>

</style>
Copy the code
/ / the parent component
<template>
  <div>
    <h1>ref Parent</h1>

    <child ref="c"></child>
  </div>
</template>

<script>
import child from './04-Child'
export default {
  components: {
    child
  },
  mounted () {
    this.$refs.c.focus()
    this.$refs.c.value = 'hello input'}}</script>

<style>

</style>
Copy the code

The $refs only take effect after the components are rendered, and they are not responsive. This only serves as an “escape pod” for directly manipulating subcomponents — you should avoid accessing $refs in templates or computed properties.

Simple state management solution

If multiple components want to share the state (data), the above method can be implemented, but it is cumbersome, and it is difficult to track the changes of data by passing values between multiple components, and it is difficult to locate problems if they occur.

A typical scenario when you encounter multiple components that need to share state: shopping cart. If we use the above scheme is not suitable, we will encounter the following problems:

  • Multiple views depend on the same state.
  • Behaviors from different views need to change the same state.

For problem 1, the pass-throughs method would be cumbersome for multiple nested components, and would be ineffective for state transfer between sibling components.

For problem two, we often use parent-child components to refer directly or to change and synchronize multiple copies of the state through events. These patterns are very fragile and often result in unmaintainable code

So why don’t we extract the shared state of components and manage it as a global singleton pattern? In this mode, our tree of components forms a huge “view” where any component can get state or trigger behavior no matter where it is in the tree! We can store the state of multiple components, or the state of the entire program, in a centralized location and can detect changes to the data. You may have already thought of Vuex.

Let’s do it in a simple way first.

  • Start by creating a shared repository store object
// store.js
export default {
  debug: true.state: {
    user: {
      name: 'xiaomao'.age: 18.sex: 'male'
    }
  },
  setUserNameAction (name) {
    if (this.debug) {
      console.log(' 'setUserNameAction triggered., name)
    }
    this.state.user.name = name
  }
}
Copy the code
  • The shared repository store object is stored in the data of the component whose state needs to be shared
<template>
  <div>
    <h1>componentA</h1>
    user name: {{ sharedState.user.name }}
    <button @click="change">Change Info</button>
  </div>
</template>

<script>
import store from './store'
export default {
  methods: {
    change () {
      store.setUserNameAction('componentA')
    }
  },
  data () {
    return {
      privateState: {},
      sharedState: store.state
    }
  }
}
</script>

<style>

</style>
Copy the code

We then extend the convention by stating that the component is not allowed to directly change the state that belongs to the Store object. Instead, the component should execute an action to dispatch an event to notify the Store of the change, thus ending up with a structure similar to Vuex. The benefit of this convention is that we are able to record state changes in all stores while implementing advanced debugging tools that can record changes, save state snapshots, and roll back history/time travel.

Vuex review

What is a Vuex

Vuex is a state management pattern developed specifically for vue.js applications. It uses centralized storage to manage the state of all components of an application, and rules to ensure that the state changes in a predictable manner. Vuex is also integrated into Vue’s official debugging tool, DevTools Extension, providing advanced debugging features such as zero-configuration time-travel debugging, state snapshot import and export, and more.

  • Vuex is a state management library designed specifically for vue.js
  • It stores data that needs to be shared in a centralized manner
  • From a usage point of view, it is a JavaScript library
  • It is used for state management, complex component communication and data sharing

When to use Vuex

Official documents:

Vuex helps manage shared state and comes with more concepts and frameworks. This requires a balance between short – and long-term benefits.

If you don’t plan to develop large, single-page applications, using Vuex can be tedious and redundant. That’s true — if your application is simple enough, you’re better off not using Vuex. A simple store pattern is all you need. However, if you need to build a medium to large single-page application, you are likely to be thinking about how to better manage state outside of components, and Vuex would be a natural choice. To quote Dan Abramov, author of Redux, the Flux architecture is like glasses: You’ll know when you need it.

When your application has the following requirement scenarios:

  • Multiple views depend on the same state
  • Behaviors from different views need to change the same state

It is recommended that businesses that fit this scenario use Vuex for data management, such as the very typical shopping cart scenario.

Note: Don’t abuse Vuex, don’t use the business that does not meet the above requirements, but it will make your application more troublesome.

Core Concept Review

  • Store: The Store is the core of an app that uses Vuex. There is only one Store per app. The Store is a container that contains most of the state in the app. Of course, we can’t change the state of the application directly, we have to change the state by submitting Mutations.
  • State: The State is stored in the Store, and because the Store is unique, the State is also unique, called a single State tree. However, keeping all State in State makes the program difficult to maintain, which can be addressed in a subsequent module. The state here is reactive.
  • Getters: Getters are like the computed properties in Vuex, which are easy to derive from one property, internally cache the computed results, and recalculate them only when the dependent state changes.
  • Mutations: Changes in state require submission of Mutations to complete.
  • Actions: Actions are similar to Mutations, except that Actions can operate asynchronously, resulting in changes of internal state that require submitting Mutations.
  • Module: With a single state tree, all the state of an application is concentrated on a large object. When the app becomes very complex, the Store becomes very bloated. To solve the above problems, Vuex allows Stor to be divided into modules, each of which has its own State, Getter, Mutations, Actions, or even nested submodules.

Example demonstrates

vuex-demo

The basic structure

  • Import Vuex
  • Registered Vuex
  • Inject $store into the Vue instance

State

Vuex uses a single state tree that contains all the state of the application hierarchy in a single object.

Simplify the use of State in views with mapState, which returns computed properties.

MapState can be used in two ways:

  • Receive array parameter
// This method is provided by vuex, so you must import it before using it
import { mapState } from 'vuex'
// mapState returns a computed property named count and MSG
// Use count and MSG in the template
computed: {
  ...mapState(['count'.'msg'])}Copy the code
  • Receiving Object Parameters

If the current view already has a count and a MSG, there will be a name conflict.

// This method is provided by vuex, so you must import it before using it
import { mapState } from 'vuex'
// You can rename the returned computed properties by passing in the object
// Use num and message directly in the template
computed: {
  ...mapState({
    num: state= > state.count, 
    message: state= > state.msg 
  })
}
Copy the code

Getter

Getters are computed properties in stores, and using mapgetters to simplify usage in views

import { mapGetter } from 'vuex'
computed: {
  ...mapGetter(['reverseMsg']),
  // Change the name to reverse in the template. mapGetter({reverse: 'reverseMsg'})}Copy the code

Mutation

The only way to change the state in Vuex’s Store is to submit mutation. Mutations in Vuex are very similar to events: each mutation has an event type (type) of a string and a callback function (handler). This callback function is where we actually make the state change, and it takes state as the first argument.

The advantage of using Mutation to change a state is that a state change made by one location in a set can be traced wherever the change is made. Advanced time-travel debugging capabilities can be implemented

import { mapMutations } from 'vuex'
methods: {
  ...mapMutations(['increate']),
  // Pass object to solve the same name problem. mapMutations({increateMut: 'increate'})}Copy the code

Action

Action is similar to mutation, except that:

  • The Action commits the mutation instead of directly changing the state.
  • An Action can contain any asynchronous operation.
import { mapActions } from 'vuex'
methods: {
  ...mapActions(['increate']),
  // Pass object to solve the same name problem. mapActions({increateAction: 'increate'})}Copy the code

Module

By using a single state tree, all the state of the application is concentrated into one large object. When the application becomes very complex, the Store object can become quite bloated.

To solve this problem, Vuex allows us to split the store into modules. Each module has its own state, mutation, action, getter, and even nested submodules.