美文网首页
Vue3 | Composition API 包括setup、r

Vue3 | Composition API 包括setup、r

作者: 凌川江雪 | 来源:发表于2021-11-25 00:32 被阅读0次

    完整原文地址见简书

    更多完整Vue笔记目录敬请见《前端 Web 笔记 汇总目录(Updating)》


    本文内容提要

    • Composition API 的作用
    • setup函数
    • 例程,打印查看setup内容
    • 非响应引用的案例
    • ref()概念、原理 与 实战
    • reactive()概念、原理 与 实战
    • 使用readonly限制对象的访问权限
    • 使用toRefs()reactive对象进一步封装
    • 多个属性进行解构
    • 多个属性 配合toRefs() 进行解构
    • toRefs()无法处理 undefined的键值
    • 使用toRef()应对上述问题
    • 关于setup函数的三个参数【attrs、slots、emit】
    • 回顾 没有 CompositionAPI时,emit的用法
    • 使用setup的 context.emit 替代 this.$emit
    • 使用Composition API开发 todoList
    • 完善toDoList案例
    • 优化上例的逻辑结构!
    • setup的 computed 计算属性
    • 当然以上是computed 的默认用法,实际上它可以接收一个对象
    • 将上例的处理值换成 Object类型,再例
    • setup 中的 watch 监听
    • setup 中的 watch 监听:监听Object类型
    • setup 中的 watch 监听:监听Object类型的 多个属性
    • setup 中的 watchEffect监听 以及 与 watch 的异同比较
    • 两者都可以用以下的方式,在一个设定的时延之后,停止监听
    • 为 watch 配置 immediate属性,可使具备同watchEffect的 即时性
    • setup 中的 生命周期
    • setup中的provide、inject用法
    • 配合上ref实现 响应特性 以及 readonly实现 单向数据流
    • setup结合ref指令

    Composition API 的作用

    使得相同的、相关的功能代码 可以比较 完整地聚合起来,
    提高可维护性、可读性,提高开发效率;

    规避 同一个功能的代码,
    却散落在 组件定义中的data、methods、computed、directives、template、mixin等各处 的问题;

    setup函数

    --- Composition API 所有代码编写之前,
    都要 建立在setup函数 之上;

    --- 在created 组件实例 被完全初始化之前 回调;
    (所以注意在setup函数中,
    使用与this相关的调用是没有用的)

    --- setup函数中的内容,
    可以在 该组件的 模板template 中直接使用;
    (如下例程)

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Hello World! heheheheheheda</title>
        <script src="https://unpkg.com/vue@next"></script>
    </head>
    <body>
        <div id="heheApp"></div>
    </body>
    <script>
    
        const app = Vue.createApp({
    
            template: `
                <div @click="handleClick">{{name}}</div>    
            `,
    
            setup(props, context) {
                return {
                    name: 'zhao',
                    handleClick: () => {
                        alert(666);
                    }
                }
            }
        });
    
        const vm = app.mount('#heheApp');
    </script>
    </html>
    
    运行效果:

    例程,打印查看setup内容

    <script>
    
        const app = Vue.createApp({
    
            template: `
                <div @click="handleClick">{{name}}</div>    
            `,
            methods: {
                test() {
                    console.log(this.$options);
                }
            },
            mounted() {
                this.test();
            },
            setup(props, context) {
                return {
                    name: 'zhao',
                    handleClick: () => {
                        alert(666);
                    }
                }
            }
        });
    
        const vm = app.mount('#heheApp');
    </script>
    
    运行效果:

    由于调用时序的关系,setup中 无法调用this等相关 如变量、methods中 等 其他内容,但是其他内容 却可以调用 setup函数!!【setup生时众为生,众生时setup已生】

    <script>
    
        const app = Vue.createApp({
    
            template: `
                <div @click="handleClick">{{name}}</div>    
            `,
            methods: {
                test() {
                    console.log(this.$options.setup());
                }
            },
            mounted() {
                this.test();
            },
            setup(props, context) {
                return {
                    name: 'zhao',
                    handleClick: () => {
                        alert(666);
                    }
                }
            }
        });
    
        const vm = app.mount('#heheApp');
    </script>
    

    非响应引用的案例

    如下,这样没有使用ref/reactive的写法,是不会有响应的:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Hello World! heheheheheheda</title>
        <script src="https://unpkg.com/vue@next"></script>
    </head>
    <body>
        <div id="heheApp"></div>
    </body>
    <script>
    
        const app = Vue.createApp({
    
            template: `
                <div>{{name}}</div>    
            `,
            setup(props, context) {
                let name = 'guan';
                setTimeout(() => {
                    name = 'zhao';
                }, 2000);
                return { name }
            }
        });
    
        const vm = app.mount('#heheApp');
    </script>
    </html>
    

    如下,运行之后,两秒延时之后,DOM文本展示并不会自动改成zhao,而是一直展示初始化的guan

    ref()概念、原理 与 实战

    使用ref可以 用于处理 基础类型的数据,赋能响应式

    原理:通过 proxy 将 数据 封装成
    类似 proxy({value: '【变量值】'})这样的一个响应式引用
    数据变化时,就会 触发template等相关UI的更新

    【赋予 非data中定义的变量 以响应式的能力 ——
    原先,我们是借助Vue的data函数,完成响应式变量的定义的;
    有了ref之后,我们可以不借助data中的定义,
    而直接在普通的函数中对js变量proxy封装,
    就可以对 普通的js引用 赋能响应式了】;

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Hello World! heheheheheheda</title>
        <script src="https://unpkg.com/vue@next"></script>
    </head>
    <body>
        <div id="heheApp"></div>
    </body>
    <script>
    
        const app = Vue.createApp({
    
            template: `
                <div>{{name}}</div>    
            `,
            setup(props, context) {
                const { ref } = Vue;
                let name = ref('guan');
                setTimeout(() => {
                    name.value = 'zhao';
                }, 2000);
                return { name }
            }
        });
    
        const vm = app.mount('#heheApp');
    </script>
    </html>
    

    运行效果:


    两秒后自动变化:

    reactive()概念、原理 与 实战

    使用reactive()用于处理 非基础类型的数据(如Object、Array),赋能响应式
    原理类似ref(),只是处理的数据格式不同而已;

    如下,普通的Object类型是没有响应式的效果的:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Hello World! heheheheheheda</title>
        <script src="https://unpkg.com/vue@next"></script>
    </head>
    <body>
        <div id="heheApp"></div>
    </body>
    <script>
    
        const app = Vue.createApp({
    
            template: `
                <div>{{nameObj.name}}</div>    
            `,
            setup(props, context) {
                // const { ref } = Vue;
                const nameObj = { name: 'guan'};
                setTimeout(() => {
                    nameObj.name = 'zhao';
                }, 2000);
                return { nameObj }
            }
        });
    
        const vm = app.mount('#heheApp');
    </script>
    </html>
    
    运行效果,两秒后无反应:

    使用reactive()处理Object类型后,具备响应式的能力:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Hello World! heheheheheheda</title>
        <script src="https://unpkg.com/vue@next"></script>
    </head>
    <body>
        <div id="heheApp"></div>
    </body>
    <script>
    
        const app = Vue.createApp({
    
            template: `
                <div>{{nameObj.name}}</div>    
            `,
            setup(props, context) {
                const { reactive } = Vue;
                const nameObj = reactive({ name: 'guan'});
                setTimeout(() => {
                    nameObj.name = 'zhao';
                }, 2000);
                return { nameObj }
            }
        });
    
        const vm = app.mount('#heheApp');
    </script>
    </html>
    

    运行效果:


    两秒后自动变化:

    使用reactive()处理Array类型数据

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Hello World! heheheheheheda</title>
        <script src="https://unpkg.com/vue@next"></script>
    </head>
    <body>
        <div id="heheApp"></div>
    </body>
    <script>
    
        const app = Vue.createApp({
    
            template: `
                <div>{{nameObj[0]}}</div>    
            `,
            setup(props, context) {
                const { reactive } = Vue;
                const nameObj = reactive([123, 99, 567]);
                setTimeout(() => {
                    nameObj[0] = 666;
                }, 2000);
                return { nameObj }
            }
        });
    
        const vm = app.mount('#heheApp');
    </script>
    </html>
    

    运行效果:


    两秒后自动变化:

    使用readonly限制对象的访问权限

    使用readonly()封装对象,可以限制对象为只读权限

    <script>
    
        const app = Vue.createApp({
    
            template: `
                <div>{{nameObj[0]}}</div>    
                <div>{{copyNameObj[0]}}</div> 
            `,
            setup(props, context) {
                const { reactive, readonly } = Vue;
                const nameObj = reactive([123, 99, 567]);
                const copyNameObj = readonly(nameObj);//----
                setTimeout(() => {
                    nameObj[0] = 666;
                    copyNameObj[0] = 666;//----
                }, 2000);
                return { nameObj, copyNameObj }//----
            }
        });
    
        const vm = app.mount('#heheApp');
    </script>
    

    运行两秒之后会有相应的报错:

    错误的解构案例

    如下的解构是行不通的,
    const { name } = nameObj;只能拿到nameObj的值,拿不到proxy对象;

    <script>
    
        const app = Vue.createApp({
    
            template: `
                <div>{{name}}</div>    
            `,
            setup(props, context) {
                const { reactive } = Vue;
                const nameObj = reactive({ name: 'guan'});
                setTimeout(() => {
                    nameObj.name = 'zhao';
                }, 2000);
    
                const { name } = nameObj;
                return { name }
            }
        });
    
        const vm = app.mount('#heheApp');
    </script>
    

    注意解构技巧

    赋值时 加上{},会 直取 其 赋值右侧 Object的值;

    <script>
    
        const app = Vue.createApp({
    
            template: `
                <div>{{name}}</div>    
            `,
            setup(props, context) {
                const { reactive } = Vue;
                const nameObj = reactive({ name: 'guan'});
                setTimeout(() => {
                    nameObj.name = 'zhao';
                }, 2000);
    
                const { name } = nameObj;
                console.log(name);
                return { name }
            }
        });
    
        const vm = app.mount('#heheApp');
    </script>
    



    直接赋值,便是直接赋值一份引用;

    <script>
    
        const app = Vue.createApp({
    
            template: `
                <div>{{name}}</div>    
            `,
            setup(props, context) {
                const { reactive } = Vue;
                const nameObj = reactive({ name: 'guan'});
                setTimeout(() => {
                    nameObj.name = 'zhao';
                }, 2000);
    
                const name  = nameObj;
                console.log(name);
                return { name }
            }
        });
    
        const vm = app.mount('#heheApp');
    </script>
    

    使用toRefs()reactive对象进一步封装

    --- toRefs() expects a reactive object
    首先,toRefs()需要接受一个reactive对象;
    即,传给toRefs()之前,需要先用reactive()进行封装;

    --- 使用toRefs()reactive封装的对象 进一步封装,
    便可以顺利解构;

    --- 原理:toRefs()将类似proxy({name:'guan'})的结构,
    转化成类似{ name: proxy( {value:'guan'}) }的结构,
    这里可以看到name键的值,其实就类似于ref的处理结果;

    然后使用const { name }{ name: proxy( {value:'guan'}) }进行解构赋值,
    左侧name变量 拿到的就是proxy( {value:'guan'})这一部分的值,
    所以放入DOM节点展示时候,
    直接使用name即可;

    --- !!!注意:
    类似reactive()的处理结果,
    proxy({name:'guan'})的结构,
    放入DOM节点展示时候,需要使用nameObj.name的格式;
    而类似ref()的处理结果,
    proxy( {value:'guan'})的结构,
    放入DOM节点展示时候,直接使用nameObj的格式即可;

    <script>
    
        const app = Vue.createApp({
    
            template: `
                <div>{{name}}</div>    
            `,
            setup(props, context) {
                const { reactive, toRefs } = Vue;
                const nameObj = reactive({ name: 'guan'});
                setTimeout(() => {
                    nameObj.name = 'zhao';
                }, 2000);
    
                const { name } = toRefs(nameObj);
                console.log(name);
                return { name }
            }
        });
    
        const vm = app.mount('#heheApp');
    </script>
    

    运行两秒后,自动更新UI:

    多个属性进行解构

    注意多个属性解构时的写法 以及
    return时的写法;

    <script>
    
        const app = Vue.createApp({
    
            template: `
                <div>{{name}}</div>    
                <div>{{age}}</div>    
                <div>{{address}}</div>    
            `,
            setup(props, context) {
                const { reactive, toRefs } = Vue;
                const nameObj = reactive({ name: 'guan', age: 22, address:'chaozhou'});
                setTimeout(() => {
                    nameObj.name = 'zhao';
                    nameObj.age = 66;
                    nameObj.address = 'guangzhou';
                }, 2000);
    
                const { name, age, address } = (nameObj);
                console.log(name, age, address);
                return { name, age, address }
            }
        });
    
        const vm = app.mount('#heheApp');
    </script>
    

    运行结果(当然不会自动更新):

    多个属性 配合toRefs() 进行解构

    <script>
    
        const app = Vue.createApp({
    
            template: `
                <div>{{name}}</div>    
                <div>{{age}}</div>    
                <div>{{address}}</div>    
            `,
            setup(props, context) {
                const { reactive, toRefs } = Vue;
                const nameObj = reactive({ name: 'guan', age: 22, address:'chaozhou'});
                setTimeout(() => {
                    nameObj.name = 'zhao';
                    nameObj.age = 66;
                    nameObj.address = 'guangzhou';
                }, 2000);
    
                const { name, age, address } = toRefs(nameObj);
                console.log(name, age, address);
                return { name, age, address }
            }
        });
    
        const vm = app.mount('#heheApp');
    </script>
    

    运行结果:

    两秒后自动刷新:

    toRefs()无法处理 undefined的键值

    如果意图解构的键,
    不存在于toRefs()封装的对象中,
    使用时会报错:

    <script>
    
        const app = Vue.createApp({
    
            template: `
                <div>{{age}}</div>    
            `,
            setup(props, context) {
                const { reactive, toRefs } = Vue;
                const data = reactive({ name: 'guan'});
                const { age } = toRefs(data);
                setTimeout(() => {
                    age.value = 'zhao';
                }, 2000);
    
                return { age }
            }
        });
    
        const vm = app.mount('#heheApp');
    </script>
    
    运行结果:

    使用toRef()应对上述问题

    toRef(data, key)会尝试从data中读取key对应的键值,
    如果读得到,就直接取值,
    如果读不到,会赋值undefined,后续可以为之赋实值:

    <script>
    
        const app = Vue.createApp({
    
            template: `
                <div>{{age}}</div>    
            `,
            setup(props, context) {
                const { reactive, toRef } = Vue;
                const data = reactive({ name: 'guan'});
                const age = toRef(data, 'age');
                setTimeout(() => {
                    age.value = 'zhao';
                }, 2000);
    
                return { age }
            }
        });
    
        const vm = app.mount('#heheApp');
    </script>
    

    运行两秒后自动更新:

    关于setup函数的三个参数

    setup函数的context参数中的三个属性,即attrs, slots, emit
    获取方法(解构context参数):

    setup(props, context) {
               const { attrs, slots, emit } = context;
               return { };
           }
    
    attrs

    -- 是一个Proxy对象;
    -- 子组件的none-props属性都存进attrs中;

    如下,父组件调用子组件,传递myfield属性,
    子组件没有用props接收,则myfield作为none-props属性被 子组件承接,
    这时候会存进setup函数的attrs中:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Hello World! heheheheheheda</title>
        <script src="https://unpkg.com/vue@next"></script>
    </head>
    <body>
        <div id="heheApp"></div>
    </body>
    <script>
    
        const app = Vue.createApp({
            template: `
                <child myfield='heheda' />    
            `
        });
    
        app.component('child', {
            template:`
                <div>child</div>
            `,
            setup(props, context) {
                const { attrs, slots, emit } = context;
                console.log(attrs);
                return { };
            }
        })
    
        const vm = app.mount('#heheApp');
    </script>
    </html>
    

    运行效果:可以看到attrs也是一个Proxy对象


    (attrs.myfield)打印取值:
    <script>
    
        const app = Vue.createApp({
            template: `
                <child myfield='heheda' />    
            `
        });
    
        app.component('child', {
            template:`
                <div>child</div>
            `,
            setup(props, context) {
                const { attrs, slots, emit } = context;
                console.log(attrs.myfield);
                return { };
            }
        })
    
        const vm = app.mount('#heheApp');
    </script>
    
    slots

    -- 是一个Proxy对象;
    -- 其中有一个 以为default为键的函数,
    这个函数会以 虚拟DOM的形式,
    返回 传给 子组件的slot插槽组件



    打印slots属性:

    <script>
    
        const app = Vue.createApp({
            template: `
                <child>hehehe</child>    
            `
        });
    
        app.component('child', {
            template:`
                <div>child</div>
                <div><slot/></div>
            `,
            setup(props, context) {
                const { attrs, slots, emit } = context;
                console.log(slots);
                return { };
            }
        })
    
        const vm = app.mount('#heheApp');
    </script>
    

    打印slots属性中的 default函数, 可见其返回的是虚拟DOM:

    <script>
    
        const app = Vue.createApp({
            template: `
                <child>hehehe</child>    
            `
        });
    
        app.component('child', {
            template:`
                <div>child</div>
                <div><slot/></div>
            `,
            setup(props, context) {
                const { attrs, slots, emit } = context;
                console.log(slots.default());
                return { };
            }
        })
    
        const vm = app.mount('#heheApp');
    </script>
    
    使用setup中 context参数的 slots属性中的 default方法所返回的 虚拟DOM,通过render函数的形式,在setup函数返回,可以覆盖template的内容,渲染UI
    <script>
    
        const app = Vue.createApp({
            template: `
                <child>hehehe</child>    
            `
        });
    
        app.component('child', {
            template:`
                <div>child Text</div>
                <div><slot/></div>
            `,
            setup(props, context) {
                const { h } = Vue;
                const { attrs, slots, emit } = context;
                return () => h('div', {}, slots.default());
            }
        })
    
        const vm = app.mount('#heheApp');
    </script>
    

    补充:不使用 Vue3 的这个compositionAPI,子组件只能这样去获取slots内容

    通过在其他函数中,使用this.$slots的方式调用到slots内容

    <script>
    
        const app = Vue.createApp({
            template: `
                <child>hehehe</child>    
            `
        });
    
        app.component('child', {
            mounted () {
                console.log("this.$slots --- ",this.$slots);
            },
            setup(props, context) {
                console.log("context.slots --- ", context.slots);
    
                const { h } = Vue;
                const { attrs, slots, emit } = context;
                return () => h('div', {}, slots.default());
            }
        })
    
        const vm = app.mount('#heheApp');
    </script>
    

    运行结果如下,可以看到跟setup函数的context.slots是一样的:

    回顾 没有 CompositionAPI时,emit的用法
    <script>
    
        const app = Vue.createApp({
            methods: {
                handleChange() {
                    alert('hehehe');
                }
            },
            template: `
                <child @change="handleChange">hehehe</child>    
            `
        });
    
        app.component('child', {
            template: `
                <div  >hehehe</div>    
            `,
            mounted () {
                this.$emit('change');
            }
        })
    
        const vm = app.mount('#heheApp');
    </script>
    
    使用setup的 context.emit 替代 this.$emit
    <script>
    
        const app = Vue.createApp({
            methods: {
                handleChange() {
                    alert('hehehe');
                }
            },
            template: `
                <child @change="handleChange">hehehe</child>    
            `
        });
    
        app.component('child', {
            template: `
                <div @click="handleClick">6666</div>    
            `,
            setup(props, context) {
                const { h } = Vue;
                const { attrs, slots, emit } = context;
                function handleClick() {emit('change');}
                return { handleClick };
            }
        })
    
        const vm = app.mount('#heheApp');
    </script>
    

    运行,点击文本:

    使用Composition API开发 todoList

    调测input框事件

    setup中,
    --- const inputValue = ref('6666');定义到一个proxy对象,
    可以将此对象 用于setup外的 template中,
    完成双向绑定中的一环——数据字段映射到 UI!!!;

    --- handleInputValueChange定义一个方法;
    运行时,将对应触发组件内容
    赋值给 inputValue
    完成双向绑定中的另一环——UI 映射到数据!!!;
    template中,
    :value="inputValue"使得对象的内容 初始化显示inputValue的内容;

    --- @input="handleInputValueChange"使得输入框被用户输入新的内容时,
    会调用对应的方法,这里调用:

    <script>
    
        const app = Vue.createApp({
            setup() {
                const { ref } = Vue;
                const inputValue = ref('6666');
                const handleInputValueChange = (e) => {
                    console.log("e --- ",e);
                    console.log("e.target.value", e.target.value);
    
                    inputValue.value = e.target.value;
                }
                return {
                    inputValue,
                    handleInputValueChange
                }
            },
            template: `
                <div>
                    <div>
                        <input :value="inputValue" @input="handleInputValueChange"/>
                        <div>{{inputValue}}</div>
                        <button>提交</button>
                    </div>
                    <ul>
                        <li>1</li>
                        <li>2</li>
                    </ul>                        
                </div>    
            `
        });
    
        const vm = app.mount('#heheApp');
    </script>
    
    运行效果:

    完善toDoList案例

    --- setup 中定义 数组list,以及 handleSubmit处理函数,
    并都在return中返回;

    --- template中,
    button添加click事件回调;
    li 标签,使用v-for,完成列表渲染:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Hello World! heheheheheheda</title>
        <script src="https://unpkg.com/vue@next"></script>
    </head>
    <body>
        <div id="heheApp"></div>
    </body>
    <script>
    
        const app = Vue.createApp({
            setup() {
                const { ref, reactive } = Vue;
                const inputValue = ref('6666');
                const list = reactive([]);
                const handleInputValueChange = (e) => {
                    inputValue.value = e.target.value;
                }
                const handleSubmit = () => {
                    console.log("now, inputValue.value --- ", inputValue.value);
                    list.push(inputValue.value);
                }
                return {
                    list,
                    inputValue,
                    handleInputValueChange,
                    handleSubmit
                }
            },
            template: `
                <div>
                    <div>
                        <input :value="inputValue" @input="handleInputValueChange"/>
                        <button @click="handleSubmit">提交</button>
                    </div>
                    <ul>
                        <li v-for="(item, index) in list" :key="index">{{item}}</li>
                    </ul>                        
                </div>    
            `
        });
    
        const vm = app.mount('#heheApp');
    </script>
    </html>
    
    运行效果:

    优化上例的逻辑结构!

    如下,将 setup()中,
    --- 与 list 相关的定义和操作函数,
    封装到 listHandleAction中,然后return;

    --- 与 inputValue 相关的定义和操作函数,
    封装到 inputHandleAction中,然后return;

    --- 最后 setup() 调用以上两个业务模块封装函数
    解构返回的内容,进行中转调用

    【分模块 聚合业务逻辑,只留一个核心方法进行统筹调度,有MVP那味了】
    【这样设计,业务分明,方便定位问题,可读性、可维护性高!!

    <script>
    
        const listHandleAction = () => {
            const { reactive } = Vue;
            const list = reactive([]);
            const addItemToList = (item) => {
                console.log("now, item --- ", item);
                list.push(item);
            }
            return { list, addItemToList }
        }
    
        const inputHandleAction = () => {
            const { ref } = Vue;
            const inputValue = ref('');
            const handleInputValueChange = (e) => {
                inputValue.value = e.target.value;
            }
            return { inputValue, handleInputValueChange }
        }
    
        const app = Vue.createApp({
            setup() {
                const { list, addItemToList } = listHandleAction();
                const { inputValue, handleInputValueChange } = inputHandleAction();
                return {
                    list, addItemToList,
                    inputValue, handleInputValueChange
                }
            },
            template: `
                <div>
                    <div>
                        <input :value="inputValue" @input="handleInputValueChange"/>
                        <button @click="addItemToList(inputValue)">提交</button>
                    </div>
                    <ul>
                        <li v-for="(item, index) in list" :key="index">{{item}}</li>
                    </ul>                        
                </div>    
            `
        });
    
        const vm = app.mount('#heheApp');
    </script>
    
    运行效果:

    setup的 computed 计算属性

    <script>
    
        const app = Vue.createApp({
            setup() {
                const { ref, computed } = Vue;
                const count = ref(0);
                const handleClick = () => {
                    count.value += 1;
                }
                const countAddFive = computed(() => {
                    return count.value + 5;
                })
                return { count, countAddFive, handleClick }
            },
            template: `
                <div>
                    <div>
                        <span @click="handleClick">{{count}}</span> --- {{countAddFive}}
                    </div>
                </div>    
            `
        });
    
        const vm = app.mount('#heheApp');
    </script>
    
    运行结果:

    当然以上是computed 的默认用法,实际上它可以接收一个对象

    这个对象包含两个函数属性,
    第一个是get函数,其内容即取值时候返回的内容,同默认用法;
    第二个是set函数,当试图修改computed变量的值时,就会回调这个方法,
    接收的参数,即试图修改的值

    如下,试图在3秒后修改computed变量countAddFive的值,
    这时回调set方法:

    <script>
    
        const app = Vue.createApp({
            setup() {
                const { ref, computed } = Vue;
                const count = ref(0);
                const handleClick = () => {
                    count.value += 1;
                }
                const countAddFive = computed({
                    get: () => {
                        return count.value + 5;
                    },
                    set: (param) => {
                        count.value = param -5;
                    }
                })
    
                setTimeout(() => {
                    countAddFive.value = 1000;
                }, 2000);
    
                return { count, countAddFive, handleClick }
            },
            template: `
                <div>
                    <div>
                        <span @click="handleClick">{{count}}</span> --- {{countAddFive}}
                    </div>
                </div>    
            `
        });
    
        const vm = app.mount('#heheApp');
    </script>
    
    运行3s后:

    将上例的处理值换成 Object类型,再例

    <script>
    
        const app = Vue.createApp({
            setup() {
                const { reactive, computed } = Vue;
                const countObj = reactive({ count: 0 });
                const handleClick = () => {
                    countObj.count += 1;
                }
                const countAddFive = computed({
                    get: () => {
                        return countObj.count + 5;
                    },
                    set: (param) => {
                        countObj.count = param -5;
                    }
                })
    
                setTimeout(() => {
                    countAddFive.value = 1000;
                }, 2000);
    
                return { countObj, countAddFive, handleClick }
            },
            template: `
                <div>
                    <div>
                        <span @click="handleClick">{{countObj.count}}</span> --- {{countAddFive}}
                    </div>
                </div>    
            `
        });
    
        const vm = app.mount('#heheApp');
    </script>
    

    运行效果同上例;

    setup 中的 watch 监听

    如下,
    ---watch一个参数为要监听的引用,
    第二个参数为函数类型,当监听的引用发生变化时会回调,
    其有两个参数,一个是当前(变化后的)值,一个是变化前的值;

    --- input组件中,v-model完成双向绑定!!!

    --- input输入内容时,触发 双向绑定的特性,
    内容映射到name引用上,
    ref响应特性name的内容又映射到{{name}}DOM节点上:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Hello World! heheheheheheda</title>
        <script src="https://unpkg.com/vue@next"></script>
    </head>
    <body>
        <div id="heheApp"></div>
    </body>
    <script>
    
        const app = Vue.createApp({
            setup() {
                const { ref, watch } = Vue;
                const name = ref('heheda')
                watch(name, (currentValue, prevValue)  => {
                    console.log(currentValue, prevValue);
                })
                return { name }
            },
            template: `
                <div>
                    <div>
                        Name:<input v-model="name">
                    </div>
                    <div>
                        Name is {{name}}
                    </div>
                </div>    
            `
        });
    
        const vm = app.mount('#heheApp');
    </script>
    </html>
    
    运行效果:

    setup 中的 watch 监听:监听Object类型

    注意setup的watch的 可监听数据类型

    所以,这里主要是
    ---将watch的 第一个参数改成 函数;
    ---使用toRefs,简化传递过程;
    (不然template要接收和处理的就是 nameObject.name了,而不是这里的name)
    <script>
    
        const app = Vue.createApp({
            setup() {
                const { reactive, watch, toRefs } = Vue;
                const nameObj = reactive({name: 'heheda'});
                watch(() => nameObj.name, (currentValue, prevValue)  => {
                    console.log(currentValue, prevValue);
                });
    
                const { name } = toRefs(nameObj);
                return { name }
            },
            template: `
                <div>
                    <div>
                        Name:<input v-model="name">
                    </div>
                    <div>
                        Name is {{name}}
                    </div>
                </div>    
            `
        });
    
        const vm = app.mount('#heheApp');
    </script>
    

    运行效果同上例;

    setup 中的 watch 监听:监听Object类型的 多个属性

    注意watch的参数写法,
    一参写成,以函数类型元素数组
    二参,参数列表写成两个数组,
    第一个为current值数组,第二个为prev值数组;

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Hello World! heheheheheheda</title>
        <script src="https://unpkg.com/vue@next"></script>
    </head>
    <body>
        <div id="heheApp"></div>
    </body>
    <script>
    
        const app = Vue.createApp({
            setup() {
                const { reactive, watch, toRefs } = Vue;
                const nameObj = reactive({name: 'heheda', englishName:"lueluelue"});
                watch([() => nameObj.name, () => nameObj.englishName],
                     ([curName, curEngN], [prevName, prevEngN])  => {
                    console.log(curName, prevName, curEngN, prevEngN);
                });
    
                const { name, englishName } = toRefs(nameObj);
                return { name, englishName }
            },
            template: `
                <div>
                    <div>
                        Name:<input v-model="name">
                    </div>
                    <div>
                        Name is {{name}}
                    </div>
                    <div>
                        EnglishName:<input v-model="englishName">
                    </div>
                    <div>
                        EnglishName is {{englishName}}
                    </div>
                </div>    
            `
        });
    
        const vm = app.mount('#heheApp');
    </script>
    </html>
    
    运行,先在Name输入框输入,后再EnglishName框输入,效果:

    setup 中的 watchEffect监听 以及 与 watch 的异同比较

    函数中,使得纯函数 变成 非纯函数的 异步处理等部分逻辑块,
    称之为effect块

    • --- watch惰性的,只有watch监听的字段发生变化时,
      watch的处理逻辑才会被回调;
      --- watchEffect即时性的,也就是除了watch回调特性
      watchEffect处理逻辑还会在页面渲染完成时立马先执行一次,
      watchEffect监听的字段未曾改变,
      watchEffect就已经执行了一次;

      (实例可以见下例运行效果)

    • watch需要写明监听字段
      watchEffect不需要,直接写处理逻辑即可,
      底层封装!自动监听!所写处理逻辑中用到的!所有字段!


      如下例子中,
      watchEffect的处理逻辑——console.log(nameObj.name, nameObj.englishName);
      仅一行代码,
      完成对nameObj.namenameObj.englishName两个字段的监听,
      并完成了处理逻辑;

    • watch可以直接从参数列表中获取到之前(变化前)的值当前(变化后)的值
      watchEffect不行,处理逻辑中拿到的直接就是当前(变化后)的值


    • 两者都可以用以下的方式,在一个设定的时延之后,停止监听
    • watch 配置 immediate属性,可使具备同watchEffect的 即时性
    <script>
    
        const app = Vue.createApp({
            setup() {
                const { reactive, watch, watchEffect, toRefs } = Vue;
                const nameObj = reactive({name: 'heheda', englishName:"lueluelue"});
    
                watchEffect(() => {
                    console.log(nameObj.name, nameObj.englishName);
                })
    
                const { name, englishName } = toRefs(nameObj);
                return { name, englishName }
            },
            template: `
                <div>
                    <div>
                        Name:<input v-model="name">
                    </div>
                    <div>
                        Name is {{name}}
                    </div>
                    <div>
                        EnglishName:<input v-model="englishName">
                    </div>
                    <div>
                        EnglishName is {{englishName}}
                    </div>
                </div>    
            `
        });
    
        const vm = app.mount('#heheApp');
    </script>
    

    跟紧console.log(nameObj.name, nameObj.englishName);
    先在Name输入框输入123,后再EnglishName框输入456,运行效果:

    注意第一行打印,第一行是页面渲染完成时立马执行,
    用户未曾输入内容,watchEffect监听的字段未曾改变,
    watchEffect就已经执行了一次,体现watchEffect即时性!!!

    两者都可以用以下的方式,在一个设定的时延之后,停止监听

    watch / watchEffect函数返回值 赋给一个字段(如下stopWatch / stopWatchEffect);
    接着在watch / watchEffect处理逻辑中,
    编写类似setTimeout的异步函数,
    并在其中调用 与 刚刚定义的字段 同名的 函数(如下stopWatch() / stopWatchEffect()),
    即可停止对应的监听

    <script>
    
        const app = Vue.createApp({
            setup() {
                const { reactive, watch, watchEffect, toRefs } = Vue;
                const nameObj = reactive({name: 'heheda', englishName:"lueluelue"});
                const stopWatch = watch([() => nameObj.name, () => nameObj.englishName],
                     ([curName, curEngN], [prevName, prevEngN])  => {
                    console.log(curName, prevName, curEngN, prevEngN);
                    setTimeout(() => {
                        stopWatch();
                    }, 3000);
                });
    
                const stopWatchEffect = watchEffect(() => {
                    console.log(nameObj.name, nameObj.englishName);
                    setTimeout(() => {
                        stopWatchEffect();
                    }, 3000);
                })
    
                const { name, englishName } = toRefs(nameObj);
                return { name, englishName }
            },
            template: `
                <div>
                    <div>
                        Name:<input v-model="name">
                    </div>
                    <div>
                        Name is {{name}}
                    </div>
                    <div>
                        EnglishName:<input v-model="englishName">
                    </div>
                    <div>
                        EnglishName is {{englishName}}
                    </div>
                </div>    
            `
        });
    
        const vm = app.mount('#heheApp');
    </script>
    

    运行,可以看到,
    前3s(我们设定的时延)两个输入框是可以响应监听的,
    但是3s之后,无论怎么输入内容,console也不会打印log,
    因为这时两个监听效果已经取消了:

    watch 配置 immediate属性,可使具备同watchEffect的 即时性

    如下,使用{ immediate: true}watch 配置 immediate属性,
    可使具备同watchEffect的 即时性:

    <script>
    
        const app = Vue.createApp({
            setup() {
                const { reactive, watch, toRefs } = Vue;
                const nameObj = reactive({name: 'heheda', englishName:"lueluelue"});
                const stopWatch = watch([() => nameObj.name, () => nameObj.englishName],
                     ([curName, curEngN], [prevName, prevEngN])  => {
                    console.log(curName, prevName, curEngN, prevEngN);
                }, { immediate: true});
    
                const { name, englishName } = toRefs(nameObj);
                return { name, englishName }
            },
            template: `
                <div>
                    <div>
                        Name:<input v-model="name">
                    </div>
                    <div>
                        Name is {{name}}
                    </div>
                    <div>
                        EnglishName:<input v-model="englishName">
                    </div>
                    <div>
                        EnglishName is {{englishName}}
                    </div>
                </div>    
            `
        });
    
        const vm = app.mount('#heheApp');
    </script>
    

    运行效果如下,undefined是因第一次执行时,
    监听的变量还没有变化,
    所以就没有先前值(prevValue)的说法,只有当前值(currentValue)

    setup 中的 生命周期

    --- Vue3.0提供了一些对应生命周期的,可以写在setup函数中的回调方法;
    (具体请看 下方例程)

    --- setup函数的执行时间点 在于beforeCreateCreated之间,
    所以CompositionAPI里边是没有类似onBeforeCreateonCreated的方法的,
    要写在这两个周期中的逻辑,
    直接写在setup中即可;

    下面是两个Vue3.0引入的新钩子:
    --- onRenderTracked
    渲染跟踪,
    跟踪 收集响应式依赖的时机,
    每次准备开始渲染时(onBeforeMount后,onMounted前)回调;

    --- onRenderTriggered
    渲染触发,
    每次触发页面重新渲染时回调,
    回调后,下一轮的 onBeforeMount紧跟其后;

    <script>
    
        const app = Vue.createApp({
            setup() {
                const { ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated,
                     onRenderTracked, onRenderTriggered } = Vue;
                const heheda = ref('heheda');
                onBeforeMount(() => {
                    console.log('onBeforeMount');
                }),
                onMounted(() => {
                    console.log('onMounted');
                }),
                onBeforeUpdate(() => {
                    console.log('onBeforeUpdate');
                }),
                onUpdated(() => {
                    console.log('onUpdated');
                }),
                onRenderTracked((event) => {
                    console.log('onRenderTracked', event);
                }),
                onRenderTriggered((event) => {
                    console.log('onRenderTriggered', event);
                })
                const handleClick = () => {
                    heheda.value = 'lululu';
                    console.log("you had clicked the text!");
                }
                return { heheda, handleClick }
            },
            template: `
                <div @click="handleClick">
                    {{heheda}}
                </div>    
            `
        });
    
        const vm = app.mount('#heheApp');
    </script>
    

    运行效果:

    setup中的provide、inject用法

    --- 父组件拿出provide,提供键值对;
    --- 子组件拿出inject,一参为接收键,二参为默认值;

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Hello World! heheheheheheda</title>
        <script src="https://unpkg.com/vue@next"></script>
    </head>
    <body>
        <div id="heheApp"></div>
    </body>
    <script>
    
        const app = Vue.createApp({
            setup() {
                const { provide } = Vue;
                provide('name', 'li');
                return { }
            },
            template: `
                <div>
                    <child />
                </div>    
            `
        });
    
        app.component('child', {
            setup() {
                const { inject } = Vue;
                const name = inject('name', 'default');
                return {name}
            },
            template: `
                <div>{{name}}</div>    
            `
        })
    
        const vm = app.mount('#heheApp');
    </script>
    </html>
    
    运行结果:

    配合上ref实现 响应特性 以及 readonly实现 单向数据流

    --- setup中, 借provide传输的 数据字段 配合上ref
    使之具备响应的特性;
    --- 父组件提供 更改数据的接口,
    在使用provide传递 数据字段时,加上 readonly包裹,
    使得子组件 需要更改 父组件传递过来的数据字段 时,
    无法直接 修改字段,
    需调用 父组件的接口方法 更改,
    按 单向数据流 规范编程;

    <script>
    
        const app = Vue.createApp({
            setup() {
                const { provide, ref, readonly } = Vue;
                const name = ref('li');
                provide('name', readonly(name));
                provide('changeName', (value) => {
                    name.value = value;
                })
                return { }
            },
            template: `
                <div>
                    <child />
                </div>    
            `
        });
    
        app.component('child', {
            setup() {
                const { inject } = Vue;
                const name = inject('name', 'default');
                const changeName = inject('changeName');
                const handleClick = () => {
                    changeName('lin');
                }
                return { name, handleClick }
            },
            template: `
                <div @click="handleClick">{{name}}</div>    
            `
        })
    
        const vm = app.mount('#heheApp');
    </script>
    
    运行效果: 点击:

    setup结合ref指令

    前面笔记《Vue3 | Mixin、自定义指令、Teleport传送门、Render函数、插件 详解 及 案例分析》有写到普通场景的ref指定;

    --- setup中的ref是前面说的生成响应式字段的意思;
    --- template中的ref是获取对应的dom节点;
    --- 而当 template中的ref指定的字段名,
    跟setup中的ref生成的响应式字段名一样的时候,两者就会关联起来;

    如下,
    template中和setup中的字段heheda关联起来,
    在setup的onMounted中,使用这个DOM节点字段,
    打印DOM代码:

    <script>
    
        const app = Vue.createApp({
            setup() {
                const { ref, onMounted } = Vue;
                const heheda = ref(null);
                onMounted(() => {
                    console.log(heheda);
                    console.log(heheda.value);
                })
                return { heheda }
            },
            template: `
                <div>
                    <div ref="heheda">lueluelue</div>
                </div>    
            `
        });
    
    
        const vm = app.mount('#heheApp');
    </script>
    
    运行结果:

    相关文章

      网友评论

          本文标题:Vue3 | Composition API 包括setup、r

          本文链接:https://www.haomeiwen.com/subject/lziokltx.html