參考: https://www.cnblogs.com/Highdoudou/p/9993870.html
https://www.cnblogs.com/ljx20180807/p/9987822.html
性能優化
-
觀察者機制的變化:Proxy 替代 object.defineProperty
- Vue 2.x使用
Object.defineProperty
的 getter 和 setter。 但是,Vue 3 將使用 ES2015 Proxy 作為其觀察者機制。 這消除了以前存在的警告,使速度加倍,並節省了一半的內存開銷。
- Vue 2.x使用
-
virtual DOM重構(比2.5快一倍) 【和模版大小有關 --> 和模版內的動態內容數量相關】
-
-
傳統:組件 update時,整個vdom樹需要重新創建,然后遍歷進行diff, update
-
新的更新策略: block tree
-
-
-
-
區分動態節點和靜態節點
-
基於動態節點指令(v-if, v-for, {{ name }}等)更新
-
-
-
編譯時優化
-
slot默認編譯為函數
-
vnode的創建函數保持參數一致化
-
編譯時生成vnode的類型標記
新增composition-api,
https://composition-api.vuejs.org/
可以在vue2.x的項目中通過安裝@vue/composition-api包來使用composition-api.
- reactive:reactive的作用是將對象包裝成響應式對象——通過 Proxy代理后的對象。
- ref:由傳入值返回一個響應式的、可變的且只有value一個屬性的ref對象。
當ref被作為render context被返回,在template中使用該ref對象時,自動獲取內部的值,不需要使用.value屬性。
<template> <div>{{ count }}</div> </template> <script> export default { setup() { return { count: ref(0) } } } </script>
當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)
-
- reactive對象與ref對象的區別
可以通過如何撰寫標准的 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()
- computed:傳入一個getter()函數,返回一個值不可變的響應式ref對象。
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的值也相應的改變
- readonly:將接受到的對象(無論是響應式對象、ref對象或普通對象),轉成通過Proxy代理的只讀對象。
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!
- watchEffect:接受一個函數,當依賴改變時,重新調用該函數。
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之后再去調用。
<template> <div>{{ count }}</div> </template> <script> export default { setup() { const count = ref(0) watchEffect(() => { console.log(count.value) }) return { count } } } </script>
上述代碼,第一輪會同步打印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 dependency
watchEffect( () => { /* side effect */ }, { onTrigger(e) { debugger // 進行交互式調試 } } )
- watch:等價於vue 2.x中的this.$watch.
相比於watchEffect(), watch()可幫我們實現:
- Perform the side effect lazily;
- Be more specific about what state should trigger the watcher to re-run;
- Access both the previous and current value of the watched state.
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調整
<template>
<div>{{count}} {{obj.foo}}</div>
</template>
<script>
import { ref, reactive, watchEffect } from '@vue/composition-api' export default { props: { name: String }, setup (props) { const count = ref(0) const obj = reactive({ foo: 'abc' }) watchEffect(() => { console.log(props.name) }) return { count, obj } } } </script>
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!') }) }
- <template>和之前一樣,同樣 vue-next也支持手寫 render的寫法。 template和 render同時存在的情況,優先 render。
- setup()是新增的主要變動。顧名思義, setup函數會在組件掛載前運行一次,類似組件初始化的作用, setup需要返回一個對象或者函數。返回對象會被賦值給組件實例的 renderContext,在組件的模板作用域可以被訪問到,類似 data的返回值。返回函數會被當做是組件的 render。
基於邏輯關注點組織代碼
在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配合
-
組合式 API 會在 2.x 的選項 (
data
、computed
和methods
) 之前解析,並且不能提前訪問這些選項中定義的 property。 -
setup()
函數返回的 property 將會被暴露給this
。它們在 2.x 的選項中可以訪問到。
vue3.0與react的比較:
-
-
同樣的邏輯組合、復用能力
-
composition api 的 setup() 只會調用一次,相比與react hooks
-
符合js 直覺
-
沒有閉包變量問題
-
不會在每次渲染時重復執行,降低垃圾回收的壓力;
-
不存在內聯回調導致子組件永遠更新的問題
- 不存在忘記記錄依賴的問題,也不需要“useEffect”和“useMemo”並傳入依賴數組以捕獲過時的變量。Vue 的自動依賴跟蹤可以確保偵聽器和計算值總是准確無誤。
-
-