第十节:复习mixin/extends 和 开启Vue3的Composition Api(setup、reactiveApi、refApi、readonly)

一. Vue2.x的mixin和extends

1. mixin简介

(1). 目前我们是使用组件化的方式在开发整个Vue的应用程序,但是组件和组件之间有时候会存在相同的代码逻辑,我们希望对相同的代码逻辑进行抽取

(2). 在Vue2和Vue3中都支持的一种方式就是使用Mixin来完成:

 A. Mixin提供了一种非常灵活的方式,来分发Vue组件中的可复用功能

 B. 一个Mixin对象可以包含任何组件选项

 C. 当组件使用Mixin对象时,所有Mixin对象的选项将被 混合 进入该组件本身的选项中

2. mixin入门和合并规则

(1). 合并规则

(2). 代码实战 

A. 封装方法common1.js

export const myCommon1 = {
    created() {
        console.log('---------------我是created2执行--------------')
    },
    data() {
        return {
            msg: 'hello msg2',
            title: 'hello title2',
            name:'hello name2',
        };
    },
    methods: {
        Test1() {
            console.log('test1111111');
        },
        Test2() {
            console.log('test222222');
        }
    }
}
View Code

B. 组件代码 

<template>
    <div>
        <h4>{{msg}}</h4>
        <h4>{{title}}</h4>
        <h5><button @click="Test1">点我1</button></h5>
        <!--下面代码引入common1.js的时候再打开,否则注释掉 -->
        <h4>{{name}}</h4>
        <h5><button @click="Test2">点我2</button></h5>
    </div>
</template>

<script>
    import { myCommon1 } from './common1.js'

    export default {
        mixins:[myCommon1],
        created() {
            console.log('---------------我是created1执行--------------')
        },
        data() {
            return {
                msg: 'hello msg1',
                title: 'hello title1'
            };
        },
        methods: {
            Test1() {
                console.log('test1');
            }
        }

    }
</script>

运行结果:msg、title、和Test1方法,调用的都是组件自身定义的内容; name 和Test2方法则走的是common1.js中的内容。

3. mixin的全局混入

 如果组件中的某些选项,是所有的组件都需要拥有的,那么这个时候我们可以使用全局的mixin:

A. 全局的Mixin可以使用 应用app的方法 mixin 来完成注册;

B. 一旦注册,那么全局混入的选项将会影响每一个组件;

main.js中代码:

const app = createApp(App);

// 测试mixin的全局混入
app.mixin({
    created() {
        console.log('---------------我是created2执行--------------')
    },
    data() {
        return {
            msg: 'hello msg2',
            title: 'hello title2',
            name:'hello name2',
        };
    },
    methods: {
        Test1() {
            console.log('test1111111');
        },
        Test2() {
            console.log('test222222');
        }
    }
});


app.mount('#app')
View Code

4. extends的使用

 另外一个类似于Mixin的方式是通过extends属性:允许声明扩展另外一个组件,类似于Mixins;

抽离的组件

<template>
    <div>
            
    </div>
</template>

<script>
    export default {
        data() {
            return {
                msg: 'hello msg2',
                title: 'hello title2',
                name:'hello name2',
            };
        },
        methods: {
            Test1() {
                console.log('test1111111');
            },
            Test2() {
                console.log('test222222');
            }
        }
    }
</script>

<style scoped>
</style>
View Code

父组件

<template>
    <div>
        <h4>{{msg}}</h4>
        <h4>{{title}}</h4>
        <h5><button @click="Test1">点我1</button></h5>
    </div>
</template>

<script>
    import BasePage from './BasePage.vue';
    
    export default {
        extends:BasePage,
        created() {
            console.log('---------------我是created1执行--------------')
        },
        data() {
            return {};
        },
        methods: {

        }

    }
</script>

<style scoped>
</style>
View Code

5. 总结 

 在开发中extends用的非常少,在Vue2中比较推荐大家使用Mixin,而在Vue3中推荐使用Composition API。 

 

二. ComPosition Api-setup

1. Options Api的缺点

(1). 在Vue2中,我们编写组件的方式是Options API:

 Options API的一大特点就是在对应的属性中编写对应的功能模块;

 比如data定义数据、methods中定义方法、computed中定义计算属性、watch中监听属性改变,也包括生命周期钩子(composition api中这些统统干掉,写在setup中了)

(2). 这种代码有一个很大的弊端:

 当我们实现某一个功能时,这个功能对应的代码逻辑会被拆分到各个属性中

 当我们组件变得更大、更复杂时,逻辑关注点的列表就会增长,那么同一个功能的逻辑就会被拆分的很分散

 尤其对于那些一开始没有编写这些组件的人来说,这个组件的代码是难以阅读和理解的(阅读组件的其他人);

2. Composition Api简介

 Vue Composition API(VCA)的思想就是:同一个逻辑关注点相关的代码收集在一起会更好。

 在Vue组件中,Composition Api将所有的代码都写在setup函数中。 用它来替代之前所编写的大部分其他选项;比如methods、computed、watch、data、生命周期等等;

3. Composition Api入门-setup

(1). 参数

(2). 返回值 

 setup的返回值可以在模板template中被使用;即之前的data选项、methord都是在返回值暴露。

(3). 代码分享

子组件

<template>
    <div>
        <h3>我是child1组件</h3>
        <h4>{{msg}}</h4>
        <h4>{{counter}}</h4>
        <h4><button @click="AddNum()">加1</button></h4>
    </div>
</template>

<script>
    export default {
        props: {
            msg: {
                type: String,
                default: 'Hello Child1'
            }
        },
        /* 
         参数说明:
         1. props:获取父组件传递过来的props中定义的属性
         2. context (或写成   {attrs, slots, emit}    )
            A.attrs:所有的非prop的attribute;
            B.slots:父组件传递过来的插槽(这个在以渲染函数返回时会有作用,后面会讲到);
            C.emit:当我们组件内部需要发出事件时会用到emit(因为我们不能访问this,所以不可以通过 this.$emit发出事件);
         注:setup中不能使用this关键字
         
         结果剖析:
            这里点击按钮,页面上显示的值没有响应式的增加,console.log中增加了。
            原因:对于一个定义的变量来说,默认情况下,Vue并不会跟踪它的变化,来引起界面的响应式操作。
              
         */
        // setup(props, context) {
        setup(props, { attrs, slots, emit }) {
            // 1. 测试setup的相关参数
            console.log(props.msg);
            console.log(attrs.id, attrs.class);
            console.log(slots);
            console.log(emit);

            // 2.编写相关业务
            let counter = 100;
            const AddNum = () => {
                counter++;
                console.log(counter);
            };

            // 3. 测试setup的返回值
            return {
                title: 'hello title1',
                counter,
                AddNum
            }
        }
    }
</script>

<style scoped>
</style>
View Code

父组件

<template>
    <div>
        <child1 msg="hello setup" id="j_child1" class="ypfClass"></child1>    
    </div>
</template>

<script>
    import child1 from './child1.vue';
    export default {
        components: {
            child1
        },
        data() {
            return {};
        }
    }
</script>

<style scoped>
</style>
View Code

剖析: 

  这里点击按钮,页面上显示的值没有响应式的增加,console.log中增加了。

  原因:对于一个定义的变量来说,默认情况下,Vue并不会跟踪它的变化,来引起界面的响应式操作。

注:setup中不可以使用this 

 

三. reactiveApi

1. 用法

作用:为在setup中定义的数据提供响应式的特性

代码如下:

<template>
    <div>
        <h3>我是child1组件</h3>
        <h4>{{myData.counter}}</h4>
        <h4><button @click="AddNum()">加1</button></h4>
    </div>
</template>

<script>
    import { reactive } from 'vue';

    export default {
        props: {
            msg: {
                type: String,
                default: 'Hello Child1'
            }
        },
        setup(props, { attrs, slots, emit }) {
            // reactiveApi写法
            const myData = reactive({
                counter: 100
            });

            const AddNum = () => {
                myData.counter++;
                console.log(myData.counter);
            };

            return {
                myData,
                AddNum
            }
        }
    }
</script>

2. 原理

 这是因为当我们使用reactive函数处理我们的数据之后,数据再次被使用时就会进行依赖收集

 当数据发生改变时,所有收集到的依赖都是进行对应的响应式操作(比如更新界面);

 事实上,我们编写的data选项,也是在内部交给了reactive函数将其编程响应式对象的

3. reactive Api对传入的类型是有限制的

对数组的补充说明

let data1=reactive([]);       // data1数组无法做到响应式

let data2=reactive({

  list1:[],  

})     //data2.list1数组可以响应式

let data3=ref([]);       // data3数组可以做到响应式

4. reactive其它Api补充

 

 

四. refApi

1. 简介

 ref 会返回一个可变的响应式对象,该对象作为一个 响应式的引用 维护着它内部的值,这就是ref名称的来源;它内部的值是在ref的 value 属性中被维护的;

注:

(1)在模板template中引入ref的值时,Vue会自动帮助我们进行解包操作,所以我们并不需要在模板中通过 ref.value 的方式来使用;

(2)在 setup 函数内部,它依然是一个 ref引用, 所以对其进行操作时,我们依然需要使用 ref.value的方式;

(3)模板中的解包是浅层的解包,如果放在对象里,则不能解包;但是我们将ref放到一个reactive的属性当中,那么在模板中使用时,它会自动解包

2. 实战

<template>
    <div>
        <h3>我是child1组件1111</h3>
        <!-- 1. refApi的在template中默认进行浅层解包,不写.vaule -->
        <h4>1. refApi的浅层解包:{{counter}}</h4>
        <!-- 2. 对象包裹不能解包 -->
        <h4>2. 对象包裹不能解包:{{myInfo1.counter.value}}</h4>
        <!-- 3. 将ref放到一个reactive的属性当中,那么在模板中使用时,它会自动解包 -->
        <h4>3. 将ref放到一个reactive的属性当中,那么在模板中使用时,它会自动解包:{{myInfo2.counter}}</h4>
        <h4><button @click="AddNum()">加1</button></h4>
    </div>
</template>

<script>
    import { reactive, ref } from 'vue';

    export default {
        props: {
            msg: {
                type: String,
                default: 'Hello Child1'
            }
        },
        setup(props, { attrs, slots, emit }) {
            //1. refApi写法
            let counter = ref(100);
            //2.普通对象包裹,则不能解包
            let myInfo1 = {
                counter
            }
            //3.reactive包裹ref对象,又会自动解包
            let myInfo2=reactive({
                counter
            })
            
            const AddNum = () => {
                counter.value++;
                console.log(counter.value);
            };
            return {
                counter,
                myInfo1,
                myInfo2,
                AddNum
            }
        }
    }
</script>

3. 补充ref其它Api

代码分享:

<template>
    <div>
        <h3>{{info1}}</h3>
        <h3>{{info2}}</h3>

        <h3><button @click="Edit1">修改1</button></h3>
        <h3><button @click="Edit2">修改2</button></h3>
        <h3><button @click="Edit3">修改3</button></h3>
    </div>
</template>

<script>
    import { ref, shallowRef, triggerRef } from 'vue';

    export default {
        setup(props, context) {
            // 1. ref响应式对象
            var info1 = ref({ age1: 10 });        
            // 2. 浅层的ref对象
            var info2 = shallowRef({ age2: 20 });
            
            // 默认的ref支持响应式
            var Edit1 = () => {
                info1.value.age1++;    
            }
            // 下面的修改不支持响应式
            var Edit2 = () => {            
                info2.value.age2++;    
            }
            // 下面的修改支持响应式
            var Edit3= () => {
                info2.value.age2++;
                // 手动触发和 shallowRef 相关联的副作用
                triggerRef(info2);        
            }
            
            return {
                info1,
                info2,
                Edit1,
                Edit2,
                Edit3
            }
        }
    }
</script>
View Code

 

 

五. readonly

1. 背景

 我们通过reactive或者ref可以获取到一个响应式的对象,但是某些情况下,我们传入给其他地方(组件)的这个响应式对象希望在另外一个地方(组件)被使用,但是不能被修改,这个时候如何防止这种情况的出现呢?

(1). Vue3为我们提供了readonly的方法;

(2). readonly会返回原生对象的只读代理(也就是它依然是一个Proxy,这是一个proxy的set方法被劫持,并且不能对其进行修改);

2. 使用规则

 在开发中常见的readonly方法会传入三个类型的参数:普通对象、reactive返回的对象、ref的对象

在readonly的使用过程中,有如下规则:

(1). readonly返回的对象都是不允许修改的;

(2). 但是经过readonly处理的原来的对象是允许被修改的;

 A. 比如 const info = readonly(obj),info对象是不允许被修改的;

   B. 当obj被修改时,readonly返回的info对象也会被修改;

   C. 但是我们不能去修改readonly返回的对象info;

(3). 其实本质上就是readonly返回的对象的setter方法被劫持了而已;

核心代码分享:

setup() {
            //1. 普通对象
            const info1 = { name: 'ypf' };
            const readonlyInfo1 = readonly(info1);
            const EditContent1 = () => {
                readonlyInfo1.name = 'ypf2';
                console.log(readonlyInfo1);
            };

            //2.reactive对象
            const info2 = reactive({
                name: 'ypf'
            });
            const readonlyInfo2 = readonly(info2);
            const EditContent2 = () => {
                readonlyInfo2.name = 'ypf2';
                console.log(readonlyInfo2);
            };

            //3.ref对象
            const info3 = ref('ypf');
            const readonlyInfo3 = readonly(info3);
            const EditContent3 = () => {
                readonlyInfo3.value = 'ypf2';
                console.log(readonlyInfo3);
            };

            return {
                EditContent1,
                EditContent2,
                EditContent3
            } 
}

运行结果:

3. 应用场景

 在我们传递给其他组件数据时,往往希望其他组件仅仅使用我们传递的内容,但是不允许它们修改传递的内容,就可以使用readonly了。

 如下:向home组件中传递了1个readonlyInfo对象,该对象只能被使用,不能被修改。

 

 

!

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