早在六月初,vue做者尤雨溪在知乎上发布了一篇vue3.0的RFC [Vue Function-based API RFC], 这篇文章指明了在19年底即将发布的vue3.0的初步路线。
复制代码
RFC (Request For Comments
),中文翻译为 "意见征求稿"javascript
咱们能够思考一下,为何 vue团队 要选择function-based API
?vue
众所周知, vue.js 的 api 对开发者十分的友好。在开发中,vue.js 的API强制要求开发者将组件代码基于选项切分开来。java
理想很美好,现实很骨感。缺少经验的新手在项目不断迭代中可能会将逻辑写在同一个文件,使得代码逻辑很是不易阅读及抽离。react
新的API制约不多
,它提倡开发者根据逻辑去抽离成函数,经过返回值将逻辑数据返回回来,也不仅是能够根据逻辑去抽离代码,也能够为了写出更好的漂亮代码而抽离函数。webpack
表格分页mixin,对话框mixin等
)。但在引入了多个 mixin 的状况下,会出现引用赋值混乱/命名重复的困扰,虽然解决了快速开发的问题,但这使得事情更加的糟糕。// mixin-mouse.js
export default {
data () {
return {
x: 0,
y: 0
}
},
methods: {
update(e) {
x = e.pageX;
y = e.pageY;
}
},
mounted() {
window.addEventListener('mousemove', this.update)
},
destroyed() {
window.removeEventListener('mousemove', this.update)
}
}
复制代码
// mouse.js
import { binding, onMounted, onUnmouted } from "vue";
export const useMouse = () => {
const x = binding(0)
const y = binding(0)
const update = e => {
x.value = e.pageX
y.value = e.pageY
}
onMounted(() => {
window.addEventListener('mousemove', update)
})
onUnmounted(() => {
window.removeEventListener('mousemove', update)
})
return { x, y }
}
复制代码
// app.js
import { binding } from "vue";
import { useMouse } from "./mouse";
export default {
setup () {
const { x, y } = useMouse();
return { x, y };
}
}
复制代码
引入的时候,开发者可以更清晰的识别及处理合并进来的值,这样更容易维护。git
import { reactive, toBindings } from "vue";
export default {
// props, ctx不必定会有
setup(props) {
const state = reactive({
name: "lxs"
});
return {
...toBindings(state)
}
}
}
复制代码
尤大大指出,setup 函数里面可使用 this 获取到当前上下文,但不必用。因此liximomo的vue-function-api
版本,setup 函数里面的 this 为 undefined,并且不只仅有 props 参数,还有 context 参数,里面有如下参数:github
attrs: Object
emit: ƒ ()
parent: VueComponent
refs: Object
root: Vue
slots: Object
web
export default {
props: {
visible: {
type: Boolean,
default: false
}
},
setup(props, { attrs, emit, parent, refs, root, slots }) {
return {}
}
}
复制代码
binding()
返回的是一个包装对象(value wrapper)
, 里面只有一个 .value
属性,该属性指向内部被包装的值。算法
import { reactive, binding, isBinding, toBindings } from "vue";
export default {
setup() {
const name = binding("lxs");
console.log(name);
// ValueWrapper {
// value: Object
// _internal: {__ob__: Observer}
// __proto__: AbstractWrapper
// }
const state = reactive({
name: "test"
})
const changeName = () => {
if (isBinding(name)) {
name.value = "new lxs";
}
}
return {
...toBindings(state),
name,
changeName
}
}
}
复制代码
binding
函数附带了两个功能性函数:vue-router
isBinding: 判断是否为包装对象
toBindings: 将原始值转化成包裹对象
复制代码
咱们知道在 JavaScript 中,原始值类型如 string 和 number 是只有值,没有引用的
。若是在一个函数中返回一个字符串变量,接收到这个字符串的代码只会得到一个值,是没法追踪原始变量后续的变化的。
所以,包装对象的意义就在于提供一个让咱们可以在函数之间以引用的方式传递任意类型值的容器。这有点像 React Hooks 中的 useRef
—— 但不一样的是 Vue 的包装对象同时仍是响应式的数据源。有了这样的容器,咱们就能够在封装了逻辑的组合函数中将状态以引用的方式传回给组件。组件负责展现(追踪依赖),组合函数负责管理状态(触发更新)
声明数据和更新数据更加清晰。
setup()
中返回的是一个包装对象,可是在模版渲染的过程或者嵌套在另外一个包装对象的时候,若是判断类型为包装对象,都会被自动展开为内部的值。reactive()
返回一个没有包装的响应式对象,等同于 vue 2.6 版本之后的 Vue.observable()
函数。Vue.observable()
提供了让 data
块里面的值可以在外面定义的功能。import { reactive } from 'vue'
const object = reactive({
count: 0
})
object.count++
复制代码
当一个包装对象被做为另外一个响应式对象的属性引用的时候也会被自动展开:
const count = binding(0)
const obj = reactive({
count
})
console.log(obj.count) // 0
obj.count++
console.log(obj.count) // 1
console.log(count.value) // 1
count.value++
console.log(obj.count) // 2
console.log(count.value) // 2
复制代码
.value
去取它内部的值 —— 在模版中你甚至不须要知道它们的存在。
除了直接包装一个可变的值,咱们也能够包装经过计算产生的值:
import { binding, computed } from "vue";
import dayjs from "dayjs";
export const timeHook = () => {
const time = binding(new Date().getTime());
const formatTime = computed(() => time.value, val => {
return dayjs(val).format("YYYY-MM-DD hh:mm:ss");
});
return {
time,
formatTime
}
}
复制代码
计算值的行为跟计算属性 (computed property
) 同样:只有当依赖变化的时候它才会被从新计算。
computed()
返回的是一个只读的包装对象,它能够和普通的包装对象同样在 setup()
中被返回 ,也同样会在渲染上下文中被自动展开。默认状况下,若是用户试图去修改一个只读包装对象,会触发警告。
双向计算值能够经过传给computed
第二个参数做为 setter
来建立:
import { binding, computed } from "vue";
import dayjs from "dayjs";
export const timeHook = () => {
const time = binding(new Date().getTime());
const formatTime = computed(
// read
() => time.value + 2000,
// write
val => {
return dayjs(val).format("YYYY-MM-DD hh:mm:ss");
});
return {
time,
formatTime
}
}
复制代码
watch()
函数与旧API的 $watch
同样提供了观察状态变化的能力。它的做用相似React Hooks 的 useEffect
,但实现原理和调用时机其实彻底不同。
一个返回任意值的函数
一个包装对象
一个包含上述两种数据源的数组
复制代码
watch()
函数的回调,会在建立时就执行一次,至关于2.x的 watcher 的immediate: true
。watch()
的回调在触发时,DOM
总会在一个更新过的状态。export default {
props: {
tableHeight: {
type: [String, Number],
default: 200
}
},
setup(props) {
watch(
() => props.tableHeight,
val => {
console.log("DOM render flush")
},
{
flush: 'post', // default, fire after renderer flush
flush: 'pre', // fire right before renderer flush
flush: 'sync' // fire synchronously
}
)
}
}
复制代码
watch(
[valueA, () => valueB.value],
([a, b], [prevA, prevB]) => {
console.log(`a is: ${a}`)
console.log(`b is: ${b}`)
}
)
复制代码
const stop = watch(...)
// stop watching
stop()
复制代码
watch()
是在一个组件的setup()
或是生命周期函数中被调用的,那么该 watcher
会在当前组件被销毁时也一同被自动中止,不然将要本身自动中止。有时候当观察的数据源变化后,咱们可能须要对以前所执行的反作用进行清理。举例来讲,一个异步操做在完成以前数据就产生了变化,咱们可能要撤销还在等待的前一个操做
。为了处理这种状况,watcher 的回调会接收到的第三个参数是一个用来注册清理操做的函数。调用这个函数能够注册一个清理函数。清理函数会在下属状况下被调用,咱们常常须要在 watcher 的回调中用async function
来执行异步操做:
在回调被下一次调用前
在 watcher 被中止前
复制代码
watch(idValue, (id, oldId, onCleanup) => {
const token = performAsyncOperation(id)
onCleanup(() => {
// id 发生了变化,或是 watcher 即将被中止.
// 取消还未完成的异步操做。
token.cancel()
})
})
复制代码
const data = value(null)
watch(getId, async (id) => {
data.value = await fetchData(id)
}
复制代码
onXXX
函数(只能在setup()
中使用)-> destroyed 调整为 unmounted
:import { onMounted, onUpdated, onUnmounted } from 'vue'
const MyComponent = {
setup() {
onMounted(() => {
console.log('mounted!')
})
onUpdated(() => {
console.log('updated!')
})
// destroyed 调整为 unmounted
onUnmounted(() => {
console.log('unmounted!')
})
}
}
复制代码
vue-function-api
是在 install
里面 设置Vue.config.optionMergeStrategies.setup
,让 setup
函数在beforeCreate
中注入。// setup.ts
Vue.mixin({
beforeCreate: functionApiInit,
});
复制代码
// hook.js
import { provide, binding } from 'vue'
export const randomKeyHook = () => {
const randomKey = binding(
Math.random()
.toString(36)
.substr(2)
)
provide({
randomKey
})
return {
randomKey
}
}
复制代码
import { inject } from 'vue';
export default {
setup() {
const randomKey = inject('randomKey')
return {
randomKey
}
}
}
复制代码
3.0 的一个主要设计目标是加强对 TypeScript
的支持。本来vue团队指望经过Class API
来达成这个目标,可是通过讨论和原型开发,vue团队认为 Class 并非解决这个问题的正确路线,基于 Class 的 API 依然存在类型问题。
基于函数的 API 自然对类型推导很友好,由于TS对函数的参数、返回值和泛型的支持已经很是完备
。更值得一提的是基于函数的 API 在使用 TS 或是原生 JS 时写出来的代码几乎是彻底同样的。
new Vue()
来建立,而是经过新的 createApp
来建立,)import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
复制代码
.sync
, 添加 v-model
指令参数data(由 setup() + binding) + reactive) 取代)
computed(由 computed 取代)
methods( 由 setup() 中声明的函数取代)
watch (由 watch() 取代)
provide/inject(由 provide() 和 inject() 取代)
mixins (由组合函数取代)
extends (由组合函数取代)
复制代码
// main.ts
import { provideRouter, useRouter } from 'vue-router'
import router from './router'
new Vue({
setup() {
provideRouter(router)
}
})
复制代码
// ... in a child component
export default {
setup() {
const { route /*, router */ } = useRouter()
const isActive = computed(() => route.name === 'myAwesomeRoute')
return {
isActive
}
}
}
复制代码
import router from './router'
new Vue({
setup() {
router.use()
}
})
复制代码
// useState, useGetter, useMutation, useAction, useModule
import { useModule } from 'vuex'
import { value, computed } from 'vue';
export const itemsModule = () => {
const items = value([])
const size = computed(() => items.value.length);
const addItem = (content) => {
items.value.push(content)
}
return {
items,
size,
addItem
}
}
useModule(itemsModule)
复制代码
nuxt.js
,在vue-cli4
中提供统一的 store
目录,若是store
目录存在,程序将本身作如下事情引用 vuex 模块
将 vuex 模块 加到 vendors 构建配置中去
设置 Vue 根实例的 store 配置项
复制代码
尤大大文章刚出的时候,不少技术帖子对于 vue3.0
的语法更新表示不满,他们表示,若是是大规模模仿react
,倒不如去学习 react
,这样也是学习新的语法。
这样大错特错,vue
和react
的实现有很大的差异, vue
实际上模仿的是hooks
,而不是 react
。
Template
和 Setup
分开,react
则是写在了一块儿。vue
的 setup
函数只在初始化以前执行一次,而 React
在更新数据的时候,都会执行一次 render
。
vue
将 hooks
和 mutable
深度结合,在数据更新的时候,经过包装对象的 obj.value
,在obj的数据变动的时候,引用保持不变,只有值改变了,vue 经过 新的API Proxy
监听数据的变化,能够作到 setup 函数不从新执行。而Template 的重渲染则彻底继承 Vue 2.0的依赖收集机制,它无论值来自哪里,只要用到的值变了,就能够从新渲染了。
React Hooks 存在的问题:全部Hooks都在渲染闭包中执行,每次重渲染都有必定性能压力,并且频繁的渲染会带来许多闭包,虽然能够依赖 GC 机制回收,但会给 GC 带来不小的压力。
v8的垃圾回收算法
从宏观上来看,V8的堆分为3部分,年轻分代/年老分代/大对象空间。对于各类类型,v8有对应的处理方式:
1. 年轻分代(短暂)
分为两堆,一半使用,一半用于垃圾清理。在堆中分配而内容不够时,会将超出生命期的垃圾对象清除出去,释放掉空间。
2. 年老分代
主要类型:
(1) 从年轻分代中移动过来的对象
(2) JIT (即时编辑器) 以后产生的代码
(3) 全局对象
标记清除和标记整理算法将可清除的垃圾对象和有效的对象区分开来,但超过度配给年老分代但空间时,V8会清除垃圾代码,造成了碎片的内存块,而后压缩内存块。固然,这个过程是走走停停的。(上述问题)
3. 大对象空间
整块分配,一次性回收。
复制代码
vue3.0
对 vue 的主要3个特色:响应式、模板、对象式的组件声明方式,进行了全面的更改,底层的实现和上层的api都有了明显的变化,基于 Proxy
从新实现了响应式,基于 treeshaking
内置了更多功能,提供了类式的组件声明方式。并且源码所有用 Typescript
重写。以及进行了一系列的性能优化。
取其精华,去其糟糠, vue团队但愿剔除掉代码灵活性差的帽子,大胆的改变了原有的开发模式,更加亲和于 vue生态
及javascript
生态。vue一直在不断的进步,让开发者减小框架的约束,放飞开发者的思想。
感谢 vue 团队一向的高出产率~
2019-07-29在github上的截的路线图
1. vue对兼容模式的开发目前完成了80%;
2. 还没有进行破坏性更新的讨论;
3. vue3.0会伴着vue-cli4的出现 -> webpack5;
4. 各生态目前正在同步更新;
5. Q3季度将要开始vue3.0的测试;
等
复制代码
期待吧~
参考文章:
尤雨溪 - Vue Function-based API RFC (https://zhuanlan.zhihu.com/p/68477600)
黄子毅 - 精读《Vue3.0 Function API》(https://zhuanlan.zhihu.com/p/71667382)
vuejs - rfcs (https://github.com/vuejs/rfcs/issues)
复制代码
ps: 我的公众号求加个阅读量哈,二维码以下: