Skip to content

英文:Composition API

setup

知识点

  1. setup 是 Vue3 中新的配置项,值为一个函数。特别重要!!!

  2. setup 是所有组合式 API 表演的舞台

  3. setup 在 beforeCreate 之前执行一次,在 setup 方法中 this 的指向是 undefined。

  4. setup 不能用 async 修饰。

  5. 组件中所用到的数据、方法、计算属性等,均要配置在 setup 中。

    js
    export default {
    	name: 'Vue3App',
    	setup() {
    		let name = '张三';
    		let age = 20;
    		let nameClick = () => {
    			console.log('this is name click function');
    		}
    
    		return { name, age, nameClick };
    	}
    };

注意

  1. Vue3 配置中可以写 Vue2 的 data、methods 等,但不推荐。
  2. 若混合写,Vue3 不能访问到 Vue2 配置中的数据、方法等。Vue2 可以访问 Vue3 中的数据和方法。
  3. 若混合写,属性或方法名重复时,以 Vue3 为主。

setup 返回值

  1. 返回对象:对象中的数据、方法在模板中可以直接使用。
  2. 返回函数:略。

setup 参数

  1. props:值为对象。是由组件外部传递且在组件内部声明接收的属性。
  2. context:上下文对象。
    • attrs:值为对象。是由组件外部传递且在组件内部 没有 声明接收的属性。
    • slots:插槽内容。
    • emit:分发自定义事件。
vue
<template>
    <Demo name="iGma" :age="20" @info="emitInfo" />
</template>

<script>
import { defineAsyncComponent } from 'vue';		// 异步加载组件

export default {
    name: 'Vue3App',
    components: {
        Demo: defineAsyncComponent(() => import('./components/Demo'))
    },
    setup() {
        const emitInfo = (e) => {
            console.log(e);
        }

        return { emitInfo }
    }
};
</script>
vue
<template>
    <div @click="infoClick">
        <h2>姓名:{{ name }}</h2>
        <h2>年龄:{{ age }}</h2>
    </div>
</template>

<script>
    export default {
        name: 'Vue3Demo',
        props: ['name', 'age'],
        setup(props, context) {
            const infoClick = () => {
                context.emit('info', props);
            }

            return { infoClick }
        }
    };
</script>

ref

  1. 作用:定义一个响应式数据。通过 ref 函数可以将一个数据加工成 引用对象,即 ref 函数的返回值是一个引入对象 —— RefImpl。
  2. ref 接受的数据可以是基本类型,也可以是对象类型。
    • 基本类型数据响应式依然使用 Object.defineProperty()getset 方法完成的。
    • 对象类型的数据是借助 Vue3 内部的 reactive 函数完成的。
  3. 语法
vue
<script>
import { ref } from 'vue'

export default {
    name: 'Vue3App',
    setup() {
        // 定义
        let name = ref('张三');
        let age = ref(20);
        let school = ref({
            name: '内蒙古农业大学',
            address: '内蒙古'
        })

        // 改变数据
        let changeInfo = () => {
            name.value = '李四';
            age.value = 21;
            school.value.name = '包头师范大学';
            school.value.address = '包头'
        }

        return { name, age, school, changeInfo }
    }
};
</script>

reactive

  1. 作用:定义一个对象类型数据的响应式(基本数据类型不可以)。
  2. 语法:const 代理对象 = reactive(源对象)
  3. reactive 定义的响应式数据是深层次的,第 n 层数据发生变化时 Vue 也能监听到。
  4. 内部基于 ES6 的 Proxy 实现的,通过代理对象操作源对象内部数据。
vue
<script>
import { reactive } from 'vue'
    
export default {
    name: 'Vue3App',
    setup() {
        // 定义
        let data = reactive({
            name: '张三',
            age: 20,
            school: {
                name: '内蒙古农业大学',
                address: '呼和浩特'
            }
        })

        // 使用
        let changeInfo = () => {
            data.name = '李四';
            data.age = 18;
            data.school.name = '包头师范大学';
            data.school.address = '包头';
        }

        return { data, changeInfo }
    }
};
</script>

响应式原理

Vue2 的响应式

  1. 实现原理:

    • 对象类型通过 Object.defineProperty() 对属性的读取、修改进行拦截(数据劫持)。
    • 数组类型通过重写更新数组的一系列方法来实现拦截。
  2. $

    js
    this.$set(对象, key, value);	// 新增 or 修改对象属性
    this.$delete(对象, key);	// 删除对象属性
  3. 存在的问题:

    • 新增属性、删除属性,页面不更新。
    • 直接通过下标改数组,页面也不更新。

Vue3 的响应式

  1. 通过 Proxy 代理对象拦截对象中任意属性的变化,包括读写、添加、删除等。

  2. 通过 Reflect 反射对象对源对象的属性进行操作。

    js
    let obj = {
        name: '张三',
        age: 20,
    }
    
    let p = new Proxy(obj, {
        get(obj, key) {
            console.log('读取数据');
            return Reflect.get(obj, key);
        },
        set(obj, key, value) {
            console.log('修改 or 新增数据');
            Reflect.set(obj, key, value);
        },
        deleteProperty(obj, key) {
            console.log('删除数据');
            return Reflect.deleteProperty(obj, key);
        }
    });

计算属性

vue
<template>
    单价:<input type="number" v-model="data.money" /><br>
    数量:<input type="number" v-model="data.number" /><br>
    <p>简单写法总计:{{ data.total }}</p>
    <p>完整写法总计:{{ data.total_com }}</p>
</template>

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

export default {
    name: 'Vue3Demo',
    setup() {
        const data = reactive({
            money: 1,
            number: 1
        });

        // 简写
        data.total = computed(() => data.money * data.number);

        // 完整写法
        data.total_com = computed({
            get: () => data.money * data.number,
            set(value) {
                
            }
        });

        return { data }
    }
};
</script>

监听属性

知识点

watch 方法有三个参数:

  • 参数一:要监听的属性,可以是单个属性,也可以是多个属性,多个属性用数组。类型(ref 对象,reactive 对象)
  • 参数二:回调函数。
  • 参数三:配置对象。

案例

监听单个 ref 数据
vue
<template>
    <h3 @click="sum++">sum: {{ sum }}</h3>
</template>

<script>
    import { ref, watch } from 'vue'
    
    export default {
        name: 'Vue3Demo',
        setup() {
            let sum = ref(0);

            // 监听单个 ref 数据
            watch(sum, (n,o) => {
                console.log(`sum 新值为 ${n},旧值为 ${o}`);
            });

            return { sum }
        }
    };
</script>
监听多个 ref 数据
vue
<template>
    <h3 @click="sum++">sum: {{ sum }}</h3>
    <h3 @click="msg += '!'">msg: {{ msg }}</h3>
</template>

<script>
    import { ref, watch } from 'vue'
    export default {
        name: 'Vue3Demo',
        setup() {
            let sum = ref(0);
            let msg = ref('Hello');

            // 监听多个 ref 数据
            watch([sum, msg], (n, o) => {
                console.log(`数据发生变化`, n, o);
            })

            return { sum, msg }
        }
    };
</script>
监听 reactive 数据的全部属性
  • 坑 1:此处无法正确获取 oldValue。
  • 坑 2:强制开启了深度监听,deep 配置无效。
vue
<template>
    <h3>姓名:{{ data.name }}</h3>
    <h3>年龄:{{ data.age }}</h3>
    <h3>Job:{{ data.job.a }}</h3>
    <button @click="data.age++">age++</button>
    <button @click="data.job.a++">job.a++</button>
</template>

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

export default {
    name: 'Vue3App',
    setup() {
        const data = reactive({
            name: 'iGma',
            age: 20,
            job: {
                a: 10
            }
        });
        watch(data, (nv, ov) => {
            console.log(nv, ov);
        })

        return { data };
    },
};
</script>
监听 reactive 数据的某个属性

watch 方法的第一个参数是一个带有返回值的方法,切返回值是要监听的数据。

vue
<script>
    import { reactive, watch } from 'vue'
    
    export default {
        name: 'Vue3Demo',
        setup() {
            let info = reactive({
                name: '张三',
                age: 10,
            });

            watch(() => info.age,(n, o) => {
                console.log(n, o);
            })

            return { info }
        }
    };
</script>
监听 reactive 数据的某些属性
vue
<script>
    import { reactive, watch } from 'vue'
    
    export default {
        name: 'Vue3Demo',
        setup() {
            let info = reactive({
                name: '张三',
                age: 10,
                school: {
                    uc: {
                        name: '内蒙古农业大学'
                    }
                }
            });

            watch([() => info.age, () => info.name],(n, o) => {
                console.log(n, o);
            })

            return { info }
        }
    };
</script>
监听 reactive 数据的某个对象类型的属性(deep 有效,oldValue 无效 )
vue
<script>
    import { reactive, watch } from 'vue'
    
    export default {
        name: 'Vue3Demo',
        setup() {
            let info = reactive({
                name: '张三',
                age: 10,
                school: {
                    uc: {
                        name: '内蒙古农业大学'
                    }
                }
            });

            watch(() => info.school,(n, o) => {
                console.log(n, o);
            }, { deep: true })

            return { info }
        }
    };
</script>

智能监听 watchEffect

  1. watchEffect 方法中用到的属性即是要监听的属性。
  2. watchEffect 方法和 computed 有点像,不同的是 computed 注重的是计算出来的值,而 watchEffect 更注重的是计算的过程。
vue
<script>
    import { reactive, watchEffect } from 'vue'
    
    export default {
        name: 'Vue3Demo',
        setup() {
            let info = reactive({
                name: '张三',
                age: 10,
                school: {
                    uc: {
                        name: '内蒙古农业大学'
                    }
                }
            });

            watchEffect(() => {
                let { age } = info;
                let { name } = info.school.uc;
                console.log('监听 info.age 和 info.school.uc.name 属性的变化');
            })

            return { info }
        }
    };
</script>

生命周期

生命周期

配置项写法

vue
<template>
    <button @click="show = !show">显示 or 隐藏 Demo 组件</button>
    <Demo v-if="show" />
</template>

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

export default {
    name: 'Vue3Demo',
    components: {
        Demo: defineAsyncComponent(() => import('./components/Demo'))
    },
    setup() {
        let show = ref(true);
        return { show };
    },
};
</script>
vue
<template>
    <h2 @click="sum++">sum: {{ sum }}</h2>
</template>

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

export default {
    name: 'Vue3Demo',
    setup() {
        let sum = ref(0);

        return { sum }
    },
    beforeCreate() {
        console.log('---beforeCreate---');
    },
    created() {
        console.log('---created---');
    },
    beforeMount() {
        console.log('---beforeMount---');
    },
    mounted() {
        console.log('---mounted---');
    },
    beforeUpdate() {
        console.log('---beforeUpdate---');
    },
    updated() {
        console.log('---updated---');
    },
    beforeUnmount() {
        console.log('---beforeUnmount---');
    },
    unmounted() {
        console.log('---unmounted---');
    }
};
</script>

组合式 API 写法

  1. 组合式 API 的执行早于配置项写法
  2. 对应关系
配置项组合式 API
beforeCreatesetup()
createdsetup()
beforeMountonBeforeMount()
mountedonMounted()
beforeUpdateonBeforeUpdate()
updatedonUpdated()
beforeUnmountonBeforeUnmount()
unmountedonUnmounted()
vue
<template>
    <h2 @click="sum++">sum: {{ sum }}</h2>
</template>

<script>
import { ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from 'vue';
export default {
    name: 'Vue3Demo',
    setup() {
        let sum = ref(0);
        console.log('---setup---');
        
        onBeforeMount(() => {
            console.log('---onBeforeMount---');
        })
        onMounted(() => {
            console.log('---onMounted---');
        })
        onBeforeUpdate(() => {
            console.log('---onBeforeUpdate---');
        })
        onUpdated(() => {
            console.log('---onUpdated---');
        })
        onBeforeUnmount(() => {
            console.log('---onBeforeUnmount---');
        })
        onUnmounted(() => {
            console.log('---onUnmounted---');
        })

        return { sum }
    },
};
</script>

自定义 Hook

  1. Hook 本质是一个函数,它可以把 setup 中使用到的组合式 API 进行封装,方便其他组件复用。
  2. 类似于 Vue2 中的 mixin。
  3. 创建 /src/hooks 目录,用来存放自定义 Hook 文件。
js
import { ref } from "vue"
    
export default () => {
   let page = ref(0);
   let pagesize = ref(10);
   
   // 获取列表
   const getList = () => {
      console.log('发送网络请求');
   };
   
   // 点击分页
   const changePage = (e) => {
      console.log(`changePage 接收到参数 e = ${e}`);
      page.value = e;
      getList();
   };
   
   return { page, pagesize, getList, changePage }
}
vue
<template>
    <h3>当前页码:{{ page }}</h3>
    <h3>单页数据量:{{ pagesize }}</h3>
    <button @click="changePage(2)">改变页码</button>
    <button @click="getList">发送网络请求</button>
</template>

<script>
import usePages from "../hooks/usePage";

export default {
   name: 'Vue3Demo',
   setup() {
      return { ...usePages() }
   },
};
</script>

toRef 与 toRefs

  1. 作用:创建一个 ref 对象,其 value 值指向另一个对象中的某个属性。
  2. 语法:const name = toRef(object, key)
  3. 应用:要将响应式对象中的某个属性单独提供给外部使用,其保留响应式。
vue
<template>
    <h3>姓名:{{ name }}</h3>
    <h3>年龄:{{ age }}</h3>
    <h3>工资:{{ k }}</h3>

    <button @click="name += '~'">改变 name</button>
    <button @click="age++">改变 age</button>
    <button @click="k++">改变 k</button>
</template>

<script>
    import {onUpdated, reactive, toRef, toRefs} from 'vue';

    export default {
        name: 'Vue3Demo',
        setup() {
            const data = reactive({
                name: 'iGma',
                age: 20,
                job: {
                    k: 10
                }
            });

            onUpdated(() => {
                console.log(data);
            })

            return {
                k: toRef(data.job, 'k'),
                ...toRefs(data)
            }
        },
    };
</script>

基于 MIT 许可发布