Componentization – additional knowledge supplement

1. Component life cycle

1.1. Understanding the life cycle

What is the life cycle?

  • In biology, the biological life cycle refers to a series of changes that an organism goes through from the beginning to the end of life;
  • Each component may go through a series of processes from creation, mounting, updating, uninstalling, etc.;
  • At a certain stage in this process, we may want to add some code logic of our 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 function:

  • Life cycle functions are some hook functions (callback 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 ;

1.2. Life cycle process

1.3. Process analysis of life cycle 

App/Home/Banner/ShowMessage

  1. Before creating a component instance 

    beforeCreate 

  2. Create a component instance

    created (important: 1. Send network request; 2. Event monitoring; 3. this.$watch())

  3. template template compilation

    beforeMount

  4. Mount to virtual DOM, virtual DOM->real DOM->interface see h2/div

    mounted (important: element has been mounted, get DOM, use DOM)

  5. Data update: message changes

    beforeUpdate

  6. Generate a new VNode based on the latest data, generate a new virtual DOM -> real DOM

    updated

  7. no longer use v-if="false"

    beforeUnmount

  8. Remove the VNode previously mounted in the virtual DOM from the virtual DOM

    unmounted (relatively important: recycling operation (cancel event listening))

  9. Destroy the component instance

1.4. Examples:

Home.vue

<template>
  <h2>Home</h2>
</template>

<script>
  export default {
    beforeUnmount() {
      console.log("home beforeUnmount")
    },
    unmounted() {
      console.log("home unmounted")
    }
  }
</script>

<style scoped>
</style>

app.vue

<template>
  <h2>message: {
   
   {message}}-{
   
   {counter}}</h2>
  <button @click="message = 'Hello World'">修改message</button>
  <button @click="counter++">+1</button>

  <div>
    <button @click="isShowHome = !isShowHome">显示Home</button>
    <home v-if="isShowHome"></home>
  </div>
</template>

<script>
  import Home from "./Home.vue"

  export default {
    components: {
      Home
    },
    data() {
      return {
        message: "Hello App",
        counter: 0,
        isShowHome: true
      }
    },
    // 1.组件被创建之前
    beforeCreate() {
      console.log("beforeCreate")
    },
    // 2.组件被创建完成
    created() {
      console.log("created")
      console.log("1.发送网络请求, 请求数据")
      console.log("2.监听eventbus事件")
      console.log("3.监听watch数据")
    },
    // 3.组件template准备被挂载
    beforeMount() {
      console.log("beforeMount")
    },
    // 4.组件template被挂载: 虚拟DOM -> 真实DOM
    mounted() {
      console.log("mounted")
      console.log("1.获取DOM")
      console.log("2.使用DOM")
    },
    // 5.数据发生改变
    // 5.1. 准备更新DOM
    beforeUpdate() {
      console.log("beforeUpdate")
    },
    // 5.2. 更新DOM
    updated() {
      console.log("updated")
    },

    // 6.卸载VNode -> DOM元素
    // 6.1.卸载之前
    beforeUnmount() {
      console.log("beforeUnmount")
    },
    // 6.2.DOM元素被卸载完成
    unmounted() {
      console.log("unmounted")
    }
  }
</script>

<style scoped>
</style>

2. ref gets the element component

2.1, the use of $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.

2.2, $parent and $root 

  • 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;

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

2.3. Examples

banner.view

<template>
  <div class="banner">
    <h2>Banner</h2>
  </div>
</template>

<script>
  export default {
    created() {
      
    },
    methods: {
      bannerClick() {
        console.log("bannerClick")
      }
    }
  }
</script>

<style scoped>
</style>

app.vue

<template>
  <div class="app">
    <!--ref属性绑定值,然后下面我们就能轻易的获取-->
    <h2 ref="title" class="title" :style="{ color: titleColor }">{
   
   { message }}</h2>
    <button ref="btn" @click="changeTitle">修改title</button>

    <banner ref="banner"/>
  </div>
</template>

<script>
  import Banner from "./Banner.vue"

  export default {
    components: {
      Banner
    },  
    data() {
      return {
        message: "Hello World",
        titleColor: "red"
      }
    },
    methods: {
      changeTitle() {
        // 1.不要主动的去获取DOM, 并且修改DOM内容
        // this.message = "你好啊, 李银河!"
        // this.titleColor = "blue"

        // 2.获取h2/button元素
        console.log(this.$refs.title)
        console.log(this.$refs.btn)

        // 3.获取banner组件: 组件实例
        console.log(this.$refs.banner)
        
        // 3.1.在父组件中可以主动的调用子组件的对象方法
        this.$refs.banner.bannerClick()

        // 3.2.获取banner组件实例, 获取banner中的元素
        console.log(this.$refs.banner.$el)

        // 3.3.如果banner template是多个根, 拿到的是第一个node节点
        // 注意: 开发中不推荐一个组件的template中有多个根元素
        // console.log(this.$refs.banner.$el.nextElementSibling)

        // 4.组件实例还有两个属性(了解):
        console.log(this.$parent) // 获取父组件
        console.log(this.$root) // 获取根组件 
      }
    }
  }
</script>

<style scoped>
</style>

3. The use of dynamic components

3.1. Case of switching components

For example, we now want to implement a function:

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

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

3.2, 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:

3.3. Realization of dynamic components 

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

What does the value of currentTab need to be?

  • Global registration: it can be a component registered through the component function ;
  • Layout registration: the components registered in the components object of a component object

3.4. Value passing 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;

3.5. Examples 

About.vue

<template>
  <div>
    <h2>About组件</h2>
  </div>
</template>

<script>
  export default {

  }
</script>

<style scoped>
</style>

Home.vue

<template>
  <div>
    <h2>Home组件: {
   
   { name }} - {
   
   { age }}</h2>
    <button @click="homeBtnClick">homeBtn</button>
  </div>
</template>

<script>
  export default {
    props: {
      name: {
        type: String,
        default: ""
      },
      age: {
        type: Number,
        default: 0
      }
    },
    emits: ["homeClick"],
    methods: {
      homeBtnClick() {
        this.$emit("homeClick", "home")
      }
    }
  }
</script>

<style scoped>
</style>

Category.vue

<template>
  <div>
    <h2>Category组件</h2>
  </div>
</template>

<script>
  export default {

  }
</script>

<style scoped>
</style>

app.vue

<template>
  <div class="app">
    <div class="tabs">
      <template v-for="(item, index) in tabs" :key="item">
        <button :class="{ active: currentTab === item }" 
                @click="itemClick(item)">
          {
   
   { item }}
        </button>
      </template>
    </div>
    <div class="view">
      <!-- 1.第一种做法: v-if进行判断逻辑, 决定要显示哪一个组件 -->
      <!-- <template v-if="currentIndex === 0">
        <home></home>
      </template>
      <template v-else-if="currentIndex === 1">
        <about></about>
      </template>
      <template v-else-if="currentIndex === 2">
        <category></category>
      </template> -->

      <!-- 2.第二种做法: 动态组件 component -->
      <!-- is中的组件需要来自两个地方: 1.全局注册的组件 2.局部注册的组件 -->
      <!-- <component :is="tabs[currentIndex]"></component> -->
      <component name="why" 
                 :age="18"
                 @homeClick="homeClick"
                 :is="currentTab">
      </component>
    </div>
  </div>
</template>

<script>
  import Home from './views/Home.vue'
  import About from './views/About.vue'
  import Category from './views/Category.vue'

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

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

4. Keep-alive components

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;

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 .

4.2, keep-alive attribute 

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 ;

4.3, the life cycle of the cache component 

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 two life cycle hook functions activated and deactivated to monitor;

4.4. Examples 

Home.vue

<template>
  <div>
    <h2>Home组件</h2>
    <h2>当前计数: {
   
   { counter }}</h2>
    <button @click="counter++">+1</button>
  </div>
</template>

<script>
  export default {
    name: "home",
    data() {
      return {
        counter: 0
      }
    },
    created() {
      console.log("home created")
    },
    unmounted() {
      console.log("home unmounted")
    },
    // 对于保持keep-alive组件, 监听有没有进行切换
    // keep-alive组件进入活跃状态
    activated() {
      console.log("home activated")
    },
    deactivated() {
      console.log("home deactivated")
    }
  }
</script>

<style scoped>
</style>

app.vue

<template>
  <div class="app">
    <div class="tabs">
      <template v-for="(item, index) in tabs" :key="item">
        <button :class="{ active: currentTab === item }" 
                @click="itemClick(item)">
          {
   
   { item }}
        </button>
      </template>
    </div>
    <div class="view">
      <!-- include: 组件的名称来自于组件定义时name选项  -->
      <keep-alive include="home,about">
        <component :is="currentTab"></component>
      </keep-alive>
    </div>
  </div>
</template>

<script>
  import Home from './views/Home.vue'
  import About from './views/About.vue'
  import Category from './views/Category.vue'

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

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

5. The use of asynchronous components

5.1, Webpack code subpackage

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?

Use the import function, which returns a promise object

5.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:

5.3, the second way of writing asynchronous components (understand) 

5.4. Examples 

app.vue

<template>
  <div class="app">
    <div class="tabs">
      <template v-for="(item, index) in tabs" :key="item">
        <button :class="{ active: currentTab === item }" 
                @click="itemClick(item)">
          {
   
   { item }}
        </button>
      </template>
    </div>
    <div class="view">
      <!-- include: 组件的名称来自于组件定义时name选项  -->
      <keep-alive include="home,about">
        <component :is="currentTab"></component>
      </keep-alive>
    </div>
  </div>
</template>

<script>
  import { defineAsyncComponent } from 'vue'

  import Home from './views/Home.vue'
  import About from './views/About.vue'
  // import Category from './views/Category.vue'
  // const Category = import("./views/Category.vue")  // 导出的是promise对象
  const AsyncCategory = defineAsyncComponent(() => import("./views/Category.vue"))

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

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

6. The v-model of the component

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 ;

6.1. Realization of component v-model 

So, in order for our MyInput component to work properly, the <input> 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:

6.2. 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;

Note: Here I am binding two properties

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

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

6.3. Examples 

Counter.view

<template>
  <div>
    <h2>Counter: {
   
   { modelValue }}</h2>
    <button @click="changeCounter">修改counter</button>
  </div>
</template>

<script>
  export default {
    props: {
      modelValue: {
        type: Number,
        default: 0
      }
    },
    emits: ["update:modelValue"],
    methods: {
      changeCounter() {
        this.$emit("update:modelValue", 999)
      } 
    }
  }
</script>

<style scoped>
</style>

Counter2.vue

<template>
  <div>
    <h2>Counter: {
   
   { counter }}</h2>
    <button @click="changeCounter">修改counter</button>

    <!-- why绑定 -->
    <hr>
    <h2>why: {
   
   { why }}</h2>
    <button @click="changeWhy">修改why的值</button>
  </div>
</template>

<script>
  export default {
    props: {
      counter: {
        type: Number,
        default: 0
      },
      why: {
        type: String,
        default: ""
      }
    },
    emits: ["update:counter", "update:why"],
    methods: {
      changeCounter() {
        this.$emit("update:counter", 999)
      },
      changeWhy() {
        this.$emit("update:why", "kobe")
      }
    }
  }
</script>

<style scoped>
</style>

app.vue

<template>
  <div class="app">
    <!-- 1.input v-model -->
    <!-- <input v-model="message">
    <input :value="message" @input="message = $event.target.value"> -->

    <!-- 2.组件的v-model: 默认modelValue -->
    <counter v-model="appCounter"></counter>
    <counter :modelValue="appCounter" @update:modelValue="appCounter = $event"></counter>
    
    <!-- 3.组件的v-model: 自定义名称counter -->
    <!-- <counter2 v-model:counter="appCounter" v-model:why="appWhy"></counter2> -->
  </div>
</template>

<script>
  import Counter from './Counter.vue'
  import Counter2 from './Counter2.vue'

  export default {
    components: {
      Counter,
      Counter2
    },
    data() {
      return {
        message: "Hello World",
        appCounter: 100,
        appWhy: "coderwhy"
      }
    }
  }
</script>

<style scoped>
</style>

7. Component mixing into Mixin

7.1. Understanding Mixins

  • At present, we are developing the entire Vue application in a componentized way, but sometimes the same code logic exists between components , and we hope to extract the same code logic .
  • One way supported in both Vue2 and Vue3 is to use Mixin to do it :
    • Mixin provides a very flexible way to distribute reusable functions in Vue components ;
    • A Mixin object can contain any component options ;
    • When a component uses a Mixin object, all the options of the Mixin object will be mixed into the options of the component itself ;

7.2. Basic usage of Mixin

7.3, Mixin merge rules 

If the options in the Mixin object conflict with the options in the component object, how will Vue operate?

  • Here it is divided into different situations for processing;

Case 1: If it is the return value object of the data function

  • Return value objects are merged by default ;
  • If the properties of the data return value object conflict, the data of the component itself will be retained ;

Case 2: If it is a life cycle hook function

  • The hook functions of the life cycle will be merged into the array and will be called;

Case 3: Options whose values ​​are objects , such as methods, components, and directives, will be merged into the same object.

  • For example, if there are methods options and methods are defined, they will all take effect ;
  • But if the key of the object is the same , then the key-value pair of the component object will be taken ;

7.4. Global mixing into Mixin

If some options in the component are required by all components, then we can use the global mixin at this time:

  • The global Mixin can use the method mixin of the application app to complete the registration;
  • Once registered, the global mix-in options will affect every component ;

7.5. Examples 

message-mixin.js

export default {
  data() {
    return {
      message: "Hello World"
    }
  },
  created() {
    console.log("message:", this.message)
  }
}

Home.vue

<template>
  <h2>Home组件</h2>
</template>

<script>
  import messageMixin from '../mixins/message-mixin'

  export default {
    // options api
    components: {

    },
    mixins: [messageMixin],
    data() {
      return {
        homeNames: [ "abc", "cba" ]
      }
    },
    methods: {

    },
    created() {
      console.log("home created")
    }
  }
</script>

<style scoped>
</style>

About.vue

<template>
  <h2>About组件</h2>
</template>

<script>
  import messageMixin from '../mixins/message-mixin'

  export default {
    mixins: [messageMixin]
  }
</script>

<style scoped>
</style>

Category.vue

<template>
  <h2>Category组件</h2>
</template>

<script>
  import messageMixin from '../mixins/message-mixin'

  export default {
    mixins: [messageMixin]
  }
</script>

<style scoped>
</style>

app.vue

<template>
  <div>
    <home></home>
    <about></about>
    <category></category>
  </div>
</template>

<script>
  import Home from './views/Home.vue'
  import About from './views/About.vue'
  import Category from './views/Category.vue'

  export default {
    components: {
      Home,
      About,
      Category
    }
  }
</script>

<style scoped>
</style>

Guess you like

Origin blog.csdn.net/weixin_52851967/article/details/128743067