01-Vue 3.0開篇-理解
一胧辽、為什么現(xiàn)在才講Vue3.0?
- 因為昨天才發(fā)布正式版本
- 正式版之前API不穩(wěn)定(白學)
- 正式版之前企業(yè)開發(fā)用不上(不穩(wěn)定)
二促煮、為什么現(xiàn)在要講Vue3.0?
- 正式版已經(jīng)發(fā)布,已經(jīng)基本穩(wěn)定
- 預計2021年企業(yè)開發(fā)用得上
- 學習是一個漸進的過程
三颜矿、如何學習Vue3.0?
- 不用全力以赴
- 因為上線項目暫時還不會用
- 因為相關生態(tài)還有待完善
- 先學習vue2.x
- Vue3.0并不是推到重來,很多2.x內(nèi)容依然被保留
- 先學習TypeScript
- Vue3.0采用TS重寫,想知其然知其所以然必須學習TS
02-Vue3.0-diff算法-理解&&03-Vue3.0-靜態(tài)提升和監(jiān)聽緩存-理解
一召衔、Vue3.0六大亮點
- Performance: 性能比Vue 2.x快1.2~2倍
- Tree shaking support: 按需編譯,體積比vue2.x更小
- Composition API: 組合API(類似React Hooks)
- Better TypeScript support: 更好的Ts支持
- Custom Renderer API: 暴露了自定義渲染API
- Fragment, Teleport(Protal), Suspense: 更先進的組件
二祭陷、Vue3.0是如何變快的
1. diff方法優(yōu)化
- Vue2中的虛擬dom是進行全量比對
- Vue3新增了靜態(tài)標記(PatchFlag)
在與上次虛擬節(jié)點進行比對時候苍凛,只對比帶有patch flag的節(jié)點
并且可以通過flag的信息 得知當前節(jié)點要比對的具體內(nèi)容
(1) Vue2 diff算法
(2) Vue3 diff算法
(3) Vue編譯demo
<div>
<p>我是段落</p>
<p>{{msg}}</p>
</div>
import {
createVNode as _createVNode,
toDisplayString as _toDisplayString,
openBlock as _openBlock,
createBlock as _createBlock
} from "vue"
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock("div", null, [
_createVNode("p", null, "我是段落"),
_createVNode("p", null, _toDisplayString(_ctx.msg), 1 /* TEXT */)
]))
}
(4) 附錄 PatchFlags
export const enum PatchFlags {
TEXT = 1, // 動態(tài)文本節(jié)點
CLASS = 1 << 1, // 2 動態(tài)class
STYLE = 1 << 2, // 4 動態(tài)style
PROPS = 1 << 3, // 8 動態(tài)屬性,但不包含類名和樣式
FULL_PROPS = 1 << 4, // 16 具有動態(tài) key 屬性兵志,當 key 改變時醇蝴,需要進行完整的 diff 比較
HYDRATE_EVENTS = 1 << 5, // 32 帶有監(jiān)聽事件的節(jié)點
STABLE_FRAGMENT = 1 << 6, // 64 一個不會改變子節(jié)點順序的 fragment
KEYED_FRAGMENT = 1 << 7, // 128 帶有key屬性的 fragment 或部分帶有 key
UNKEYED_FRAGMENT = 1 << 8, // 256 子節(jié)點沒有 key 的 fragment
NEED_PATCH = 1 << 9, // 512 一個節(jié)點只會進行非 props 比較
DYNAMIC_SLOTS = 1 << 10, // 1024
HOISTED = -1,
BAIL = -2
}
2. 靜態(tài)提升(hoistStatic)
- Vue2中無論元素是否參與更新,每次都會重新創(chuàng)建想罕,然后再渲染
- Vue3中對于不參與更新的元素悠栓,會做靜態(tài)提升,只會被創(chuàng)建一次,在渲染時直接復用即可
(1) Vue編譯demo
<div>
<p>我是段落</p>
<p>{{msg}}</p>
</div>
import {
createVNode as _createVNode,
toDisplayString as _toDisplayString,
openBlock as _openBlock,
createBlock as _createBlock
} from "vue"
const _hoisted_1 = /*#__PURE__*/_createVNode("p", null, "我是段落", -1 /* HOISTED */)
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock("div", null, [
_hoisted_1,
_createVNode("p", null, _toDisplayString(_ctx.msg), 1 /* TEXT */)
]))
}
3. 事件偵聽器緩存(cacheHandlers)
- 默認情況下onClick會被視為動態(tài)綁定闸迷,所以每次都會去追蹤它的變化
但是因為是同一個函數(shù)嵌纲,所以沒有追蹤變化,直接緩存起來復用即可
(1) Vue編譯demo
<div>
<button @click="onClick">按鈕</button>
</div>
關閉事件偵聽器緩存
import {
createVNode as _createVNode,
openBlock as _openBlock,
createBlock as _createBlock
} from "vue"
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock("div", null, [
_createVNode("button", { onClick: _ctx.onClick }, "按鈕", 8 /* PROPS */, ["onClick"])
]))
}
開啟事件偵聽器緩存
import {
createVNode as _createVNode,
openBlock as _openBlock,
createBlock as _createBlock
} from "vue"
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock("div", null, [
_createVNode("button", {
onClick: _cache[1] || (_cache[1] = (...args) => (_ctx.onClick(...args)))
}, "按鈕")
]))
}
注意點:轉換之后的代碼腥沽,大家可能還看不懂逮走,但是不要緊,我們只需要觀察有沒有靜態(tài)標記即可今阳,因為我們知道在Vue3的diff算法中师溅,只有有靜態(tài)標記的才會進行比較宴霸,才會進行追蹤
4. ssr渲染
- 當有大量靜態(tài)內(nèi)容時候柄沮,這些內(nèi)容會被當做純字符串推進一個buffer里面涛碑,
即使存在動態(tài)綁定窒典,會通過模板插值嵌入進去。這樣會比通過虛擬dom來渲染的快上很多很多合陵。 - 當靜態(tài)內(nèi)容大到一定量級時候氮唯,會用_createStaticVNode方法在客戶端生成一個static node轧简,這些靜態(tài)node膝舅,會被直接innerHTML嗡载,就不需要創(chuàng)建對象,然后根據(jù)對象渲染仍稀。
04-Vue3.0-項目創(chuàng)建-理解
一洼滚、創(chuàng)建Vue3的三種方式
1. Vue-CLI
npm install -g @vue/cli
vue create projectName
cd projectName
npm run serve
2. Webpack
git clone https://github.com/vuejs/vue-next-webpack-preview.git projectName
cd projectName
npm install
npm run dev
3. Vite
二、 什么是Vite?
- Vite是Vue作者開發(fā)的一款意圖取代webpack的工具
- 其實現(xiàn)原理是利用ES6的import會發(fā)送請求去加載文件的特性技潘,攔截這些請求遥巴,做一些預編譯,省去webpack冗長的打包時間
三享幽、 利用Vite創(chuàng)建Vue3項目
1. 安裝Vite
npm install -g create-vite-app
2. 創(chuàng)建Vue3項目
create-vite-app projectName
3. 安裝依賴運行項目
cd projectName
npm install
npm run dev
四铲掐、Vue3.0 demo
- 修改App.vue文件
<template>
<div>
<p>{{msg}}</p>
<button @click="myFn">按鈕</button>
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
msg: '智播漁',
}
},
methods: {
myFn() {
alert('www.it666.com')
},
},
}
</script>
05-Vue2.x-存在的問題-理解
一、todolist demo
<template>
<div>
<form>
<input
type="text"
v-model="stu.id"
/>
<input
type="text"
v-model="stu.name"
/>
<input
type="text"
v-model="stu.age"
/>
<input
type="submit"
@click="addStu"
/>
</form>
<ul>
<li
v-for="(stu,index) in stus"
:key="stu.id"
@click="remStu(index)"
>
{{stu.name}} -- {{stu.age}}
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
stus: [
{ id: 1, name: 'zs', age: 10 },
{ id: 2, name: 'ls', age: 20 },
{ id: 3, name: 'ww', age: 30 },
],
stu: {
id: '',
name: '',
age: '',
},
}
// 新增功能1的數(shù)據(jù)
// 新增功能2的數(shù)據(jù)
},
methods: {
remStu(index) {
this.stus = this.stus.filter((stu, idx) => idx != index)
},
addStu(e) {
e.preventDefault()
const stu = Object.assign({}, this.stu)
this.stus.push(stu)
this.stu = {
id: '',
name: '',
age: '',
}
},
// 新增功能1的業(yè)務邏輯
// 新增功能2的業(yè)務邏輯
},
computed: {
// 新增功能1的業(yè)務邏輯
// 新增功能2的業(yè)務邏輯
},
watch: {
// 新增功能1的業(yè)務邏輯
// 新增功能2的業(yè)務邏輯
},
}
</script>
二琉闪、 問題
數(shù)據(jù)和業(yè)務邏輯分散迹炼,不利于管理維護
06-Vue3.0-組合API上-理解
一砸彬、組合API初體驗 demo
<template>
<div>
<p>{{count}}</p>
<button @click="myFn">按鈕</button>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
name: 'App',
// setup函數(shù)是組合API的入口函數(shù)
setup() {
// 定義了一個名稱叫做count的變量颠毙,這個變量的初始值是0
// 這個變量發(fā)生改變之后,Vue會自動更新UI
let count = ref(0)
// 在組合API中砂碉,如果想定義方法蛀蜜,不用定義到methods中,直接定義即可
function myFn() {
count.value += 1
}
// 注意點
// 在組合API中定義的變量/方法增蹭,要想在外界使用滴某,必須通過return {xxx, xxx}暴露出去
return {
count,
myFn,
}
},
}
</script>
二、注意點
- setup函數(shù)是組合API的入口函數(shù)
- 使用ref定義一個變量并設置初始值,這個變量發(fā)生改變之后霎奢,Vue會自動更新UI
- ref函數(shù)只能監(jiān)聽簡單類型的變化户誓,不能監(jiān)聽復雜類型的變化(對象/數(shù)組)
- 在組合API中,如果想定義方法幕侠,不用定義到methods中帝美,直接定義即可
- 在組合API中定義的變量/方法,要想在外界使用晤硕,必須通過return {xxx, xxx}暴露出去
07-Vue3.0-組合API中-理解
一悼潭、業(yè)務抽離demo
<template>
<div>
<ul>
<li
v-for="(stu,index) in state.stus"
:key="stu.id"
@click="remStu(index)"
>
{{stu.name}} -- {{stu.age}}
</li>
</ul>
</div>
</template>
<script>
import { reactive } from 'vue'
function useRemoveStudent() {
let state = reactive({
stus: [
{ id: 1, name: 'zs', age: 10 },
{ id: 2, name: 'ls', age: 20 },
{ id: 3, name: 'ww', age: 30 },
],
})
function remStu(index) {
state.stus = state.stus.filter((stu, idx) => idx != index)
}
return { state, remStu }
}
export default {
name: 'App',
setup() {
let { state, remStu } = useRemoveStudent()
return {
state,
remStu,
}
},
}
</script>
二、 理解
刪除用戶的業(yè)務代碼被抽離到了useRemoveStudent中舞箍,利于之后的管理和維護
08-Vue3.0-組合API下-理解
一舰褪、多文件demo
<template>
<div>
<form>
<input
type="text"
v-model="state2.stu.id"
/>
<input
type="text"
v-model="state2.stu.name"
/>
<input
type="text"
v-model="state2.stu.age"
/>
<input
type="submit"
@click="addStu"
/>
</form>
<ul>
<li
v-for="(stu,index) in state.stus"
:key="stu.id"
@click="remStu(index)"
>
{{stu.name}} -- {{stu.age}}
</li>
</ul>
</div>
</template>
<script>
import { reactive } from 'vue'
import useAddStudent from './js/add'
import useRemoveStudent from './js/remove'
export default {
name: 'App',
setup() {
let { state, remStu } = useRemoveStudent()
let { state2, addStu } = useAddStudent(state)
return {
state,
remStu,
state2,
addStu,
}
},
}
</script>
import { reactive } from 'vue'
function useRemoveStudent() {
let state = reactive({
stus: [
{ id: 1, name: 'zs', age: 10 },
{ id: 2, name: 'ls', age: 20 },
{ id: 3, name: 'ww', age: 30 },
],
})
function remStu(index) {
state.stus = state.stus.filter((stu, idx) => idx != index)
}
return { state, remStu }
}
export default useRemoveStudent
import { reactive } from 'vue'
function useAddStudent (state) {
let state2 = reactive({
stu: {
id: '',
name: '',
age: '',
},
})
function addStu (e) {
e.preventDefault()
const stu = Object.assign({}, state2.stu)
state.stus.push(stu)
state2.stu = {
id: '',
name: '',
age: '',
}
}
return { state2, addStu }
}
export default useAddStudent
二、理解
- 所有的功能都可以放到獨立的模塊中去管理
09-Vue3.0-來點動力-理解
Vue2.0 按照操作劃分代碼塊疏橄,Vue3.0 按業(yè)務劃分代碼塊
10-Vue3.0-組合API本質(zhì)-理解&&11-Vue3.0-setup執(zhí)行時機和注意點-理解
一占拍、 Composition API 和 Option API 混合使用
Composition API 和 Option API可以混合使用
1. 混合使用demo
<template>
<div>
<p>{{name}}</p>
<button @click="myFn1">按鈕</button>
<p>{{age}}</p>
<button @click="myFn2">按鈕</button>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
name: 'App',
data() {
return {
name: 'lnj',
}
},
methods: {
myFn1() {
alert('abc')
},
},
setup() {
let age = ref(18)
function myFn2() {
alert('sxx')
}
return {
age,
myFn2,
}
},
}
</script>
二、 Composition API本質(zhì)(組合API/注入API)
Composition API的本質(zhì)就是在運行的時候將暴露出去的數(shù)據(jù)注入到option api中捎迫,如將數(shù)據(jù)注入到data中刷喜,將方法注入到methods中。
三立砸、 setup執(zhí)行時機
setup在beforeCreate和created兩個生命周期之間執(zhí)行
- beforeCreate: 表示組件剛剛被創(chuàng)建出來掖疮,組件的data和methods還沒有初始化好
- setup
- created: data和methods已經(jīng)初始化好
四、 setup注意點
- 由于在執(zhí)行setup函數(shù)的時候颗祝,還沒有執(zhí)行created生命周期方法浊闪,所以在setup函數(shù)中,是無法使用data和methods
- 由于我們不能在setup函數(shù)中無法使用data和methods螺戳,所以Vue為了避免我們錯誤的使用搁宾,它直接將函數(shù)中的this修改成了undefined
- setup函數(shù)只能是同步的,不能是異步的
12-Vue3.0-reactive-理解
一倔幼、什么是reactive
- reactive是Vue3中提供的實現(xiàn)響應式數(shù)據(jù)的方法
- 在Vue2中響應式數(shù)據(jù)是通過defineProperty來實現(xiàn)的盖腿,而在Vue3中響應式數(shù)據(jù)是通過ES6的Proxy來實現(xiàn)的
二、reactiv注意點
- reactive參數(shù)必須是對象(json/arr)
- 如果給reactive傳遞了其他對象
- 默認情況下修改對象损同,界面不會自動更新
- 如果想更新翩腐,可以通過重新賦值的方式
1. 給reactive傳遞非對象無法實現(xiàn)響應式
點擊按鈕值發(fā)生變化但頁面將不會發(fā)生變化
<template>
<div>
<p>{{state}}</p>
<button @click="myFn">按鈕</button>
</div>
</template>
<script>
import { reactive } from 'vue'
export default {
name: 'App',
setup() {
// 創(chuàng)建一個響應式數(shù)據(jù)
// 本質(zhì):就是將傳入的數(shù)據(jù)包裝成一個Proxy對象
let state = reactive(123)
function myFn() {
state = 666 // 由于在創(chuàng)建響應式數(shù)據(jù)的時候傳遞的不是一個對象,所以無法實現(xiàn)響應式
console.log(state)
}
return {
state,
myFn,
}
},
}
</script>
2. 需要傳遞一個對象才可以實現(xiàn)響應式
點擊按鈕頁面將發(fā)生變化
<template>
<div>
<p>{{state.age}}</p>
<button @click="myFn">按鈕</button>
</div>
</template>
<script>
import { reactive } from 'vue'
export default {
name: 'App',
setup() {
let state = reactive({
age: 123,
})
function myFn() {
state.age = 666
console.log(state)
}
return {
state,
myFn,
}
},
}
</script>
3. 數(shù)組也可以監(jiān)聽
點擊按鈕修改數(shù)組的值將發(fā)現(xiàn)頁面發(fā)生變化
<template>
<div>
<p>{{state}}</p>
<button @click="myFn">按鈕</button>
</div>
</template>
<script>
import { reactive } from 'vue'
export default {
name: 'App',
setup() {
let state = reactive([1, 3, 5])
function myFn() {
state[0] = 666
console.log(state)
}
return {
state,
myFn,
}
},
}
</script>
4. 其他對象不能實現(xiàn)響應式膏燃,需要重新賦值
調(diào)用Date自帶的方法不能實現(xiàn)響應式茂卦,需要使用注釋的方法實現(xiàn)
<template>
<div>
<p>{{state.time}}</p>
<button @click="myFn">按鈕</button>
</div>
</template>
<script>
import { reactive } from 'vue'
export default {
name: 'App',
setup() {
let state = reactive({
time: new Date(),
})
function myFn() {
// const newTime = new Date(state.time.getTime())
// newTime.setDate(newTime.getDate() + 1)
// state.time = newTime
state.time.setDate(state.time.getDate() + 1)
console.log(state.time)
}
return {
state,
myFn,
}
},
}
</script>
13-Vue3.0-ref-理解
一、什么是ref
- ref和reactive一樣组哩,也是用來實現(xiàn)響應式數(shù)據(jù)的方法
- 由于reactive必須傳遞一個對象等龙,所以導致在企業(yè)開發(fā)中如果我們只想讓某個變量實現(xiàn)響應式的時候會非常麻煩处渣,所以Vue3就給我們提供了ref方法,實現(xiàn)對簡單值的監(jiān)聽
二蛛砰、ref本質(zhì)
- ref底層的本質(zhì)其實還是reactive罐栈,系統(tǒng)會自動根據(jù)我們給ref傳入的值將它轉換成
ref(xx) -> reactive({value: xx})
三、ref注意點
- 在template中使用ref的值不用通過value獲取
- 在js中使用ref的值必須通過value獲取
- js中加value泥畅,template中不需要加value
<template>
<div>
<p>{{age}}</p>
<button @click="myFn">按鈕</button>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
name: 'App',
setup() {
let age = ref(18)
function myFn() {
age.value = 666
}
return {
age,
myFn,
}
},
}
</script>
14-Vue3.0-ref和reactive區(qū)別-理解
一悠瞬、Vue在處理的時候會先判斷數(shù)據(jù)是什么類型的
- 如果在template里面使用的是ref類型的數(shù)據(jù),那么Vue會自動幫我們添加.value
- 如果template里使用的是reactive類型的數(shù)據(jù)涯捻,那么Vue不會自動幫我們添加.value
二浅妆、Vue是如何判斷數(shù)據(jù)類型的呢
1. 打印ref數(shù)據(jù)的結果
RefImpl {_rawValue: 18, _shallow: false, __v_isRef: true, _value: 18}
__v_isRef: true
_rawValue: 18
_shallow: false
_value: 18
value: 18
2. 解釋
- Vue在解析數(shù)據(jù)之前,會自動判斷這個數(shù)據(jù)是否是 ref 類型的障癌,如果是就自動添加 .value 凌外,如果不是就不自動添加 .value
- 通過當前數(shù)據(jù)的 __v_isRef 來判斷,如果有這個私有屬性涛浙,并且取值為true康辑,那么就代表是一個ref類型的數(shù)據(jù)
三、isRef和isReactive
通過 isRef 和 isReactive 可以判斷數(shù)據(jù)是 ref 還是 reactive
<template>
<div>
<p>{{age}}</p>
<p>{{state}}</p>
<button @click="myFn">按鈕</button>
</div>
</template>
<script>
import { ref, isRef, isReactive, reactive } from 'vue'
export default {
name: 'App',
setup() {
let age = ref(18)
let state = reactive({ age: 18 })
function myFn() {
console.log(isRef(age))
console.log(isRef(state))
console.log(isReactive(age))
console.log(isReactive(state))
}
return {
age,
state,
myFn,
}
},
}
</script>
15-Vue3.0-遞歸監(jiān)聽理解
一轿亮、遞歸監(jiān)聽
默認情況下疮薇,無論是通過ref還是通過reactive都是遞歸監(jiān)聽
1. reactive遞歸監(jiān)聽
<template>
<div>
<p>{{state.a}}</p>
<p>{{state.gf.b}}</p>
<p>{{state.gf.f.c}}</p>
<p>{{state.gf.f.s.d}}</p>
<button @click="myFn">按鈕</button>
</div>
</template>
<script>
import { reactive } from 'vue'
export default {
name: 'App',
setup() {
let state = reactive({
a: 'a',
gf: {
b: 'b',
f: {
c: 'c',
s: {
d: 'd',
},
},
},
})
function myFn() {
state.a = '1'
state.gf.b = '2'
state.gf.f.c = '3'
state.gf.f.s.d = '4'
}
return {
state,
myFn,
}
},
}
</script>
2. ref遞歸監(jiān)聽
<template>
<div>
<p>{{state.a}}</p>
<p>{{state.gf.b}}</p>
<p>{{state.gf.f.c}}</p>
<p>{{state.gf.f.s.d}}</p>
<button @click="myFn">按鈕</button>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
name: 'App',
setup() {
let state = ref({
a: 'a',
gf: {
b: 'b',
f: {
c: 'c',
s: {
d: 'd',
},
},
},
})
function myFn() {
state.value.a = '1'
state.value.gf.b = '2'
state.value.gf.f.c = '3'
state.value.gf.f.s.d = '4'
}
return {
state,
myFn,
}
},
}
</script>
二、遞歸監(jiān)聽存在的問題
如果數(shù)據(jù)量比較大我注,非常消耗性能
因為遞歸監(jiān)聽將使每一層都被包裝成一個Proxy
1. 遞歸監(jiān)聽驗證
<template>
<div>
<p>{{state.a}}</p>
<p>{{state.gf.b}}</p>
<p>{{state.gf.f.c}}</p>
<p>{{state.gf.f.s.d}}</p>
<button @click="myFn">按鈕</button>
</div>
</template>
<script>
import { reactive } from 'vue'
export default {
name: 'App',
setup() {
let state = reactive({
a: 'a',
gf: {
b: 'b',
f: {
c: 'c',
s: {
d: 'd',
},
},
},
})
function myFn() {
console.log(state)
console.log(state.gf)
console.log(state.gf.f)
console.log(state.gf.f.s)
}
return {
state,
myFn,
}
},
}
</script>
點擊按鈕看到控制臺的輸出
16-Vue3.0-非遞歸監(jiān)聽-掌握
一按咒、非遞歸監(jiān)聽
1. shallowReactive
<template>
<div>
<p>{{state.a}}</p>
<p>{{state.gf.b}}</p>
<p>{{state.gf.f.c}}</p>
<p>{{state.gf.f.s.d}}</p>
<button @click="myFn">按鈕</button>
</div>
</template>
<script>
import { shallowReactive } from 'vue'
export default {
name: 'App',
setup() {
let state = shallowReactive({
a: 'a',
gf: {
b: 'b',
f: {
c: 'c',
s: {
d: 'd',
},
},
},
})
function myFn() {
// state.a = '1'
state.gf.b = '2'
state.gf.f.c = '3'
state.gf.f.s.d = '4'
console.log(state)
console.log(state.gf)
console.log(state.gf.f)
console.log(state.gf.f.s)
}
return {
state,
myFn,
}
},
}
</script>
點擊按鈕頁面將不會發(fā)生變化,查看控制臺將看到以下打印結果
發(fā)現(xiàn)除了第一層之外其它層沒有被包裝成Proxy
2. shallowRef
<template>
<div>
<p>{{state.a}}</p>
<p>{{state.gf.b}}</p>
<p>{{state.gf.f.c}}</p>
<p>{{state.gf.f.s.d}}</p>
<button @click="myFn">按鈕</button>
</div>
</template>
<script>
import { shallowRef } from 'vue'
export default {
name: 'App',
setup() {
let state = shallowRef({
a: 'a',
gf: {
b: 'b',
f: {
c: 'c',
s: {
d: 'd',
},
},
},
})
function myFn() {
state.value.a = '1'
state.value.gf.b = '2'
state.value.gf.f.c = '3'
state.value.gf.f.s.d = '4'
console.log(state)
console.log(state.value)
console.log(state.value.gf)
console.log(state.value.gf.f)
console.log(state.value.gf.f.s)
}
return {
state,
myFn,
}
},
}
</script>
點擊按鈕頁面將不會發(fā)生變化但骨,查看控制臺將看到以下打印結果
發(fā)現(xiàn)除了第一層之外所有層均沒有被包裝
注意: 如果是通過shallowRef創(chuàng)建數(shù)據(jù)励七,nameVue監(jiān)聽的是.value的變化,并不是第一層的變化
<template>
<div>
<p>{{state.a}}</p>
<p>{{state.gf.b}}</p>
<p>{{state.gf.f.c}}</p>
<p>{{state.gf.f.s.d}}</p>
<button @click="myFn">按鈕</button>
</div>
</template>
<script>
import { shallowRef } from 'vue'
export default {
name: 'App',
setup() {
let state = shallowRef({
a: 'a',
gf: {
b: 'b',
f: {
c: 'c',
s: {
d: 'd',
},
},
},
})
function myFn() {
state.value = {
a: '1',
gf: {
b: '2',
f: {
c: '3',
s: {
d: '4',
},
},
},
}
}
return {
state,
myFn,
}
},
}
</script>
點擊按鈕發(fā)現(xiàn)頁面發(fā)生變化了
3. triggerRef
采用非遞歸監(jiān)聽如果想監(jiān)聽第四層的數(shù)據(jù)奔缠,可以使用triggerRef根據(jù)傳入的數(shù)據(jù)主動更新界面
<template>
<div>
<p>{{state.a}}</p>
<p>{{state.gf.b}}</p>
<p>{{state.gf.f.c}}</p>
<p>{{state.gf.f.s.d}}</p>
<button @click="myFn">按鈕</button>
</div>
</template>
<script>
import { shallowRef, triggerRef } from 'vue'
export default {
name: 'App',
setup() {
let state = shallowRef({
a: 'a',
gf: {
b: 'b',
f: {
c: 'c',
s: {
d: 'd',
},
},
},
})
function myFn() {
state.value.gf.f.s.d = '4'
triggerRef(state)
}
return {
state,
myFn,
}
},
}
</script>
點擊按鈕發(fā)現(xiàn)第四層的數(shù)據(jù)發(fā)生了變化
注意: Vue3只提供了triggerRef方法掠抬,沒有提供triggerReactive方法,所以如果是reactive類型的數(shù)據(jù)校哎,那么是無法主動觸發(fā)界面更新的
17-Vue3.0-shallowRef本質(zhì)
ref->reactive
ref(10)->reactive({value: 10})
shallowRef->shallowReactive
shallowRef(10)->shallowReactive({value: 10})
所以如果是通過shallowRef創(chuàng)建的數(shù)據(jù)两波,它監(jiān)聽的是.value的變化,因為底層本質(zhì)上value才是第一層
18-Vue3.0-toRaw && 19-Vue3.0-toRaw
<template>
<div>
<p>{{state}}</p>
<button @click="myFn">按鈕</button>
</div>
</template>
<script>
import { reactive } from 'vue'
export default {
name: 'App',
setup() {
let obj = {
name: 'lnj',
age: 18,
}
let state = reactive(obj)
console.log(obj === state) // false
// state和obj的關系
// 引用關系闷哆,state的本質(zhì)是一個Proxy對象腰奋,在這個Proxy對象引用了obj
function myFn() {
// 如果直接修改obj,那么是無法觸發(fā)界面更新的阳准,只有通過包裝之后的對象來修改氛堕,才會觸發(fā)界面的更新
obj.name = 'zs'
console.log(state)
}
return {
state,
myFn,
}
},
}
</script>
一馏臭、toRaw
從reactive或ref中得到原始數(shù)據(jù)
<template>
<div>
<p>{{state}}</p>
</div>
</template>
<script>
import { reactive, toRaw } from 'vue'
export default {
name: 'App',
setup() {
let obj = {
name: 'lnj',
age: 18,
}
let state = reactive(obj)
let obj2 = toRaw(state)
console.log(obj === obj2)
return {
state,
}
},
}
</script>
控制臺打印true
二野蝇、toRaw作用
做一些不想被監(jiān)聽的事情(提升性能)
ref/reactive數(shù)據(jù)類型的特點:每次修改都會被追蹤讼稚,都會更新UI界面,但是這樣其實是非常消耗性能的绕沈,所以如果我們有一些操作不需要追蹤锐想,不需要更新UI界面,那么這個時候乍狐,我們就可以通過toRaw方法拿到它的原始數(shù)據(jù)赠摇,對原始數(shù)據(jù)進行修改,這樣就不會被追蹤浅蚪,這樣就不會更新UI界面藕帜,這樣性能就好了
<template>
<div>
<p>{{state}}</p>
<button @click="myFn">按鈕</button>
</div>
</template>
<script>
import { reactive, toRaw } from 'vue'
export default {
name: 'App',
setup() {
let obj = {
name: 'lnj',
age: 18,
}
let state = reactive(obj)
let obj2 = toRaw(state)
console.log(obj === obj2)
function myFn() {
obj2.name = 'zs'
console.log(state)
}
return {
state,
myFn,
}
},
}
</script>
點擊按鈕發(fā)現(xiàn)數(shù)據(jù)發(fā)生了改變,但界面沒有發(fā)生改變
三惜傲、ref的toRaw
如果想通過toRaw拿到ref類型的原始數(shù)據(jù)(創(chuàng)建時傳入的那個數(shù)據(jù))洽故,那么就必須明確告訴toRaw方法,要獲取的是.value的值盗誊,因為經(jīng)過Vue處理之后.value中保存的才是當初創(chuàng)建時傳入的那個原始數(shù)據(jù)
<template>
<div>
<p>{{state}}</p>
<button @click="myFn">按鈕</button>
</div>
</template>
<script>
import { ref, toRaw } from 'vue'
export default {
name: 'App',
setup() {
let obj = {
name: 'lnj',
age: 18,
}
// ref本質(zhì):reactive
// ref(obj) -> reactive({value: obj})
let state = ref(obj)
let obj2 = toRaw(state.value)
console.log(obj === obj2)
function myFn() {
obj2.name = 'zs'
console.log(state)
}
return {
state,
myFn,
}
},
}
</script>
20-Vue3.0-markRaw
markRaw標記某個數(shù)據(jù)永遠不會被追蹤
<template>
<div>
<p>{{state}}</p>
<button @click="myFn">按鈕</button>
</div>
</template>
<script>
import { markRaw, reactive } from 'vue'
export default {
name: 'App',
setup() {
let obj = {
name: 'lnj',
age: 18,
}
obj = markRaw(obj)
let state = reactive(obj)
function myFn() {
state.name = 'zs'
}
return {
state,
myFn,
}
},
}
</script>
點擊按鈕數(shù)據(jù)不會發(fā)生變化
21-Vue3.0-toRef-理解
一时甚、toRef的理解
- 如果利用ref將某一個對象中的屬性變成響應式的數(shù)據(jù), 我們修改響應式數(shù)據(jù)是不會影響到原始數(shù)據(jù)的哈踱。
- 如果利用toRef將某一個對象中的屬性變成響應式的數(shù)據(jù)荒适,我們修改響應式數(shù)據(jù)是會影響到原始數(shù)據(jù)的
- 但是如果響應式數(shù)據(jù)是通過toRef創(chuàng)建的,那么修改了數(shù)據(jù)并不會觸發(fā)UI界面的更新
<template>
<div>
<p>{{state}}</p>
<button @click="myFn">按鈕</button>
</div>
</template>
<script>
import { toRef } from 'vue'
export default {
name: 'App',
setup() {
let obj = {
name: 'lnj',
}
let state = toRef(obj, 'name')
function myFn() {
state.value = 'zs'
console.log(state)
console.log(obj)
}
return {
state,
myFn,
}
},
}
</script>
點擊按鈕發(fā)現(xiàn)數(shù)據(jù)與原始數(shù)據(jù)都發(fā)生了變化
二开镣、ref和toRef區(qū)別
- ref->復制刀诬,修改響應式數(shù)據(jù)不會影響以前的數(shù)據(jù)
- toRef->引用,修改響應式數(shù)據(jù)會影響以前的數(shù)據(jù)
- ref->數(shù)據(jù)發(fā)生改變邪财,界面就會自動更新
- toRef->數(shù)據(jù)發(fā)生改變舅列,界面也不會自動更新
三、toRef引用場景
如果想讓響應式數(shù)據(jù)和以前的數(shù)據(jù)關聯(lián)起來卧蜓,并且更新響應式數(shù)據(jù)之后還不想更新UI帐要,name就可以使用toRef。
22-Vue3.0-toRefs-理解
將對象中所有的屬性全部追蹤
<template>
<div>
<p>{{state}}</p>
<button @click="myFn">按鈕</button>
</div>
</template>
<script>
import { toRefs } from 'vue'
export default {
name: 'App',
setup() {
let obj = {
name: 'lnj',
age:18
}
let state = toRefs(obj)
function myFn() {
state.name.value = 'zs'
state.age.value = 666
console.log(state)
console.log(obj)
}
return {
state,
myFn,
}
},
}
</script>
點擊按鈕發(fā)現(xiàn)數(shù)據(jù)和原始數(shù)據(jù)都發(fā)生了變化
23-Vue3.0-customRef 上-理解 && 24-Vue3.0-customRef 下-理解
一弥奸、customRef
返回一個ref對象榨惠,可以顯式地控制依賴追蹤和觸發(fā)響應
<template>
<div>
<p>{{age}}</p>
<button @click="myFn">按鈕</button>
</div>
</template>
<script>
import { customRef } from 'vue'
function myRef(value) {
return customRef((track, trigger) => {
return {
get() {
track() // 告訴Vue這個數(shù)據(jù)需要追蹤變化
console.log('get', value)
return value
},
set(newValue) {
console.log('set', newValue)
value = newValue
trigger() // 告訴Vue觸發(fā)界面更新
},
}
})
}
export default {
name: 'App',
setup() {
let age = myRef(18)
function myFn() {
age.value += 1
}
return {
age,
myFn,
}
},
}
</script>
二、為什么要使用customRef
一個使用customRef的場景盛霎,根據(jù)數(shù)據(jù)的請求路徑進行追蹤
<template>
<ul>
<li
v-for="item in state"
:key="item.id"
>
{{item.name}}
</li>
</ul>
</template>
<script>
import { customRef } from 'vue'
function myRef(path, initValue) {
let value = initValue
return customRef((track, trigger) => {
fetch(path)
.then((res) => res.json())
.then((data) => {
value = data
trigger()
})
.catch((err) => {
console.log(err)
})
return {
get() {
// 這個數(shù)據(jù)是需要追中變化的
track()
console.log('get', value)
return value
},
set(newValue) {
console.log('set', newValue)
value = newValue
// 告訴Vue觸發(fā)界面更新
trigger()
},
}
})
}
export default {
name: 'App',
setup() {
let state = myRef('/data.json', [])
return {
state,
}
},
}
</script>
25-Vue3.0-ref-獲取元素-理解
在vue3.x中我們也可以通過ref來獲取元素
<template>
<div ref="box">
我是div
</div>
</template>
<script>
import { ref, onMounted } from 'vue'
export default {
name: 'App',
setup() {
let box = ref(null) // reactive({value: null})
onMounted(() => {
console.log('onMounted', box.value)
})
return {
box,
}
},
}
</script>
26-Vue3.0-readonly家族-理解
用于創(chuàng)建一個只讀的數(shù)據(jù)赠橙,并且是遞歸只讀
一、readonly
<template>
<div>
<p>{{state.name}}</p>
<p>{{state.attr.age}}</p>
<p>{{state.attr.height}}</p>
<button @click="myFn">按鈕</button>
</div>
</template>
<script>
import { readonly, isReadonly, shallowReadonly } from 'vue'
export default {
name: 'App',
setup() {
// 用于創(chuàng)建一個只讀的數(shù)據(jù)愤炸,并且是遞歸只讀
let state = readonly({
name: 'lnj',
attr: {
age: 18,
height: 1.88,
},
})
function myFn() {
state.name = '知播漁'
state.attr.age = 666
state.attr.height = 1.66
console.log(state)
}
return {
state,
myFn,
}
},
}
</script>
二期揪、shallowReadonly
用于創(chuàng)建一個只讀的數(shù)據(jù),但不是遞歸只讀的
<template>
<div>
<p>{{state.name}}</p>
<p>{{state.attr.age}}</p>
<p>{{state.attr.height}}</p>
<button @click="myFn">按鈕</button>
</div>
</template>
<script>
import { readonly, isReadonly, shallowReadonly } from 'vue'
export default {
name: 'App',
setup() {
// 用于創(chuàng)建一個只讀的數(shù)據(jù)规个,并且是遞歸只讀
let state = shallowReadonly({
name: 'lnj',
attr: {
age: 18,
height: 1.88,
},
})
function myFn() {
state.name = '知播漁'
state.attr.age = 666
state.attr.height = 1.66
console.log(state)
}
return {
state,
myFn,
}
},
}
</script>
三凤薛、isReadonly
判斷一個數(shù)據(jù)是否是只讀的
<template>
<div>
<p>{{state.name}}</p>
<p>{{state.attr.age}}</p>
<p>{{state.attr.height}}</p>
<button @click="myFn">按鈕</button>
</div>
</template>
<script>
import { readonly, isReadonly, shallowReadonly } from 'vue'
export default {
name: 'App',
setup() {
// 用于創(chuàng)建一個只讀的數(shù)據(jù)姓建,并且是遞歸只讀
let state = shallowReadonly({
name: 'lnj',
attr: {
age: 18,
height: 1.88,
},
})
function myFn() {
state.name = '知播漁'
state.attr.age = 666
state.attr.height = 1.66
console.log(state)
console.log(isReadonly(state))
}
return {
state,
myFn,
}
},
}
</script>
四、readonly 和 const 的區(qū)別
const:賦值保護缤苫,不能給變量重新賦值
readonly:屬性保護速兔,不能給屬性重新賦值
27-Vue3.0-V3響應式數(shù)據(jù)本質(zhì)上-理解 && 28-Vue3.0-V3響應式數(shù)據(jù)本質(zhì)下-理解
一、Vue3.0響應式數(shù)據(jù)本質(zhì)
- 在Vue2.x中是通過defineProperty來實現(xiàn)響應式數(shù)據(jù)的
詳見:手寫Vue全家桶 - 在Vue3.x中是通過Proxy來實現(xiàn)響應式數(shù)據(jù)的
let obj = { name: 'lng', age: 18 }
let state = new Proxy(obj, {
get (obj, key) {
console.log(obj, key)
return obj[key]
},
set (obj, key, value) {
obj[key] = value
console.log('更新界面', obj, key, value)
}
})
console.log(state.name) // lng
state.name = '直播'
console.log(state.name)
二活玲、 Proxy注意點
- set方法必須通過返回值告訴Proxy此次操作是否成功
let arr = [1, 3, 5]
let state = new Proxy(arr, {
get (obj, key) {
console.log(obj, key)
return obj[key]
},
set (obj, key, value) {
obj[key] = value
console.log('更新界面', obj, key, value)
// 沒有會報錯
return true
}
})
console.log(state[1])
state.push(7)
29-Vue3.0-手寫 shallowReactive-shallowRef-理解
function shallowReactive (obj) {
return new Proxy(obj, {
get (obj, key) {
return obj[key]
},
set (obj, key, value) {
obj[key] = value
console.log('更新界面', obj, key, value)
return true
}
})
}
function shallowRef (val) {
return shallowReactive({
value: val
})
}
30-Vue3.0-手寫reactive-ref-理解
function reactive (obj) {
if (typeof obj === 'object') {
if (obj instanceof Array) {
// 如果是一個數(shù)組涣狗,那么取出數(shù)組中的某一個元素
// 判斷每一個元素是否又是一個對象,如果又是一個對象舒憾,如果又是一個對象镀钓,那么也需要包裝成一個Proxy
obj.forEach((item, index) => {
if (typeof item === 'object') {
obj[index] = reactive(item)
}
})
} else {
// 如果是一個對象,那么取出對象屬性的取值
// 判斷每一個元素是否又是一個對象镀迂,如果又是一個對象掸宛,如果又是一個對象,那么也需要包裝成一個Proxy
for (let key in obj) {
let item = obj[key]
if (typeof item === 'object') {
obj[key] = reactive(item)
}
}
}
return new Proxy(obj, {
get (obj, key) {
return obj[key]
},
set (obj, key, value) {
obj[key] = value
console.log('更新界面', JSON.stringify(obj), key, value)
return true
}
})
} else {
console.warn('不是一個對象')
}
}
function ref (val) {
return reactive({
value: val
})
}
31-Vue3.0-手寫readonly-shallowReadonly-理解
function shallowReadonly (obj) {
return new Proxy(obj, {
get (obj, key) {
return obj[key]
},
set (obj, key, value) {
console.log('只讀')
}
})
}
function readonly (obj) {
if (typeof obj === 'object') {
if (obj instanceof Array) {
// 如果是一個數(shù)組招拙,那么取出數(shù)組中的某一個元素
// 判斷每一個元素是否又是一個對象唧瘾,如果又是一個對象,如果又是一個對象别凤,那么也需要包裝成一個Proxy
obj.forEach((item, index) => {
if (typeof item === 'object') {
obj[index] = readonly(item)
}
})
} else {
// 如果是一個對象饰序,那么取出對象屬性的取值
// 判斷每一個元素是否又是一個對象,如果又是一個對象规哪,如果又是一個對象求豫,那么也需要包裝成一個Proxy
for (let key in obj) {
let item = obj[key]
if (typeof item === 'object') {
obj[key] = readonly(item)
}
}
}
return new Proxy(obj, {
get (obj, key) {
return obj[key]
},
set (obj, key, value) {
console.log('只讀')
}
})
} else {
console.warn('不是一個對象')
}
}