zoukankan      html  css  js  c++  java
  • 第十节:复习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对传入的类型是有限制的

    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 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
     
  • 相关阅读:
    《Asp.Net Forums2.0深入分析》之 Asp.Net Forums是如何实现代码分离和换皮肤的
    Community Server专题五:IHttpHandlerFactory
    自定义 HttpModule 示例
    动态加入控件的方法
    JS应用DOM入门:DOM的对象属性
    JS应用DOM入门:简单文档DOM结构分析
    httpmodule专题(2)
    Java 算法之快速排序
    HTML与.jsp的融合
    Exception
  • 原文地址:https://www.cnblogs.com/yaopengfei/p/15362517.html
Copyright © 2011-2022 走看看