Vue3.0 Compsition API 详解

856 阅读6分钟

前言

本文主要介绍Vue3的新特性以及使用方法。从Vue3发布beta版本,到现在2个月了,还没有学习的小伙伴,要抓紧学习了。

正文

  • 3.0 & 2.0 区别

  1. 数据驱动能力更强,重构响应式系,基于 proxy 的数据驱动能够弥补原来 definePorperty 的不足,使用Proxy的优势:
1.Proxy在使用上比Object.defineProperty方便的多
2.可直接监听数组类型的数据变化
3.Proxy代理整个对象,Object.defineProperty只代理对象上的某个属性
4.可拦截apply、ownKeys、has等方法,而Object.defineProperty不行
5.直接实现对象属性的新增/删除
  1. 新增Composition API,更好的逻辑复用和代码组织
  2. 重构 Virtual DOM
1. 模板编译时的优化,将一些静态节点编译成常量
2. slot优化,将slot编译为lazy函数,将slot的渲染的决定权交给子组件
3. 模板中内联事件的提取并重用(原本每次渲染都重新生成内联函数)
  1. 代码结构调整,更有利于Tree shaking,使得体积更小
  2. 使用Typescript替换Flow,全面拥抱 typescript,2.x 版本无论用 class component 还是 配置 都不能很好的支持 ts.
  • 3.0 新特性

生命周期 & 常用api对比

Vue2.xVue3.x
beforeCreatesetup()
createdsetup()
beforeMountonBeforeMount()
mountedonMounted()
beforeUpdateonBeforeUpdate()
updatedonUpdated()
beforeUnmountonBeforeUnmount()
unmountedonUnmounted()
dataref()、reactive()
watchwatch()、watchEffect()
computedcomputed()
this.$storeuseStore()
this.$routeruseRouter()
this.$routeuseRoute()

setup()

setup 函数是 Compsition API 的入口函数,替代了我们之前的生命周期函数beforeCreate、created。
setup接受2个参数
1. props: 用来接收父组件传入的值
2. { attrs, emit, slots } = context

ref

ref 函数将我们定义的变量包装成了一个响应式的数据对象。
ref 对象拥有一个指向内部值的单一属性 .value,每次访问时我们都需要加上 .value,但在模板中使用时,它会自动解套,无需在模板内额外书写 .value

reactive()

reactive 函数是用来创建一个响应式的数据对象,类似我们之前Vue2.x的data

区别 ref、reactive

可以看出,点击按钮后,name的值变成了lisi,但是视图并没有更新,还是zhangsan,但是ref的age却更新了。
当然,这并不是bug,原因在于一个响应型对象(reactive object) 一旦被销毁或展开(...state),其响应式特性(reactivity)就会丢失。

那么如何解决reactive展开后丢失响应式特效呢,下面会介绍 toRef、toRefs

建议:
1. 基本类型值(StringNmuberBoolean 等)或单值对象(类似像 {count: 3} 这样只有一个属性值的对象)使用 ref
2. 引用类型值(ObjectArray)使用 reactive

isref() & unref() & toRef() & toRefs()

1.  isref函数用来判断一个数据是否是响应式的
const isAgeRef = isRef(age);
console.log(isAgeRef); //true

2.  unref函数,接收一个ref参数,如果这个值是 ref 就返回 .value,否则原样返回
const age = ref(1);
const newAge = unref(age)
console.log(newAge) // 1

3.  toRef函数将一个数据变成响应式数据
let age = 20;
let newAge = toRef(age)
console.log(age) // 20 
console.log(newAge) // ObjectRefImpl {_object: 20, _key: undefined, __v_isRef: true}

4. toRefs函数就是将传入的对象里所有的属性的值都转化为响应式数据对象,解决上面reactive展开后丢失响应式问题
return {
	...toRefs(state),
	age,
	handleClick,
};

shallowRef() & triggerRef()

通常我们使用 ref() 函数时,目的是为了引用原始类型值,例如:ref(1)。但我们仍然可以引用非基本类型值,例如一个对象:

const refObj = ref({ foo: 1 })

此时,refObj.value 是一个对象,这个对象依然是响应的,例如如下代码会触发响应:

refObj.value.foo = 2

shallowRef() 顾名思义,它只代理 ref 对象本身,也就是说只有 .value 是被代理的,而 .value 所引用的对象并没有被代理:

const refObj = shallowRef({ foo: 1 })

refObj.value.foo = 3 // 无效,值改变了,但是并不会更新试图

refObj.value = { // 有效,值改变了,并更新视图
    foo:2
}
但是这样就带来一个问题,改个数据还要重新赋值,太麻烦了,这就用到了 triggerRef,我们需要在赋值后面调用依次triggerRef()函数,并把需要更新的ref传入进入,达到更新视图的目的。
refObj.value.foo = 3 
triggerRef(refObj)

shallowReactive()

shallowReactive函数是用来定义一个渐层的 reactive。
每一层都用 Proxy 进行了包装

const obj = {
	name: "zhangsan",
	children: {
		name: "lisi",
	},
}

const state = reactive(obj);
console.log(state);
console.log(state.children);

只有第一层被 Proxy 处理了,也就是说只有修改第一层的值时,才会响应式更新

const shallowState = shallowReactive(obj)
console.log(shallowState);
console.log(shallowState.children);

toRaw()

toRaw函数接收代理对象作为参数,并获取原始对象。
由于经过ref和reactive包装后的对象,两者是一个引用关系,当我们修改原始值时,reactive 的值也会跟着改变,但是视图不会更新
由此可见,当我们想修改数据,但不想让视图更新时,可以选择直接修改原始数据上的值,因此需要先获取到原始数据,我们可以使用 Vue3 提供的 toRaw 方法
 const obj = {
    name: '前端印象',
    age: 22
}

const state = reactive(obj) 
const raw = toRaw(state)

console.log(obj === raw)   // true

markRaw()

markRaw函数,可以将原始数据标记为非响应式的,即使用 ref 或 reactive 将其包装,仍无法实现数据响应式,其接收一个参数,即原始数据,并返回被标记后的数据
const markRawState = markRaw(obj)
const state = reactive(markRawState);
console.log(state)  

// {"name":"zhangsan","children":{"name":"lisi"}}

watch() && watchEffect() && computed()

watch() && watchEffect() && computed() 函数都是用来监听数据变化的。
  1. watct
    watch( source, cb, [options] )
    参数说明:
        source:可以是表达式或函数,用于指定监听的依赖对象
        cb:监听到数据变化后执行的回调函数
        options:可参数,可以配置的属性有 deep(深度监听)、immediate(立即触发回调函数)
    
    watch(
		() => state.collapsed,
		(newVal, oldVal) => {
			console.log(newVal);
			console.log(oldVal);
		}
	);
	
  1. watchEffect
    1. 不需要手动传入依赖,自动的收集依赖
    2. 获取不到更新之前的值,只能拿到最新值
    3. 每次初始化时会执行一次回调函数来自动获取依赖
    watchEffect(()=>{
        console.log(state.count);
    })	
    1.清除副作用
		watchEffect((onInvalidate)=>{
            let timer = getList(title.value)
            onInvalidate(()=>{
	            clearTimeout(timer)
	            
    		    执行时机:
    				1.在副作用即将重新执行时
    				2.如果在setup()或生命周期钩子函数中使用了 watchEffect, 则在卸载组件时
    				clearTimeout(timer)
	      })
	    })
	2.异步副作用
	    watchEffect(async () => {
	         const data = await fetch()
	    })
	3. 手动停止监听
    	const watcherStop=watchEffect(()=>{})	  	            
        watcherStop()
  1. computed
const count = computed(() => props.count);

const newCount = computed({
    get: () => state.count,
    set: (val) => {
        emit('state:count',val)
    },
});

useStore() & useRoute & useRouter

import { useStore } from 'vuex'
import { useRoute, useRouter } from "vue-router";

export default defineComponent({
	setup() {
	
    	const store = useStore();
	    const route = useRoute();
    	const router = useRouter();
    	console.log("store", store);
    	console.log("route", route);
    	console.log("router", router);
    	
	}
})

结尾

后续会继续总结Vue3的api和项目中遇到的问题。

最后,觉得文章对自己有帮助的小伙伴动手点赞哦!