這是我第21篇簡(jiǎn)書。
咳咳撬槽,進(jìn)入正題此改。
Vue3新增了
-
Performance
:性能更強(qiáng),比Vue 2.0快了接近2倍侄柔。 -
Tree shaking support
:可以將無用模塊“剪輯”共啃,僅打包需要的,按需編譯代碼暂题。 -
Composition API
:組合式API移剪,類似hooks,composition API 可以實(shí)現(xiàn)更靈活且無副作用的復(fù)用代碼敢靡,mixin
將不再作為推薦使用挂滓。 -
Fragment, Teleport, Suspense
:“碎片”,Teleport即Protal傳送門,“懸念” -
Better TypeScript support
:更優(yōu)秀的Ts支持 -
Custom Renderer API
:暴露了自定義渲染API
這里不得不提到vue3重寫了響應(yīng)式原理:
在 Vue 2中赶站, Vue 通過 Object.defineProperty
轉(zhuǎn)化對(duì)象屬性getters/setters
的方法來實(shí)現(xiàn)響應(yīng)式幔虏,對(duì)于數(shù)組來說額外對(duì)常用的數(shù)組方法進(jìn)行來攔截才能截獲到數(shù)組元素的變動(dòng),但這確實(shí)也造成了一些問題贝椿,比如無法感知直接通過索引來更新數(shù)組的場(chǎng)景想括。
reactive:
在 Vue 3 中,用 ES6 的 Proxy
重寫了響應(yīng)式的實(shí)現(xiàn)烙博,并將其功能 API 直接暴露給開發(fā)者瑟蜈,換言之,開發(fā)者甚至可以將 Vue 的響應(yīng)式作為一個(gè)獨(dú)立的庫來使用渣窜。
一铺根、Composition API
官方文檔:https://composition-api.vuejs.org/zh/
隨著功能的增長(zhǎng),復(fù)雜組件的代碼變得越來越難以維護(hù)乔宿。 尤其發(fā)生你去新接手別人的代碼時(shí)位迂。 根本原因是 Vue 2 通過option API
組織代碼,但是在大部分情況下详瑞,通過邏輯考慮來組織代碼更有意義掂林。
在Vue2下相關(guān)業(yè)務(wù)的代碼需要遵循option的配置寫到特定的區(qū)域,導(dǎo)致后續(xù)維護(hù)非常的復(fù)雜坝橡,同時(shí)代碼可復(fù)用性不高泻帮,而Vue3的Composition API
就是為了解決這個(gè)問題而生的。而且可與現(xiàn)有的 Options API一起使用计寇。
1锣杂、組合式的6個(gè)主要API:
-
reactive
(Composition API的核心)
接收一個(gè)普通對(duì)象然后返回該普通對(duì)象的響應(yīng)式代理。等同于 Vue2 的Vue.observable()
饲常。
響應(yīng)式轉(zhuǎn)換是“深層的”:會(huì)影響對(duì)象內(nèi)部所有嵌套的屬性蹲堂。基于 ES6的Proxy
實(shí)現(xiàn)贝淤,返回的代理對(duì)象不等于原始對(duì)象柒竞。建議僅使用代理對(duì)象而避免依賴原始對(duì)象。 -
ref
接受一個(gè)參數(shù)值并返回一個(gè)響應(yīng)式且可改變的 ref 對(duì)象播聪。ref 對(duì)象擁有一個(gè)指向內(nèi)部值的單一屬性.value
朽基。如果傳入 ref 的是一個(gè)對(duì)象,將調(diào)用 reactive 方法進(jìn)行深層響應(yīng)轉(zhuǎn)換 -
computed
傳入一個(gè) getter 函數(shù)离陶,返回一個(gè)默認(rèn)不可手動(dòng)修改的 ref 對(duì)象稼虎。
或者傳入一個(gè)擁有 get 和 set 函數(shù)的對(duì)象,創(chuàng)建一個(gè)可手動(dòng)修改的計(jì)算狀態(tài) -
readonly
傳入一個(gè)對(duì)象(響應(yīng)式或普通)或 ref招刨,返回一個(gè)原始對(duì)象的只讀代理霎俩。一個(gè)只讀的代理是“深層的”,對(duì)象內(nèi)部任何嵌套的屬性也都是只讀的。
const original = reactive({ count: 0 })
const copy = readonly(original)
watchEffect(() => {
// 依賴追蹤
console.log(copy.count)
})
// original 上的修改會(huì)觸發(fā) copy 上的偵聽
original.count++
// 無法修改 copy 并會(huì)被警告
copy.count++ // warning!
-
wathEffect
立即執(zhí)行傳入的一個(gè)函數(shù)打却,并響應(yīng)式追蹤其依賴杉适,并在其依賴變更時(shí)重新運(yùn)行該函數(shù)。
當(dāng) watchEffect 在組件的setup()
函數(shù)或生命周期鉤子被調(diào)用時(shí)柳击, 偵聽器會(huì)被鏈接到該組件的生命周期猿推,并在組件卸載時(shí)自動(dòng)停止。
const count = ref(0)
watchEffect(() => console.log(count.value))
// -> 打印出 0
setTimeout(() => {
count.value++
// -> 打印出 1
}, 100)
-
watch
和vue2的watch一樣
2捌肴、生命周期鉤子函數(shù)
(1)setup函數(shù)
setup
函數(shù)是一個(gè)新的組件選項(xiàng)蹬叭。作為在組件內(nèi)使用 Composition API 的入口點(diǎn)。
- 調(diào)用時(shí)機(jī):
創(chuàng)建組件實(shí)例状知,然后初始化 props 秽五,緊接著就調(diào)用setup 函數(shù)。從生命周期鉤子的視角來看饥悴,它會(huì)在 beforeCreate 鉤子之前被調(diào)用筝蚕。 - 參數(shù)
const MyComponent = {
setup(props, context) {
context.attrs
context.slots
context.emit
},
}
props 作為其第一個(gè)參數(shù) (注:props 對(duì)象是響應(yīng)式的,watchEffect 或 watch 會(huì)觀察和響應(yīng) props 的更新铺坞。不要解構(gòu) props 對(duì)象,那樣會(huì)使其失去響應(yīng)性)洲胖。
第二個(gè)參數(shù)提供了一個(gè)上下文對(duì)象context济榨,attrs
和 slots
都是內(nèi)部組件實(shí)例上對(duì)應(yīng)項(xiàng)的代理,可以確保在更新后仍然是最新值绿映,所以可以解構(gòu)擒滑,無需擔(dān)心后面訪問到過期的值。
- 特別注意this的用法
this 在 setup() 中不可用叉弦!
由于 setup() 在解析 vue2 選項(xiàng)前被調(diào)用丐一,setup() 中的 this 將與 vue2選項(xiàng)中的 this 完全不同。同時(shí)在 setup() 和 vue2 選項(xiàng)中使用 this 時(shí)將造成混亂淹冰。
setup() {
function onClick() {
this // 這里 `this` 與你期望的不一樣库车!
}
}
二、簡(jiǎn)易代碼展示Vue3核心:
<script src="../dist/vue.global.js"></script>
<script>
Vue2的options api 很難做tree-shaking
// export default {
// data() {
// return {}
// }樱拴,
// methods: {
// },
// computed: {
// }
// }
// createApp代替new Vue()
const {createApp, reactive, watchEffect, computed } = Vue
// 按需引入柠衍,tree-shaking生效
// 就算你引入了computed ,如果沒有用到它晶乔,打包時(shí)就會(huì)把這段代碼刪掉珍坊。
const App = {
// template => render function(返回vdom)
// compile-dom和compile-core做的
template: `
<button @click="onclick">
{{state.count}} -- {{state.double}}
</button>
`,
setup() {
// 響應(yīng)式,用Proxy取代object.defineProperty
const state = reactive({
count: 0,
double: computed(()=> state.count*2)
})
watchEffect(()=> {
console.log('數(shù)據(jù)變了哦:', state.count)
})
function onclick() {
state.count += 1
}
return {
state,
onclick
}
}
}
createApp(App).mount('#app')
</script>
三正罢、reactive源碼學(xué)習(xí)與響應(yīng)式實(shí)現(xiàn)
劃重點(diǎn)U舐!!
// 作為緩存
// WeakMap:
// (1)Map對(duì)象的鍵可以是任何類型履怯,但WeakMap對(duì)象中的鍵只能是對(duì)象引用
// (2)WeakMap不能包含無引用的對(duì)象回还,否則會(huì)被自動(dòng)清除出集合(垃圾回收機(jī)制)。
// (3)WeakSet對(duì)象是不可枚舉的虑乖,無法獲取大小懦趋。
// 原始對(duì)象=> 響應(yīng)式對(duì)象
let toProxy = new WeakMap()
// 響應(yīng)式對(duì)象=> 原始對(duì)象
let toRaw = new WeakMap()
let effectStack = [] //存儲(chǔ)effect的地方
let targetMap = new WeakMap() // 特殊的對(duì)象 key是object
// obj.name
// {
// target: deps :{ key:[ dep1,dep2] }
// }
// 以上 存儲(chǔ)依賴關(guān)系
// 目的:收集依賴
function track(target, key) {
// 最后一個(gè) 就是最新的
const effect = effectStack[effectStack.length - 1]
// 最新的effect
if (effect) {
let depMap = targetMap.get(target)
if (depMap === undefined) {
depMap = new Map()
targetMap.set(target, depMap)
}
let dep = depMap.get(key) // obj.count target是obj,key是count
if (dep == undefined) {
dep = new Set()
depMap.set(key, dep)
}
// 雙向存儲(chǔ)無處不在疹味,優(yōu)化的原則
if (!dep.has(effect)) {
dep.add(effect)
effect.deps.push(dep)
}
}
}
// 目的:觸發(fā)更新
function trigger(target, key, info) {
// 尋找依賴effect
const depMap = targetMap.get(target)
if (depMap === undefined) {
// 沒有依賴
return
}
const effects = new Set()
const computedRunners = new Set()
if (key) {
let deps = depMap.get(key)
// deps里面全部是effect
deps.forEach((effect) => {
// effect()
if (effect.computed) {
computedRunners.add(effect)
} else {
effects.add(effect)
}
})
}
effects.forEach((effect) => effect())
computedRunners.forEach((computed) => computed())
}
function effect(fn, options = {}) {
// 其實(shí)就是往effectStackpush了一個(gè)effect函數(shù)仅叫,執(zhí)行fn
// @todo 處理options
let e = createReactiveEffect(fn, options)
if (!options.lazy) {
e()
}
return e
}
function createReactiveEffect(fn, options) {
// 構(gòu)造effect
const effect = function effect(...args) {
return run(effect, fn, args)
}
effect.deps = []
effect.computed = options.computed
effect.lazy = options.lazy
return effect
}
function run(effect, fn, args) {
if (effectStack.indexOf(effect) === -1) {
try {
effectStack.push(effect)
return fn(...args) // 執(zhí)行 執(zhí)行的時(shí)候,是要獲取的
} finally {
effectStack.pop() // effect用完就要推出去
}
}
}
function computed(fn) {
// computed就是一個(gè)特殊的effect
const runner = effect(fn, {
computed: true,
lazy: true
})
return {
effect: runner,
get value() {
return runner()
}
}
}
// 舉例:
// let obj = {name:'kkb'} 背后有一個(gè)proxy監(jiān)聽 響應(yīng)式
// obj.name 觸發(fā)get函數(shù)
// 響應(yīng)式代理(重點(diǎn))
const baseHandler = {
get(target, key) {
// target就是obj糙捺,key就是name
// 收集依賴 track
// @todo
// 大部分情況可以直接return target[key]
const res = Reflect.get(target, key) // es6新api Reflect诫咱,和proxy搭配使用
// 查找并返回target對(duì)象的property屬性
track(target, key)
// 遞歸,如果有嵌套對(duì)象接著reactive
return typeof res == 'object' ? reactive(res) : res
},
set(target, key, val) {
const info = {
oldValue: target[key],
newValue: val
}
// obj.name = xx 這里 我們是需要通知更新的
const res = Reflect.set(target, key, val)
// 觸發(fā)更新
// @todo
trigger(target, key, info)
return res
}
}
// 響應(yīng)式
function reactive(target) {
// 查詢緩存
let observed = toProxy.get(target)
if (observed) {
return observed
}
if (toRaw.get(target)) {
return target
}
// 響應(yīng)式核心:榈啤?茬浴!G┕场L秃簟!
observed = new Proxy(target, baseHandler)
// 監(jiān)聽完后铅檩,設(shè)置緩存
toProxy.set(target, observed)
toRaw.set(observed, target)
// 這兩步實(shí)現(xiàn)了雙向搜索地圖
return observed
}
reactive流程圖:
四憎夷、compiler編譯原理與Vdom
嫌麻煩可在線對(duì)比編譯結(jié)果:
Vue2.6:https://template-explorer.vuejs.org/
Vue3:https://vue-next-template-explorer.netlify.app/
template => Vdom過程:
template 解析過程
- 解析成抽象語法樹 AST
- 根據(jù)AST,用transform模板轉(zhuǎn)化
@click.prevent.capture- codeGen生成代碼字符串string
- 使用new Function() (es6新建函數(shù)) 把string 轉(zhuǎn)換成可執(zhí)行的函數(shù)
- 這個(gè)函數(shù)執(zhí)行后返回的才是Vdom
這5部是Vdom的邏輯昧旨,無論vue還是react都是這個(gè)邏輯
流程圖
compiler編譯涉及非常多的正則拾给,這里不做詳細(xì)展示。兔沃。根灯。略過~
vue為啥需要vdom逻谦?
compile模塊 vue處理成vdom
js描述dom拼卵,這個(gè)就是vdom
有了compiler 跨端才成了可能:
json:
{
type:'div',
props:{id:app},
children:[ name,
{type:div } ]
}
有了compiler 跨端才成了可能史汗。
<div><input></div> 這些標(biāo)簽,只有瀏覽器耗時(shí)
這個(gè)對(duì)象缰雇,或者json入偷,跨平臺(tái)的 使用不同平臺(tái)的render
結(jié)構(gòu)化的對(duì)象 很好解析 別的平臺(tái), 只需要記錄好映射關(guān)系就可以械哟。
(Vdom)虛擬dom優(yōu)點(diǎn):
虛擬dom輕量快速疏之,最小dom操作,提升性能和用戶體驗(yàn)
跨平臺(tái):將虛擬dom和嗎好想轉(zhuǎn)換為不同運(yùn)行時(shí)特殊操作實(shí)現(xiàn)跨平臺(tái)
兼容性:還可以加入兼容性代碼增強(qiáng)操作的兼容性
- 緩存的意義:innerHTML 內(nèi)置vdom
- 樹形結(jié)構(gòu)
- 編譯時(shí)優(yōu)化暇咆,足夠多的標(biāo)記
return function render(_ctx, _cache) {
with (_ctx) {
const {
toDisplayString: _toDisplayString,
createVNode: _createVNode,
openBlock: _openBlock,
createBlock: _createBlock
} = _Vue
return (
_openBlock(),
_createBlock('div', null, [
_createVNode(
'p',
{
id: xx
},
_toDisplayString(name),
9 /* TEXT, PROPS */,
['id']
),
_createVNode('h2', null, '大家聽我扯淡'), // 靜態(tài) 永遠(yuǎn)不會(huì)變 不用做diff 不用考慮更新
_createVNode('h2', null, '大家聽我扯淡123')
])
)
}
}
<script>
// 創(chuàng)建虛擬DOM
function createElement(type, props, children) {
return {
type,
props,
children
}
}
function render(dom) {
let el = document.createElement(dom.type)
for (let key in dom.props) {
el.setAttribute(key, dom.props[key])
}
dom.children.forEach(child => {
child = (typeof child == 'object') ? render(child) : document.createTextNode(child)
el.appendChild(child)
})
return el
}
// let vdom = <ul> </ul>
let vdom = createElement('ul', {
class: 'list'
}, [
createElement('li', {
class: 'item'
}, ['item1']),
createElement('li', {
class: 'item'
}, ['item2']),
createElement('li', {
class: 'item'
}, ['item3'])
])
var el = render(vdom)
document.body.appendChild(el)
</script>
五锋爪、runtime
1丙曙、runtime-core
與平臺(tái)無關(guān)的運(yùn)行時(shí),專門用于自定義的render其骄。其實(shí)現(xiàn)的功能有虛擬 DOM 渲染器亏镰、Vue 組件和 Vue 的各種API,我們可以利用這個(gè) runtime 實(shí)現(xiàn)針對(duì)某個(gè)具體平臺(tái)的高階 runtime拯爽,比如自定義渲染器索抓。
2、 runtime-dom
針對(duì)瀏覽器的 runtime毯炮。其功能包括處理原生 DOM API逼肯、DOM 事件和 DOM 屬性等。主要功能是適配了瀏覽器環(huán)境下節(jié)點(diǎn)和節(jié)點(diǎn)屬性的增刪改查桃煎。它暴露了兩個(gè)重要 API:render
和 createApp
篮幢,并聲明了一個(gè) ComponentPublicInstance
接口。
3为迈、功能概述
- 速度顯著提升
- 同時(shí)支持 Composition API 和 Options API三椿,以及 typings
- 基于 Proxy 實(shí)現(xiàn)的數(shù)據(jù)變更檢測(cè)
- 支持 Fragments
碎片化,不再限于模板中的單個(gè)根節(jié)點(diǎn)
render 函數(shù)也可以返回?cái)?shù)組了葫辐,類似實(shí)現(xiàn)了 React.Fragments 的功能 搜锰;Just works
- 支持 Portals
- 支持 Suspense w/ async setup()
- 服務(wù)器端渲染
- <keep-alive>
接下來看尤大大怎么更,期待正式版耿战,持續(xù)關(guān)注~~