参考: https://www.cnblogs.com/Highdoudou/p/9993870.html
https://www.cnblogs.com/ljx20180807/p/9987822.html
性能优化
观察者机制的变化:Proxy 替代 object.defineProperty
Object.defineProperty
的 getter 和 setter。 但是,Vue 3 将使用 ES2015 Proxy 作为其观察者机制。 这消除了以前存在的警告,使速度加倍,并节省了一半的内存开销。virtual DOM重构(比2.5快一倍) 【和模版大小有关 --> 和模版内的动态内容数量相关】
传统:组件 update时,整个vdom树需要重新创建,然后遍历进行diff, update
新的更新策略: block tree
区分动态节点和静态节点
基于动态节点指令(v-if, v-for, { { name }}等)更新
编译时优化
slot默认编译为函数
vnode的创建函数保持参数一致化
编译时生成vnode的类型标记
https://composition-api.vuejs.org/
可以在vue2.x的项目中通过安装@vue/composition-api包来使用composition-api.
当ref被作为render context被返回,在template中使用该ref对象时,自动获取内部的值,不需要使用.value属性。
{ { count }}
当ref对象被作为属性,传入响应式对象reactive时,会自动获取其内部的值(表现得像普通的属性值,而非对象)。
const count = ref(0) const state = reactive({ count }) console.log(state.count) // 0 state.count = 1 console.log(count.value) // 1
reactive属性绑定新的ref对象后,原来ref的value不变(断开了)。
const otherCount = ref(2) state.count = otherCount console.log(state.count) // 2 console.log(count.value) // 1
其他情况获取ref时需要带.value
const arr = reactive([ref(0)]) // need .value here console.log(arr[0].value) const map = reactive(new Map([['foo', ref(0)]])) // need .value here console.log(map.get('foo').value)
可以通过如何撰写标准的 JavaScript 逻辑来比较:
// 风格 1: 将变量分离 let x = 0 let y = 0 function updatePosition(e) { x = e.pageX y = e.pageY } // 风格 2: 单个对象 const pos = { x: 0, y: 0, } function updatePosition(e) { pos.x = e.pageX pos.y = e.pageY }
可以将风格 (1) 转换为使用 ref写法 (为了让基础类型值具有响应性) 。
将风格 (2) 转换为使用 reactive
对象的写法。
如果只使用 reactive
的问题是,使用组合函数时必须始终保持对这个所返回对象的引用以保持响应性。这个对象不能被解构或展开:
// 组合函数: function useMousePosition() { const pos = reactive({ x: 0, y: 0, }) // ... return pos } // 消费组件 export default { setup() { // 这里会丢失响应性! const { x, y } = useMousePosition() return { x, y, } // 这里会丢失响应性! return { ...useMousePosition(), } // 这是保持响应性的唯一办法! // 你必须返回 `pos` 本身,并按 `pos.x` 和 `pos.y` 的方式在模板中引用 x 和 y。 return { pos: useMousePosition(), } }, }
toRefs
API 用来提供解决此约束的办法——它将响应式对象的每个 property 都转成了相应的 ref。
function useMousePosition() { const pos = reactive({ x: 0, y: 0, }) // ... return toRefs(pos) } // x & y 现在是 ref 形式,可以i解构了! const { x, y } = useMousePosition()
const count = ref(1) const plusOne = computed(() => count.value + 1) console.log(plusOne.value) // 2 count值改变,plusOne的值相应的改变 plusOne.value++ // error
当传入的是一个具有get和set函数的对象时,返回的是一个可写的ref对象。
const count = ref(1) const plusOne = computed({ get: () => count.value + 1, set: val => { count.value = val - 1 } }) plusOne.value = 1 console.log(count.value) // 0 count值改变,plusOne的值随之改变;改变plusOne的值, count的值也相应的改变
const original = reactive({ count: 0 }) const copy = readonly(original) watchEffect(() => { // works for reactivity tracking console.log(copy.count) }) // mutating original will trigger watchers relying on the copy original.count++ // mutating the copy will fail and result in a warning copy.count++ // warning!
const count = ref(0) watchEffect(() => console.log(count.value)) setTimeout(() => { count.value++ }, 100)
当watchEffect()在setup()或生命周期钩子中被调用时,监听就始终存在该组件的生命周期中,直到组件unmount.
另一种卸载监听的情况是,watchEffect()返回一个stop handler,调用该handler即可停止监听。
const stop = watchEffect(() => { /* ... */ }) // later stop()
当向后台获取数据时,watchEffect()接受async回调函数。
const data = ref(null) watchEffect(async () => { data.value = await fetchData(props.id) })
组件的update函数也有watch effect。用户定义的watchEffect会在组件update之后再去调用。
{ { count }}
上述代码,第一轮会同步打印count.value(在onmount生命周期前); 当count发生改变时,先执行组件更新,然后再去log.
如果想将watchEffect中的回调函数第一次执行,放在onmount后,
onMounted(() => { watchEffect(() => { // access the DOM or template refs }) })
如果想让watchEffect()调用发生在组件update前,或re-run同步,需要传递一个带有flush属性(默认值为post)的option对象。
watchEffect(()=> { //... }, { flush: 'sync' // 在更新前触发 flush: "pre" })
此外,option对象还有ontrack和ontrigger两个函数属性,用于调试watcher的行为。
onTrack
will be called when a reactive property or ref is tracked as a dependencyonTrigger
will be called when the watcher callback is triggered by the mutation of a dependencywatchEffect( () => { /* side effect */ }, { onTrigger(e) { debugger // 进行交互式调试 } } )
相比于watchEffect(), watch()可帮我们实现:
watch()的数据源可以是一个返回值的getter函数,或者是一个ref对象。
// watching a getter const state = reactive({ count: 0 }) watch( () => state.count, (count, prevCount) => { /* ... */ } ) // directly watching a ref const count = ref(0) watch(count, (count, prevCount) => { /* ... */ })
对于多数据源的监听,可借助数组。
watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => { /* ... */ })
生命周期api调整
{ {count}} { {obj.foo}}
setup()接受的第一个参数是props。props是响应式的。注意:传参时不能对其解构。上面的代码将props传入setup()后,并通过watchEffect()进行监听。
setup()还可接受第二个参数context(相当于vue2.x中的this, setup()不允许使用this),context作为参数时,可以进行解构。常用的有:
context.attrs, context.slots, context.emit
setup(props, {attrs}) { function onClick() { console.log(attrs.foo) // 可以保证是最新的值 } }
其他生命周期函数可以在setup()中被同步注册。当同步执行生命周期hooks时,组件实例的renderContext,watcher和computed properties也同步建立。当组件卸载时,内部的生命周期hooks也会同步去掉。
setup() { onMounted(() => { console.log('mounted!') }) onUpdated(() => { console.log('updated!') }) onUnmounted(() => { console.log('unmounted!') }) }
在vue 2.x中,使用的是option api,要完成一个函数所用到的data或computed计算属性距离这个函数的定义可能有很多行(代码是分散的),这种碎片化使得代码维护变得困难。
这一问题在composition api中得到了解决。每个逻辑关注点的代码现在都被组合进了一个组合函数(命名以use开头)。这大大减少了在处理大型组件时不断“跳转”的需要。同时组合函数也可以在编辑器中折叠起来,使组件更容易浏览。
setup() { // ... }, } function useCurrentFolderData(networkState) { // ... } function useFolderNavigation({ networkState, currentFolderData }) { // ... } function useFavoriteFolder(currentFolderData) { // ... } function useHiddenFolders() { // ... } function useCreateFolder(openFolder) { // ... }
setup()
函数现在只是简单地作为调用所有组合函数的入口。最后的 return 语句作为单一出口确认暴露给模板的内容。
export default { setup() { // 网络状态 const { networkState } = useNetworkState() // 文件夹状态 const { folders, currentFolderData } = useCurrentFolderData(networkState) const folderNavigation = useFolderNavigation({ networkState, currentFolderData, }) const { favoriteFolders, toggleFavorite } = useFavoriteFolders( currentFolderData ) const { showHiddenFolders } = useHiddenFolders() const createFolder = useCreateFolder(folderNavigation.openFolder) // 当前工作目录 resetCwdOnLeave() const { updateOnCwdChanged } = useCwdUtils() // 实用工具 const { slicePath } = usePathUtils() return { networkState, folders, currentFolderData, folderNavigation, favoriteFolders, toggleFavorite, showHiddenFolders, createFolder, updateOnCwdChanged, slicePath, } }, }
一个组合函数仅依赖它的参数和 Vue 全局导出的 API,而不是依赖 this
上下文。你可以将组件内的任何一段逻辑导出为函数以复用。
import { ref, onMounted, onUnmounted } from 'vue' export function useMousePosition() { const x = ref(0) const y = ref(0) // 提取为函数 function update(e) { x.value = e.pageX y.value = e.pageY } onMounted(() => { window.addEventListener('mousemove', update) }) onUnmounted(() => { window.removeEventListener('mousemove', update) }) return { x, y } }
在一个组件中调用上述组合函数
import { useMousePosition } from './mouse' export default { setup() { const { x, y } = useMousePosition() // 其他逻辑... return { x, y } }, }
组合式 API 会在 2.x 的选项 (data
、computed
和 methods
) 之前解析,并且不能提前访问这些选项中定义的 property。
setup()
函数返回的 property 将会被暴露给 this
。它们在 2.x 的选项中可以访问到。
同样的逻辑组合、复用能力
composition api 的 setup() 只会调用一次,相比与react hooks
符合js 直觉
没有闭包变量问题
不会在每次渲染时重复执行,降低垃圾回收的压力;
不存在内联回调导致子组件永远更新的问题