By yugasun from yugasun.com/post/you-ma… This article can be reproduced in full, but the original author and source need to be retained.

With the foundation of the previous article, I believe that you will be able to develop a medium-sized Vuejs application, including the use of Vuejs ecosystem core tools (VUE-Router, VUEX). But in the actual project development process, we need to do more than just complete our business code, when a requirement is completed, we also need to consider more later optimization work, this article focuses on code level optimization.

Neglected computed properties of setters

Let’s go back to our state management example and use vuex to share our MSG attribute. Create SRC /store/index.js:

import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

const types = {
  UPDATE_MSG: 'UPDATE_MSG'};constmutations = { [types.UPDATE_MSG](state, payload) { state.msg = payload.msg; }};constactions = { [types.UPDATE_MSG]({ commit }, payload) { commit(types.UPDATE_MSG, payload); }};export default new Vuex.Store({
  state: {
    msg: 'Hello world',
  },
  mutations,
  actions,
});
Copy the code

Then use it in component comp1:

<template>
  <div class="comp1">
    <h1>Component 1</h1>
    <input type="text" v-model="msg">
  </div>
</template>
<script>
export default {
  name: 'comp1',
  data() {
    const msg = this.$store.state.msg;
    return {
      msg,
    };
  },
  watch: {
    msg(val) {
      this.$store.dispatch('UPDATE_MSG', { msg: val }); ,}}};</script>
Copy the code

Do the same for COMP2. SRC /main.js: SRC /main.js

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

Vue.config.productionTip = false;

/* eslint-disable no-new */
new Vue({
  store,
  el: '#app'.template: '<App/>'.components: { App },
});
Copy the code

If you do not know the basic usage of VUEX, it is recommended to read the official documentation first.

Ok, we have implemented the MSG sharing and watch its changes. When the input box changes, we will trigger the corresponding UPDATE_MSG actions via $store.dispatch to change the state. However, if you modify the input box in comp1, vue-devTools can also see that the state. MSG in Vuex does change, but the input box in Comp2 does not change. Of course, this is because when we initialize MSG, it is a direct variable assignment. $store.state. MSG is not monitored, so the two components cannot be synchronized.

$store. State. MSG = $store. State. MSG = $store. Wouldn’t it be worth it to add two more listeners?

$store.state. MSG = $store.state. MSG = $store.state.

Ok, modify comp1 as follows:

<template>
  <div class="comp1">
    <h1>Component 1</h1>
    <input type="text" v-model="msg">
  </div>
</template>
<script>
export default {
  name: 'comp1'.computed: {
    msg() {
      return this.$store.state.msg; ,}}};</script>
Copy the code

Comp1 input box, open the console, will report the following error:

vue.esm.js?efeb:591 [Vue warn]: Computed property "msg" was assigned to but it has no setter.
...
Copy the code

Because we used v-model to bind MSG to the input, when the input box changes, it must trigger the setter (assignment) of MSG, but the calculation property defines the getter for me by default, and does not define the setter. That is why the above error message appears. So let’s customize the setter:

<template>
  <div class="comp1">
    <h1>Component 1</h1>
    <input type="text" v-model="msg">
  </div>
</template>
<script>
export default {
  name: 'comp1',
  computed: {
    msg: {
      get() {
        return this.$store.state.msg;
      },
      set(val) {
        this.$store.dispatch('UPDATE_MSG', { msg: val }); }},}};</script>
Copy the code

As you can see, we can just pass the new value to our vuex in the setter when we modify the MSG value. Do the same for COMP2. When you run the project, you will notice that the comp1 input field values, comp2 input field values and store values are updated in sync. And relative to the above scheme, the amount of code is also streamlined a lot ~

Configurable Watch

Let’s start with this code:

// ...
watch: {
    username() {
      this.getUserInfo(); }},methods: {
  getUserInfo() {
    const info = {
      username: 'yugasun'.site: 'yugasun.com'};/* eslint-disable no-console */
    console.log(info);
  },
},
created() {
  this.getUserInfo();
},
// ...
Copy the code

This makes sense; it is a common scenario in real development to get user information when a component is created, then listen for the user name and retrieve the user information when it changes. So can we optimize it a little bit?

The answer is yes. When we define watcher in the Vue instance, the listener property can be an object with three properties: Deep, immediate, and handler, which are normally defined as functions, are automatically assigned to the handler by Vue. The remaining two properties are set to false by default. The immediate attribute is used in this scenario. Setting this to true means that the handler callback is executed immediately when the component is created, so that we do not need to call the created function again.

watch: {
  username: {
    immediate: true.handler: 'getUserInfo',}},methods: {
  getUserInfo() {
    const info = {
      username: 'yugasun'.site: 'yugasun.com'};/* eslint-disable no-console */
    console.log(info); }},Copy the code

Created is an issue that cannot be triggered when the Url changes but the component does not

First, the default project route is implemented via vue-router, and second, our route is similar to the following:

// ...
const routes = [
  {
    path: '/'.component: Index,
  },
  {
    path: '/:id'.component: Index,
  },
];
Copy the code

The common SRC /views/index.vue component is as follows:

<template>
  <div class="index">
    <router-link :to="{path: '/1'}">Challenge to page 2</router-link><br/>
    <router-link v-if="$route.path === '/1'" :to="{path: '/'}">return</router-link>
    <h3>{{ username }} </h3>
  </div>
</template>
<script>
export default {
  name: 'Index',
  data() {
    return {
      username: 'Loading... '}; },methods: {
    getName() {
      const id = this.$route.params.id;
      // simulate the request
      setTimeout((a)= > {
        if (id) {
          this.username = 'Yuga Sun';
        } else {
          this.username = 'yugasun'; }},300);
    },
  },
  created() {
    this.getName(); }};</script>
Copy the code

The two different paths use the same component Index, and then the getName function in the Index component executes when created, and you can see that when we switch the route to /1, our page doesn’t change and Created doesn’t fire again.

This is because vue-Router recognizes that the two routes use the same component and reuses it, so the component is not recreated and the Created cycle function is not triggered.

The usual solution is to add Watcher to listen for $route changes and then re-execute the getName function. The code is as follows:

watch: {
  $route: {
    immediate: true.handler: 'getName',}},methods: {
  getName() {
    const id = this.$route.params.id;
    // simulate the request
    setTimeout((a)= > {
      if (id) {
        this.username = 'Yuga Sun';
      } else {
        this.username = 'yugasun'; }},300); }},Copy the code

Ok, problem solved, but are there any other lazy ways to not change index.vue?

Add a key attribute to the router-view so that if the URL of the same component changes, Vuejs recreates the component. SRC/app. vue router-view SRC/app. vue router-view

<router-view :key="$route.fullPath"></router-view>
Copy the code

Forgotten $attrs

In most cases, when passing data from parent to child components, we do this via props, as in this example:

<! -- Parent component -->
<Comp3
  :value="value"
  label="Username"
  id="username"
  placeholder="Please enter user name"
  @input="handleInput"
  >

<! -- Subcomponent -->
<template>
  <label>
    {{ label }}
    <input
      :id="id"
      :value="value"
      :placeholder="placeholder"
      @input="$emit('input', $event.target.value)"
    />
  </label>
</template>
<script>
export default {
  props: {
    id: {
      type: String.default: 'username',},value: {
      type: String.default: ' ',},placeholder: {
      type: String.default: ' ',},label: {
      type: String.default: ' ',,}}}</script>
Copy the code

In the props section of the subcomponent, id, value, placeholder… This property definition will do. But if the subcomponent contains the subcomponent, and you also need to pass the ID, value, placeholder… ? Even the third and fourth order… ? So we need to define props a lot. How can we tolerate that?

$attrs = vm.$attrs = vm.

Contains property bindings (except class and style) that are not recognized (and retrieved) as prop in the parent scope. When a component does not declare any prop, all parent-scoped bindings (except class and style) are included, and internal components can be passed in via V-bind =”$attrs” — useful when creating higher-level components.

The author also specifically emphasizes how useful it is to create higher-level components to solve the problem I just mentioned. It’s not too difficult, so go ahead and use it. The code changes as follows:

<! -- Parent component -->
<Comp3
  :value="value"
  label="Username"
  id="username"
  placeholder="Please enter user name"
  @input="handleInput"
  >

<! -- Subcomponent -->
<template>
  <label>
    {{ $attrs.label }}
    <input
      v-bind="$attrs"
      @input="$emit('input', $event.target.value)"
    />
  </label>
</template>
<script>
export default{}</script>
Copy the code

Doesn’t this look much cleaner, and we’re not afraid to reference similar child components again in the operator component. With $attrs, where wouldn’t you click……

conclusion

Of course, Vuejs’ practical skills are much more than that. This is just a summary of what I have encountered in actual development, and what many of my friends tend to overlook. If you have a better way to practice, feel free to comment or email me. Let’s exchange and learn together.

The source code in this

Project directory

You-May-Not-Know-Vuejs