早在六月初,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: 我的公衆號求加個閱讀量哈,二維碼以下: