[vue] component knowledge supplement

1. Dynamic components

For example, we now want to implement a function:

  • Click a tab-bar to switch between different component displays;

Picture case screenshot

We can implement this case through two different implementation ideas:

  • Method 1: Use v-if to judge and display different components;
  • Method 2: the method of dynamic components;

1.1. v-if displays different components

We can first judge and display different components through v-if, which can be realized by using the knowledge we mentioned before:

<template>
  <div>
    <button v-for="tab in tabs" 
            :key="tab"
            :class="{active: currentTab === tab}"
            @click="tabClick(tab)">
      {
   
   {tab}}
    </button>

    <template v-if="currentTab === 'home'">
      <home></home>
    </template>
    <template v-else-if="currentTab === 'about'">
      <about></about>
    </template>
    <template v-else>
      <category></category>
    </template>
  </div>
</template>

<script>
  import Home from "./pages/Home.vue";
  import About from "./pages/About.vue";
  import Category from "./pages/Category.vue";

  export default {
    components: {
      Home, About, Category
    },
    data() {
      return {
        tabs: ["home", "about", "category"],
        currentTab: "home"
      }
    },
    methods: {
      tabClick(tab) {
        this.currentTab = tab;
      }
    }
  }
</script>

<style scoped>
  .active {
    color: red;
  }
</style>

1.2. Realization of dynamic components

Dynamic components are implemented using component components through a special attribute is :

<template>
  <div>
    <button v-for="tab in tabs" 
            :key="tab"
            :class="{active: currentTab === tab}"
            @click="tabClick(tab)">
      {
   
   {tab}}
    </button>

    <component :is="currentTab"></component>
  </div>
</template>

What does the value of currentTab need to be?

  • Can be a component registered through the component function;
  • Components registered in the components object of a component object;

1.3. Passing values ​​of dynamic components

If it is a dynamic component, can we pass values ​​to them and listen to events?

  • the same;
  • It's just that we need to put attributes and listener events on the component for use;

The code of App.vue is as follows:

<template>
  <div>
    <button v-for="tab in tabs" 
            :key="tab"
            :class="{active: currentTab === tab}"
            @click="tabClick(tab)">
      {
   
   {tab}}
    </button>

    <component name="why" 
               :age="18" 
               @pageClick="pageClick" 
               :is="currentTab"/>
  </div>
</template>

<script>
  import Home from "./pages/Home.vue";
  import About from "./pages/About.vue";
  import Category from "./pages/Category.vue";

  export default {
    components: {
      Home, About, Category
    },
    data() {
      return {
        tabs: ["home", "about", "category"],
        currentTab: "home"
      }
    },
    methods: {
      tabClick(tab) {
        this.currentTab = tab;
      },
      pageClick(payload) {
        console.log("pageClick", payload);
      }
    }
  }
</script>

<style scoped>
  .active {
    color: red;
  }
</style>

The code in Home.vue is as follows:

<template>
  <div @click="pageClick">
    Home组件: {
   
   {name}}-{
   
   {age}}
  </div>
</template>

<script>
  export default {
    props: {
      name: String,
      age: Number
    },
    emits: ["pageClick"],
    methods: {
      pageClick() {
        this.$emit("pageClick", "Home组件");
      }
    }
  }
</script>

1.4. Use of keep-alive

1.4.1. Understanding keep-alive

Let's first modify the About component in the previous case:

  • A button is added to it, and the function can be incremented by clicking;
<template>
  <div>
    About组件
    <button @click="counter++">{
   
   {counter}}</button>
  </div>
</template>

<script>
  export default {
    data() {
      return {
        counter: 0
      }
    }
  }
</script>

For example, if we set the counter to 10, can the state be maintained when switching to home and then back to about?

  • the answer is negative;
  • This is because by default, after we switch components, the about component will be destroyed, and the component will be recreated when we come back again;

However, in some cases during development, we want to keep the state of the component instead of destroying it. At this time, we can use a built-in component: keep-alive.

<keep-alive>
  <component name="why" 
             :age="18" 
             @pageClick="pageClick" 
             :is="currentTab"/>
</keep-alive>

1.4.2. keep-alive property

keep-alive has some properties

  • include - string | RegExp | Array. Only components with matching names will be cached;
  • exclude - string | RegExp | Array. Any component whose name matches will not be cached;
  • max - number | string. The maximum number of component instances that can be cached. Once this number is reached, the instances in the cache component that have not been accessed recently will be destroyed;

The include and exclude props allow components to be cached conditionally:

  • Both can be represented as comma-separated strings, regular expressions, or an array;
  • The match first checks the name option of the component itself;
  • If the name option is not available, it matches its local registration name (the key value of the parent component components option);
<!-- 逗号分隔字符串 -->
<keep-alive include="a,b">
  <component :is="view"></component>
</keep-alive>

<!-- regex (使用 `v-bind`) -->
<keep-alive :include="/a|b/">
  <component :is="view"></component>
</keep-alive>

<!-- Array (使用 `v-bind`) -->
<keep-alive :include="['a', 'b']">
  <component :is="view"></component>
</keep-alive>

1.4.3. Cache Lifecycle

For cached components, when re-entering, we will not execute life cycle functions such as created or mounted:

But sometimes we really want to monitor when we re-enter the component and when we leave the component; at
this time we can use the activated and deactivated two life cycle hook functions to monitor;

<template>
  <div>
    About组件
    <button @click="counter++">{
   
   {counter}}</button>
  </div>
</template>

<script>
  export default {
    name: "about",
    data() {
      return {
        counter: 0
      }
    },
    // 当重新进入活跃状态时会回调
    activated() {
      console.log("about activated")
    },
    // 当离开活跃状态时会回调
    deactivated() {
      console.log("about deactivated")
    }
  }
</script>

Two, asynchronous components

2.1. Code subpackage of webpack

The default packaging process:

  • By default, in the process of building the entire component tree, because the components are directly dependent on modules, webpack will package the component modules together when packaging (for example, in an app.js file);
  • At this time, as the project continues to grow, the content of the app.js file is too large, which will slow down the rendering speed of the first screen;

When packaging, the subpackage of the code:

  • Therefore, for some components that do not need to be used immediately, we can split them separately and split them into some small code blocks chunk.js;
  • These chunk.js will be loaded from the server when needed, and run the code to display the corresponding content;

So how can the code be subpackaged in webpack?

By default, when we directly use import to depend on a module, it will not be subpackaged:

import {sum} from './utils/math';

console.log(sum(20, 30));

If we want to subcontract , we can use the import function : (return a promise)

import("./utils/math").then(({ sum }) => {
  console.log(sum(20, 30));
});

The effect of image import packaging

2.2. Implement asynchronous components in vue

If our project is too large and we want to load some components asynchronously (the purpose is to subcontract them), then Vue provides us with a function: defineAsyncComponent .

defineAsyncComponent accepts two types of parameters:

  • Type 1: Factory function, which needs to return a Promise object;
  • Type 2: Accept an object type and configure the asynchronous function;

The writing method of factory function type 1:

<script>
  import { defineAsyncComponent } from 'vue';
  const AsyncHome = defineAsyncComponent(() => import("./AsyncHome.vue"));

  export default {
    components: {
      AsyncHome
    }
  }
</script>

The writing method of object type type 2:

<script>
  import { defineAsyncComponent } from "vue";
  // const AsyncHome = defineAsyncComponent(() => import("./AsyncHome.vue"));

  import Loading from "./Loading.vue";
  import Error from "./Error.vue";
  const AsyncHome = defineAsyncComponent({
    // 工厂函数
    loader: () => import("./AsyncHome.vue"),
    // 加载过程中显示的组件
    loadingComponent: Loading,
    // 加载失败时显示的组件
    errorComponent: Error,
    // 在显示 loadingComponent 之前的延迟 | 默认值:200(单位 ms)
    delay: 200,
    // 如果提供了 timeout ,并且加载组件的时间超过了设定值,将显示错误组件
    // 默认值:Infinity(即永不超时,单位 ms)
    timeout: 3000,
    // 定义组件是否可挂起 | 默认值:true
    suspensible: false,
    /**
     *
     * @param {*} error 错误信息对象
     * @param {*} retry 一个函数,用于指示当 promise 加载器 reject 时,加载器是否应该重试
     * @param {*} fail  一个函数,指示加载程序结束退出
     * @param {*} attempts 允许的最大重试次数
     */
    onError(error, retry, fail, attempts) {
      if (error.message.match(/fetch/) && attempts <= 3) {
        // 请求发生错误时重试,最多可尝试 3 次
        retry();
      } else {
        // 注意,retry/fail 就像 promise 的 resolve/reject 一样:
        // 必须调用其中一个才能继续错误处理。
        fail();
      }
    },
  });

  export default {
    components: {
      AsyncHome,
    },
  };
</script>

2.3. Asynchronous components and Suspense

Note that (2021-06-08) Suspense shows an experimental feature, and the API may be modified at any time.

Suspense is a built-in global component that has two slots:

  • default: If default can be displayed, then display the content of default;
  • fallback: If the default cannot be displayed, the contents of the fallback slot will be displayed;
<template>
  <div>
    <suspense>
      <template #default>
        <async-home></async-home>
      </template>
      <template #fallback>
        <loading/>
      </template>
    </suspense>
  </div>
</template>

3. Module reference

3.1. $refs

In some cases, we want to directly get the element object or subcomponent instance in the component:

  • We do not recommend DOM manipulation in Vue development ;
  • At this time, we can bind a ref attribute to the element or component;

Component instances have a $refs property:

  • It is an Object that holds all DOM elements and component instances registered with the ref attribute.

Implementation of App.vue:

<template>
  <div>
    <h2 ref="title">哈哈哈</h2>
    <hello-world ref="helloCpn"></hello-world>

    <button @click="visitElement">访问元素或者组件</button>
  </div>
</template>

<script>
  import HelloWorld from './HelloWorld.vue';

  export default {
    components: {
      HelloWorld
    },
    methods: {
      visitElement() {
        // 访问元素
        console.log(this.$refs.title);
        // 访问组件实例
        this.$refs.helloCpn.showMessage();
        // 访问子组件里的根元素
        console.log(this.$refs.helloCpn.$el);
      }
    }
  }
</script>

HelloWorld.vue implementation:

<template>
  <div>
        <h2>HelloWorld</h2>
  </div>
</template>

<script>
  export default {
    methods: {
      showMessage() {
        console.log("我是HelloWorld组件的showMessage方法");
      }
    }
  }
</script>

3.2. $parent

We can access the parent element through $parent.

Implementation of HelloWorld.vue:

  • Here we can also achieve it through $root, because App is our root component;
<template>
  <div>
    <button @click="visitParent">访问父组件</button>
  </div>
</template>

<script>
  export default {
    methods: {
      showMessage() {
        console.log("我是HelloWorld组件的showMessage方法");
      },
      visitParent() {
        console.log(this.$parent.message);
      }
    }
  }
</script>

Note: The $children property has been removed in Vue3, so it can no longer be used.

4. Life cycle

4.1. Life cycle picture

What is the life cycle?

  • Each component will go through a series of processes from creation, mounting, updating, and uninstalling;
  • At a certain stage in this process , the user may want to add some code logic of his own (such as requesting some server data after the component is created);
  • But how can we know which process the component is currently in? Vue provides us with component lifecycle functions;
    lifecycle functions:
  • Life cycle functions are some hook functions, which will be called back by the Vue source code at a certain time ;
  • Through the callback of the life cycle function, we can know what stage the component is currently going through;
  • Then we can write our own logic code in this life cycle;
    insert image description here

4.2. Lifecycle Walkthrough

We walk through all life cycle functions through an App and Home.

App.vue component object:

<template>
  <div>
    <button @click="toggle">切换</button>
    <div v-if="isShow">
      <home></home>
    </div>
  </div>
</template>

<script>
  import Home from './Home.vue';

  export default {
    components: {
      Home
    },
    data() {
      return {
        isShow: true
      }
    },
    methods: {
      toggle() {
        this.isShow = !this.isShow;
        console.log(this.isShow);
      }
    }
  }
</script>

Home.vue component object:

<template>
  <div>
    <button @click="changeMessage">修改message</button>
    <h2 ref="titleRef">{
   
   {message}}</h2>
  </div>
</template>

<script>
  export default {
    data() {
      return {
        message: "Hello World"
      }
    },
    methods: {
      changeMessage() {
        this.message = "你好啊,李银河";
      }
    },
    beforeUpdate() {
      console.log("beforeUpdate");
      console.log(this.$refs.titleRef.innerHTML);
    },
    updated() {
      console.log("updated");
      console.log(this.$refs.titleRef.innerHTML);
    },
    beforeCreate() {
      console.log("beforeCreate");
    },
    created() {
      console.log("created");
    },
    beforeMount() {
      console.log("beforeMount");
    },
    mounted() {
      console.log("mounted");
    },
    beforeUnmount() {
      console.log("beforeUnmount");
    },
    unmounted() {
      console.log("unmounted");
    }
  }
</script>

5. The v-model of the component

5.1. Component v-model

Earlier we can use v-model in input to complete two-way binding:

  • It is often very convenient at this time, because v-model helps us accomplish two things by default;
  • v-bind:value data binding and @input event monitoring;

If we encapsulate a component now, can v-model be used to complete these two functions at the same time when using this component elsewhere?

  • It is also possible, vue also supports the use of v-model on components;

When we use it on a component, it is equivalent to the following operations:

  • We will find that the only difference from the input element is the name of the attribute and the name of the event trigger;

Image image-20210609102632616

So, in order for our MyInput component to work properly, the components inside this component must:

  • Bind its value attribute to a prop named modelValue;
  • When its input event is triggered, the new value is thrown through the custom update:modelValue event;

The component code of MyInput.vue is as follows:

<template>
  <div>
    <input :value="modelValue" @input="inputChange">
  </div>
</template>

<script>
  export default {
    props: ["modelValue"],
    emits: ["update:modelValue"],
    methods: {
      inputChange(event) {
        this.$emit("update:modelValue", event.target.value);
      }
    }
  }
</script>

In App.vue, we can use v-model directly when using MyInput:

<template>
  <div>
    <my-input v-model="message"/>
    <button @click="changeMessage">修改message</button>
  </div>
</template>

<script>
  import MyInput from './MyInput.vue';

  export default {
    components: {
      MyInput
    },
    data() {
      return {
        message: ""
      }
    },
    methods: {
      changeMessage() {
        this.message = "Hello World"
      }
    }
  }
</script>

5.2. Computed implementation

In the above case, we may think of an implementation method: directly bidirectionally bind the properties in Props to the input

<template>
  <div>
    <input v-model="modelValue">
  </div>
</template>

<script>
  export default {
    props: ["modelValue"]
  }
</script>

Can the above method achieve two-way binding of components? the answer is no

  • Because after we modify props internally, the outside world does not know our modification to props, so the event will not be passed out;
  • In addition, it is not a good habit to directly modify the properties in props during development, do not do this;

So, we still hope to complete the two-way binding within the component, how should we do it? We can do it using setters and getters of computed properties.

<template>
  <div>
    <input v-model="value">
  </div>
</template>

<script>
  export default {
    props: ["modelValue"],
    emits: ["update:modelValue"],
    computed: {
      value: {
        get() {
          return this.modelValue;
        },
        set(value) {
          this.$emit("update:modelValue", value)
        }
      }
    }
  }
</script>

5.3. Binding multiple properties

We are now directly binding an attribute through v-model, what if we want to bind multiple attributes? That is, is it possible for us to use multiple v-models on one component?

  • We know that the v-model by default is actually an event that binds the modelValue attribute and @update:modelValue;
  • If we want to bind more, we can pass a parameter to v-model, then the name of this parameter is the name of our bound attribute;

Let's take a look at how I use it in App.vue:

<template>
  <div>
    <my-input v-model="message" v-model:title="title"/>
    <h2>{
   
   {message}}</h2>
    <button @click="changeMessage">修改message</button>
    <hr>
    <h2>{
   
   {title}}</h2>
    <button @click="changeTitle">修改title</button>
  </div>
</template>

<script>
  import MyInput from './MyInput.vue';

  export default {
    components: {
      MyInput
    },
    data() {
      return {
        message: "",
        title: ""
      }
    },
    methods: {
      changeMessage() {
        this.message = "Hello World"
      },
      changeTitle() {
        this.title = "Hello Title"
      }
    }
  }
</script>

Note: Here I am binding two properties

<my-input v-model="message" v-model:title="title"/>

v-model:title is equivalent to doing two things:

  • The title attribute is bound;
  • Listened to the @update:title event;

So, our implementation in MyInput is as follows:

<template>
  <div>
    <input :value="modelValue" @input="input1Change">
    <input :value="title" @input="input2Change">
  </div>
</template>

<script>
  export default {
    props: ["modelValue", "title"],
    emits: ["update:modelValue", "update:title"],
    methods: {
      input1Change(event) {
        this.$emit("update:modelValue", event.target.value);
      },
      input2Change(event) {
        this.$emit("update:title", event.target.value);
      }
    }
  }
</script>

Guess you like

Origin blog.csdn.net/qq_42425551/article/details/123567338