第九节:动态组件和keep-alive缓存、webpack分包和异步组件、引用元素/组件($ref)、组件生命周期、组件v-model

一. 动态组件和keep-alive

 1. 背景

 比如我们要做一个动态组件的切换,传统的做法是通过v-if进行判断切换,核心代码如下:

2. 动态组件

(1). 基本使用

  动态组件是使用 component 组件,通过一个特殊的attribute is 来实现:

代码分享:

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

        <!-- 方案2:动态组件Component -->
        <!--2.1  is属性里存放的是Components里注册的组件名称 -->
        <component :is="currentTab"></component>

    </div>
</template>

<script>
    import Home1 from './pages/Home1.vue';
    import Home2 from './pages/Home2.vue';
    import Home3 from './pages/Home3.vue';

    export default {
        components: {
            Home1,
            Home2,
            Home3
        },
        data() {
            return {
                tabs: ['home1', 'home2', 'home3'],
                currentTab: 'home1',
                myAge: 20
            };
        },
        methods: {
            tabClick(item) {
                this.currentTab = item;
            }
        }

    }
</script>

(2). 传值给子组件 和 监听子组件事件

  与普通的父子组件类似,子组件通过props接收,通过$emit对外传递。

父组件

<template>
    <div>
        <button v-for="(item,index) in tabs" :key="index" @click="tabClick(item)" :class="{active:currentTab===item}">{{item}}</button>
        <!-- 方案2:动态组件Component -->
        <!-- 2.2 传值和事件监听  在Home3组件上实现-->
        <component :is="currentTab" name="lmr" :age='myAge' @pageClick="pageClick"></component>
    </div>
</template>

<script>
    import Home1 from './pages/Home1.vue';
    import Home2 from './pages/Home2.vue';
    import Home3 from './pages/Home3.vue';

    export default {
        components: {
            Home1,
            Home2,
            Home3
        },
        data() {
            return {
                tabs: ['home1', 'home2', 'home3'],
                currentTab: 'home1',
                myAge: 20
            };
        },
        methods: {
            tabClick(item) {
                this.currentTab = item;
            },
            pageClick(v1, v2) {
                console.log(v1, v2);
            }
        }

    }
</script>

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

子组件Home3

<template>
    <div @click="myClick1">
        我是Home3组件,{{name}}--{{age}}
    </div>
</template>

<script>
    export default {
        name:'myHome3',
        props: {
            name: {
                type: String,
                default: 'ypf'
            },
            age: {
                type: Number,
                default: 18
            }
        },
        emits: ['pageClick'],
        data() {
            return {};
        },
        methods: {
            myClick1() {
                this.$emit('pageClick', 20, 30, 40);
            }
        }
    }
</script>

3. keep-alive缓存

  默认情况下,切换组件,原组件就被销毁了,再次回来会重新创建组件

代码分享:

        <!-- 方案3 keep-alive保持状态 -->
        <!-- 注意:不写include 默认所有都缓存,存在include时,优先匹配首先检查组件自身的 name 选项 -->
        <!-- 缓存生命周期测试,在Home2组件上改造 -->
        <keep-alive include="myHome1,myHome2">
            <component :is='currentTab' name='lmr' :age='myAge' @pageClick="pageClick"></component>
        </keep-alive>

剖析: 

4. keep-alive缓存生命周期 

 

 

二. Webpack分包和异步组件

1. Webpack实现js分包

(1). 背景

 默认情况下,在构建整个组件树的过程中,因为组件和组件之间是通过模块化直接依赖的,那么webpack在打包时就会将组件模块打包到一起(比如 自己编写的一些业务都在这个app.xxxx.js中,一些三方依赖都打包在chunk-vendors.xxx.js中

 这个时候随着项目的不断庞大,app.js文件的内容过大,会造成首屏渲染速度变慢

(2). 代码分包

 所以,对于一些不需要立即使用的组件,我们可以单独对它们进行拆分,拆分成一些小的代码块chunk.js; 这些chunk.js会在需要时从服务器加载下来,并且运行代码,显示对应的内容。

比如在mainjs入口中导入一段js代码,进行打包

// 普通导入js
import {sum} from './components/11_异步组件的使用/utils/test.js';
console.log(sum(10,20));

改成分包写法,进行打包

// 分包的写法
import("./components/11_异步组件的使用/utils/test.js").then(({sum})=>{
    console.log(sum(10,20));
});

2. Vue中实现异步组件

(1). 背景

 A. 如果我们的项目过大了,对于某些组件我们希望通过异步的方式来进行加载目的是可以对其进行分包处理),那么Vue中给我们提供了一个函数:defineAsyncComponent

 B. defineAsyncComponent接受两种类型的参数:

  类型一:工厂函数,该工厂函数需要返回一个Promise对象

  类型二:接受一个对象类型,对异步函数进行配置;

(2). 代码分享

<script>
    // 同步写法
    import Loading from './Loading.vue';

    // 导入异步组件
    import { defineAsyncComponent } from 'vue';
    // 写法1
    const AsyncCpn1 = defineAsyncComponent(() => import('./AsyncCpn1.vue'));

    // 写法2
    const AsyncCpn2 = defineAsyncComponent({
        loader: () => import("./AsyncCpn2.vue"),
        // 加载异步组件时要使用的组件
        loadingComponent: Loading,
        // 在显示 loadingComponent 之前的延迟 | 默认值:200(单位 ms)
        delay: 200,
        /**
         *
         * @param {*} error 错误信息对象
         * @param {*} retry 一个函数,用于指示当 promise 加载器 reject 时,加载器是否应该重试
         * @param {*} fail  一个函数,指示加载程序结束退出
         * @param {*} attempts 允许的最大重试次数
         */
        onError: function(err, retry, attempts) {

        }
    })

    export default {
        components: {
            AsyncCpn1,
            AsyncCpn2,
            Loading
        },
        data() {
            return {};
        }
    }
</script>

打包后的资源文件如下图:

3. Suspense

 

 

 

三. 引用元素/组件($ref等)

1. $ref

 某些情况下,我们在组件中想要直接获取到元素对象或者子组件实例:

 (1). 在Vue开发中我们是不推荐进行DOM操作的。

 (2). 这个时候,我们可以给元素或者组件绑定一个ref的attribute属性

(1). 获取元素 或 组件

子组件代码

<template>
    <div>
        {{msg}}
        <p><button @click="getfather">获取父亲组件和根组件</button></p>
    </div>
</template>

<script>
    export default {
        data() {
            return {
                msg: 'hello Vue3'
            };
        },
        methods:{
            getfather(){
                console.log('-----------------我是子组件中业务---------------------');
                // 1.获取父元素
                {
                    console.log(this.$parent);
                    // 不建议这么用,因为一个组件会被多个父组件调用,并不是每一个父组件里都有test1属性和getInfo方法
                    console.log(this.$parent.test1);
                    this.$parent.getInfo();
                }
                
                // 2. 获取根元素
                // {
                //     console.log(this.$root);
                //     console.log(this.$root.test1);
                //     this.$root.getInfo();
                // }
                
            }
        }
        
    }
</script>
View Code

父组件代码

<template>
    <div>
        <!-- ref绑定到元素上 -->
        <div ref="t1">hello ypf</div>
        <!-- ref绑定到组件上 -->
        <child-msg1 ref='cMsg'></child-msg1>
        <p><button @click="getInfo">获取信息</button></p>
    </div>
</template>

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

    export default {
        components: {
            ChildMsg1
        },
        data() {
            return {
                test1: '我是test1'
            };
        },
        methods: {
            getInfo() {
                // 1.获取普通元素
                {
                    console.log(this.$refs.t1);
                    console.log(this.$refs.t1.innerHTML);
                }        
                // 2. 获取组件元素
                {
                    console.log(this.$refs.cMsg);
                    // 获取组件中data数据
                    console.log(this.$refs.cMsg.msg);
                    // 调用组件中的方法
                    this.$refs.cMsg.getfather();
                }
            }
        }
    }
</script>

2. $parent 和 $root

 $parent获取父元素。

 $root获取根元素。

 注意:在Vue3中已经移除了$children的属性,所以不可以使用了。

 代码同上

 

四. 组件生命周期

1. 简介

(1). 什么是生命周期呢?

 每个组件都可能会经历从创建、挂载、更新、卸载等一系列的过程;

 在这个过程中的某一个阶段,用于可能会想要添加一些属于自己的代码逻辑(比如组件创建完后就请求一些服务器数据);

 但是我们如何可以知道目前组件正在哪一个过程呢?Vue给我们提供了组件的生命周期函数;

(2). 生命周期函数:

 生命周期函数是一些钩子函数,在某个时间会被Vue源码内部进行回调

 通过对生命周期函数的回调,我们可以知道目前组件正在经历什么阶段;

 那么我们就可以在该生命周期中编写属于自己的逻辑代码了;

2. 图例

  每个钩子函数的作用详见:https://v3.cn.vuejs.org/api/options-lifecycle-hooks.html#beforecreate

 

3. 实操 

代码分享:

<template>
    <div>
        <h4 ref="title"> {{msg}}</h4>
        <button @click="changeMessage">修改message</button>
    </div>
</template>

<script>
    export default {
        data() {
            return {
                msg: 'Hello Home'
            };
        },
        methods: {
            changeMessage() {
                this.msg = "你好啊, 李马儒"
            }
        },
        beforeCreate() {
            console.log("home beforeCreate");
        },
        created() {
            console.log("home created");
        },
        beforeMount() {
            console.log("home beforeMount");
        },
        mounted() {
            console.log("home mounted");
        },
        beforeUnmount() {
            console.log("home beforeUnmount");
        },
        unmounted() {
            console.log("home unmounted");
        },
        beforeUpdate() {
            console.log(this.$refs.title.innerHTML);
            console.log("home beforeUpdate");
        },
        updated() {
            console.log(this.$refs.title.innerHTML);
            console.log("home updated");
        }
    }
</script>

<style scoped>
</style>
View Code

 

五. 组件的v-model

(必须仔细看一下官网的都 vue3.x中新写法:https://v3.cn.vuejs.org/guide/migration/v-model.html#概览  !!!)

Vue3.x整体总结:

 (1). 父组件A调用子组件B,父组件通过 v-model ="msg" 来绑定值,子组件需要通过内置的默认值 modelValue 来接收。

 (2). 对于父组件而言,v-model="msg",相当于 :modelValue="msg" @update:modelValue="msg=$event" (赋值的同时,监听到子组件的变化,收到最新值$event,并且赋值给 msg)

 (3). 对于子组件而言,也可以通过 v-model="modelValue"来赋值(前提是input、select等标签),此时将自动监听modelVaule的变化,拿到变化值需要通过 emit对外传递。 通过中转自动监听modelValue的变化有两种方式:

  A. 通过computed属性处理一下modelValue值,然后再给v-model绑定 [详见下面案例]

  B. 通过watch监听modelValue值,变化的时候emit对外传递。[详见下面案例]

 (4). 对于子组件而言,v-model="modelValue" 也等价于 :modelValue="modelValue[xx]"    @update:modelValue="handleValueChange($event, x)"    在handelValueChange方法中实现对外emit (详见案例:https://www.cnblogs.com/yaopengfei/p/15659117.html)

 (5). 父组件中也可以通过v-model绑定其它值,不用默认的modelValue传递,比如: v-model:title="msg",等价于  :title="msg", @update:title="msg=$event" 。 子组件在props中通过title接收到父组件传递的值,后续都一样了。

分享一个element plus 分页插件的写法,帮助理解:

第一个:通过v-model绑定current-page和page-size,当点击分页条上的页码或者选择每页条数的时候,tableDate中的pageNum和pageSize会自动更新为选择后的值,这就是双向绑定哦。

第二个:没有使用v-model,当点击分页条上的页码或者选择每页条数的时候,tableDate中的pageNum和pageSize不变,永远都是最初的值

1. 回顾input中使用v-model实现属性绑定

 这里的v-model实现了两件事,v-bind:value的数据绑定和@input的事件监听。  (下面 :value 和 @input是 vue2中的写法,  vue3中改为了  :modelValue 和 @update:modelValue了)

     <div>{{message}}</div>
        <input v-model="message">
        <input :value="message" @input="message=$event">

2. 组件v-model的使用

(1). 子组件的封装

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

<script>
    export default {
        props: ['modelValue'],
        emits: ['update:modelValue'],
        computed: {
            // 这里value1对应上面v-model中绑定到值
            value1: {
                set(value) {
                    this.$emit('update:modelValue', value);
                },
                get() {
                    return this.modelValue;
                }
            }
        }
    }
</script>

也可以用@input监听, 下面截图是组件的封装的另外一种写法:

实际上 上面的v-model:xxx是一种语法糖,默认modelValue接收到话,可以省略,相当于 绑定value的同时,并对其监听

(2). 调用

<template>
    <div>
        <!-- 2. 组件上使用v-model -->
        <h4>2. 组件上使用v-model</h4>
        <!-- 2.1 按部就班调用-->
        <hy-input1 :modelValue="message" @update:modelValue="message = $event"></hy-input1>
        <hy-input1 :modelValue="message" @update:modelValue="handle1"></hy-input1>
        <!-- 2.2 直接使用v-model,等价于上面的代码 -->
        <hy-input1 v-model="message"></hy-input1>
    </div>
</template>

<script>
import HyInput1 from './HyInput1.vue';
export default {
    components: {
        HyInput1,
        HyInput2
    },
    methods: {
        // myValue是子组件传递过来的值
        handle1(myValue) {
            this.message = myValue;
        }
    },
    data() {
        return {
            message: 'hello vue3',
            title: 'hello title'
        };
    }
};
</script>

3. 组件v-model绑定多个属性

(1). 子组件

<template>
    <div>
        <input v-model="value1">
        <input v-model="myTitle">
    </div>
</template>

<script>
    export default {
        props: ['modelValue', 'title'],
        emits: ['update:modelValue', 'update:title'],
        computed: {
            value1: {
                set(val) {
                    this.$emit('update:modelValue', val);
                },
                get() {
                    return this.modelValue;
                }
            },
            myTitle: {
                set(myTitle) {
                    this.$emit('update:title', myTitle);
                },
                get() {
                    return this.title;
                }
            }
        }
    }
</script>

(2). 调用 

        <hy-input2 :modelValue="message" @update:modelValue="message = $event" :title="title" @update:title="title = $event"></hy-input2>
        <!--直接使用v-model,等价于上面的代码 -->
        <hy-input2 v-model="message" v-model:title="title"></hy-input2>

特别说明:

   子组件中如果用modelValue来接收的话,这是一个内置的值,使用的时候直接用v-model绑定即可;如果用其它的值接收,需要v-model:xxx=‘’ 使用。 

 

4. Composition Api中v-model绑定多个属性 [优秀!]

(1). 原理剖析

  子组件中,通过内置的变量modelValue接收,注意这里接收的是一个对象,然后把接收到的值ref处理一下,通过v-model,将对象的属性绑定到每个input标签上,最后用watch监听这个对象,通过emit对外传递。

  父组件中,直接通过v-model绑定,传进来一个ref修饰的对象即可。

(2). 子组件封装

<template>
    <div>
        <p>id:<input type="text" v-model="myFormData.id"></p>
        <p>name:<input type="text" v-model="myFormData.name"></p>
        <p>pwd:<input type="text" v-model="myFormData.pwd"></p>
    </div>
</template>

<script>
    import {ref,watch} from 'vue';
    export default {
        props:['modelValue'],
        emits:['update:modelValue'],
        setup(props,{emit}){
            // 获取传递的值
            const myFormData=ref({...props.modelValue});        
            console.log(props.modelValue);  //proxy对象
            console.log({...props.modelValue});  //object对象
                
            // 监听
            watch(myFormData,(newValue)=>{
                emit('update:modelValue',newValue)
            },{
                deep:true
            })
            
            
            return {
                myFormData
            }
        }
    }
</script>

(3). 父组件调用

<template>
    <div>
        <HyInput3 v-model="formData"></HyInput3>
        <p>id:{{formData.id}}</p>
        <p>name:{{formData.name}}</p>
        <p>pwd:{{formData.pwd}}</p>
    </div>
</template>

<script>
    import HyInput3 from './HyInput3.vue'
    import {ref} from 'vue'
    export default {
        components:{
            HyInput3
        },
        setup(){
            const formData=ref({
                id:'',
                name:'',
                pwd:''
            });
            return {
                formData
            }
        }
    }
</script>

(4). 结果展示

 

 

 

 

 

 

 

 

 

 

!

  • 作       者 : Yaopengfei(姚鹏飞)
  • 博客地址 : http://www.cnblogs.com/yaopengfei/
  • 声     明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
  • 声     明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
 
posted @ 2021-09-28 13:41  Yaopengfei  阅读(486)  评论(4编辑  收藏  举报