?? 前言
本筆記根據(jù)如下筆記和視頻進(jìn)行整理
老師的課件筆記,不含視頻 https://www.aliyundrive.com/s/B8sDe5u56BU筆記在線版: https://note.youdao.com/s/5vP46EPC
視頻:尚硅谷Vue2.0+Vue3.0全套教程丨vuejs從入門到精通
?? 創(chuàng)建Vue3項(xiàng)目
?? vue-cli
使用vue-cli創(chuàng)建Vue3項(xiàng)目,需要確保vue-cli版本在4.5.0以上。
## 查看@vue/cli版本,確保@vue/cli版本在4.5.0以上
vue --version
vue -V
## 安裝或者升級(jí)@vue/cli
npm install -g @vue/cli
使用vue-cli創(chuàng)建Vue3項(xiàng)目
vue create vue3_study
?? vite
vite創(chuàng)建Vue3項(xiàng)目步驟:
## 創(chuàng)建工程
npm init vite-app <project-name>
## 進(jìn)入工程目錄
cd <project-name>
## 安裝依賴
npm install
## 運(yùn)行
npm run dev
npm init vite-app vue3_study_vite
cd vue3_study_vite
npm i
npm run dev
?? 項(xiàng)目結(jié)構(gòu)
使用的為vue-cli創(chuàng)建的項(xiàng)目
src\main.js
// 引入的為一個(gè)名為createApp的工廠函數(shù),不再是Vue構(gòu)造函數(shù)
import { createApp } from 'vue'
import App from './App.vue'
// 創(chuàng)建應(yīng)用實(shí)例對(duì)象,類似于Vue2中的vm,但是更“輕”,并掛載根標(biāo)簽
createApp(App).mount('#app')
src\App.vue
<template>
<!-- Vue3組件中的模板結(jié)構(gòu)可以沒有根標(biāo)簽 -->
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
</template>
?? Vue3開發(fā)者工具的安裝
可參考:[Vue]開發(fā)環(huán)境搭建
?? 初識(shí)setup
-
setup是Vue3.0中一個(gè)新的配置項(xiàng),值為一個(gè)函數(shù)。setup是所有Composition API(組合API)“ 表演的舞臺(tái) ”。組件中所用到的:數(shù)據(jù)、方法、計(jì)算屬性等等,均要配置在setup中。
-
setup函數(shù)的兩種返回值:
- 若返回一個(gè)對(duì)象,則對(duì)象中的屬性、方法, 在模板中均可以直接使用。
<template> <h1>個(gè)人介紹</h1> <h2>name: {{name}}</h2> <h2>age: {{age}}</h2> <button @click="sayHello">打招呼</button> </template> <script> export default { name: 'App', // 測(cè)試setup, 不考慮響應(yīng)式 setup() { // 數(shù)據(jù) let name = 'ZS' let age = 22 // 方法 function sayHello() { alert(`my name is ${name}, I am ${age}, hello`) } // 返回一個(gè)對(duì)象 return { name, age, sayHello } } } </script>
- 若返回一個(gè)渲染函數(shù):則可以自定義渲染內(nèi)容。
<template> <h1>個(gè)人介紹</h1> <h2>name: {{name}}</h2> <h2>age: {{age}}</h2> <button @click="sayHello">打招呼</button> </template> <script> import {h} from 'vue' // 導(dǎo)入渲染函數(shù) export default { name: 'App', // 測(cè)試setup, 不考慮響應(yīng)式 setup() { // 數(shù)據(jù) let name = 'ZS' let age = 22 // 方法 function sayHello() { alert(`my name is ${name}, I am ${age}, hello`) } // 返回一個(gè)對(duì)象 // return { // name, // age, // sayHello // } // 返回一個(gè)函數(shù)(渲染函數(shù)) // h1為渲染到頁面的標(biāo)簽,hello world為渲染內(nèi)容 // h1為渲染到頁面的標(biāo)簽,hello world為渲染內(nèi)容 return ()=>{ return h('h1', 'hello world') } } } </script>
- 若返回一個(gè)對(duì)象,則對(duì)象中的屬性、方法, 在模板中均可以直接使用。
-
在Vue3中仍然可以使用Vue2的配置項(xiàng)
<template> <h1>個(gè)人介紹</h1> <h2>name: {{name}}</h2> <h2>age: {{age}}</h2> <h2>sex: {{sex}}</h2> <button @click="sayHello">打招呼 vue3</button> <button @click="sayWelcome">打招呼 vue2</button> </template> <script> export default { name: 'App', data() { return { sex: 'male' } }, methods: { sayWelcome() { alert('hello world vue2') } }, // 測(cè)試setup, 不考慮響應(yīng)式 setup() { // 數(shù)據(jù) let name = 'ZS' let age = 22 // 方法 function sayHello() { alert(`my name is ${name}, I am ${age}, hello`) } // 返回一個(gè)對(duì)象 return { name, age, sayHello } } } </script>
-
setup盡量不要與Vue2.x配置混用。Vue2.x配置(data、methos、computed…)中可以訪問到setup中的屬性、方法。但在setup中不能訪問到Vue2.x配置(data、methos、computed…)。
<template> <h1>個(gè)人介紹</h1> <h2>name: {{name}}</h2> <h2>age: {{age}}</h2> <h2>sex: {{sex}}</h2> <button @click="sayHello">打招呼 vue3</button> <button @click="sayWelcome">打招呼 vue2</button> <button @click="test1">vue2配置項(xiàng)讀取setup屬性方法</button> <button @click="test2">setup配置項(xiàng)讀取vue2配置項(xiàng)屬性方法</button> </template> <script> export default { name: 'App', data() { return { sex: 'male' } }, methods: { sayWelcome() { alert('hello world vue2') }, test1() { console.log(this.name) console.log(this.age) console.log(this.sayHello) } }, // 測(cè)試setup, 不考慮響應(yīng)式 setup() { // 數(shù)據(jù) let name = 'ZS' let age = 22 // 方法 function sayHello() { alert(`my name is ${name}, I am ${age}, hello`) } function test2() { console.log(this.sex) console.log(this.sayWelcome) } // 返回一個(gè)對(duì)象 return { name, age, sayHello, test2, } } } </script>
-
Vue2配置項(xiàng)如果與setup中的屬性或方法沖突,setup優(yōu)先。即發(fā)生沖突時(shí),以Vue3為主。
<template> <h1>a: {{a}}</h1> </template> <script> export default { name: 'App', data() { return { a: 100 } }, // 測(cè)試setup, 不考慮響應(yīng)式 setup() { // 數(shù)據(jù) let a = 200 // 返回一個(gè)對(duì)象 return { a } } } </script>
-
setup不能是一個(gè)async函數(shù),因?yàn)榉祷刂挡辉偈莚eturn的對(duì)象, 而是promise, 模板看不到return對(duì)象中的屬性。(后期也可以返回一個(gè)Promise實(shí)例,但需要Suspense和異步組件的配合)
?? ref 函數(shù)
- 作用: 定義一個(gè)響應(yīng)式的數(shù)據(jù)
- 語法:
const xxx = ref(initValue)
- 創(chuàng)建一個(gè)包含響應(yīng)式數(shù)據(jù)的引用對(duì)象(reference對(duì)象,簡稱ref對(duì)象)。
- JS中操作數(shù)據(jù):
xxx.value
- 模板中讀取數(shù)據(jù): 不需要.value,直接:
<div>{{xxx}}</div>
- 備注:
- 接收的數(shù)據(jù)可以是:基本類型、也可以是對(duì)象類型。
- 基本類型的數(shù)據(jù):響應(yīng)式依然是靠
Object.defineProperty()
的get
與set
完成的。 - 對(duì)象類型的數(shù)據(jù):內(nèi)部 “ 求助 ” 了Vue3.0中的一個(gè)新函數(shù)——
reactive
函數(shù)。
<template>
<h1>個(gè)人信息</h1>
<p>name: {{name}}</p>
<p>age: {{age}}</p>
<p>job: {{job.type}}</p>
<p>salary: {{job.salary}}</p>
<button @click="changeInfo">修改信息</button>
</template>
<script>
import {ref} from 'vue'
export default {
name: 'App',
setup() {
// 數(shù)據(jù)
let name = ref('ZS')
let age = ref(22)
let job = ref({
type: '前端工程師',
salary: '30k'
})
// 方法
function changeInfo() {
name.value = 'LS'
age.value = 30
job.value.type = 'Java開發(fā)工程師'
job.value.salary = '40k'
}
// 返回一個(gè)對(duì)象
return {
name,
age,
job,
changeInfo
}
}
}
</script>
?? reactive函數(shù)
- 作用: 定義一個(gè)對(duì)象類型的響應(yīng)式數(shù)據(jù)(基本類型不要用它,要用
ref
函數(shù)) - 語法:
const 代理對(duì)象= reactive(源對(duì)象)
接收一個(gè)對(duì)象(或數(shù)組),返回一個(gè)代理對(duì)象(Proxy的實(shí)例對(duì)象,簡稱proxy對(duì)象) - reactive定義的響應(yīng)式數(shù)據(jù)是“深層次的”。
- 內(nèi)部基于 ES6 的 Proxy 實(shí)現(xiàn),通過代理對(duì)象操作源對(duì)象內(nèi)部數(shù)據(jù)進(jìn)行操作。
<template>
<h1>個(gè)人信息</h1>
<p>name: {{name}}</p>
<p>age: {{age}}</p>
<p>job: {{job.type}}</p>
<p>salary: {{job.salary}}</p>
<p>hobby: {{hobby}}</p>
<p>測(cè)試數(shù)據(jù):{{job.a.b.c}}</p>
<button @click="changeInfo">修改信息</button>
</template>
<script>
import {ref, reactive} from 'vue'
export default {
name: 'App',
setup() {
// 數(shù)據(jù)
let name = ref('ZS')
let age = ref(22)
let job = reactive({
type: '前端工程師',
salary: '30k',
a: {
b: {
c: 1000
}
}
})
let hobby = reactive(['football', 'basketball'])
// 方法
function changeInfo() {
name.value = 'LS'
age.value = 30
job.type = 'Java開發(fā)工程師'
job.salary = '40k'
job.a.b.c = 2000
hobby[0] = 'reading'
}
// 返回一個(gè)對(duì)象
return {
name,
age,
job,
hobby,
changeInfo
}
}
}
</script>
??Vue3.0中的響應(yīng)式原理
?? vue2.x的響應(yīng)式
-
實(shí)現(xiàn)原理:
-
對(duì)象類型:通過
Object.defineProperty()
對(duì)屬性的讀取、修改進(jìn)行攔截(數(shù)據(jù)劫持)。 -
數(shù)組類型:通過重寫更新數(shù)組的一系列方法來實(shí)現(xiàn)攔截。(對(duì)數(shù)組的變更方法進(jìn)行了包裹)。
Object.defineProperty(data, 'count', { get () {}, set () {} })
-
-
存在問題:
- 新增屬性、刪除屬性, 界面不會(huì)更新。
- 使用
this.$set(對(duì)象, '新屬性名', '新屬性值')
或Vue.set(對(duì)象, '新屬性名', '新屬性值')
新增屬性,使用this.$delete(對(duì)象, '屬性名')
或Vue.$delete(對(duì)象, '屬性名')
刪除屬性。
- 使用
- 直接通過下標(biāo)修改數(shù)組, 界面不會(huì)自動(dòng)更新。
- 使用
this.$set(數(shù)組, 數(shù)組下標(biāo), ‘新值’)
或數(shù)組.splice(下標(biāo), 下標(biāo)+1, ‘新值’)
實(shí)現(xiàn)修改數(shù)組元素值。
- 使用
- 新增屬性、刪除屬性, 界面不會(huì)更新。
?? Vue3.0的響應(yīng)式
-
在Vue3中不存在“新增屬性、刪除屬性, 界面不會(huì)更新”與“直接通過下標(biāo)修改數(shù)組, 界面不會(huì)自動(dòng)更新”的問題。
<template> <h1>個(gè)人信息</h1> <p v-show="person.name">name: {{person.name}}</p> <p>age: {{person.age}}</p> <p v-show="person.sex">sex: {{person.sex}}</p> <p>hobby: {{person.hobby}}</p> <button @click="changeInfo">changeInfo</button> <button @click="addSex">addSex</button> <button @click="deleteName">deleteName</button> </template> <script> import {ref, reactive} from 'vue' export default { name: 'App', setup() { // 數(shù)據(jù) const person = reactive({ name: 'ZS', age: 22, hobby: ['apple', 'orange'] }) // 方法 function changeInfo() { person.hobby[0] = 'mango' } function addSex() { person.sex = 'male' } function deleteName() { delete person.name } // 返回一個(gè)對(duì)象 return { person, changeInfo, addSex, deleteName, } } } </script>
-
實(shí)現(xiàn)原理:
-
通過Proxy(代理): 攔截對(duì)象中任意屬性的變化, 包括:屬性值的讀寫、屬性的添加、屬性的刪除等。
//源數(shù)據(jù) let person = { name:'張三', age:18 } //模擬Vue3中實(shí)現(xiàn)響應(yīng)式 //Proxy對(duì)屬性的增刪改查都可以監(jiān)測(cè)得到 //#region const p = new Proxy(person,{ //有人讀取p的某個(gè)屬性時(shí)調(diào)用 get(target,propName){ console.log(`有人讀取了p身上的${propName}屬性`) return target[propName] }, //有人修改p的某個(gè)屬性、或給p追加某個(gè)屬性時(shí)調(diào)用 set(target,propName,value){ console.log(`有人修改了p身上的${propName}屬性,我要去更新界面了!`) target[propName] = value }, //有人刪除p的某個(gè)屬性時(shí)調(diào)用 deleteProperty(target,propName){ console.log(`有人刪除了p身上的${propName}屬性,我要去更新界面了!`) return delete target[propName] } }) //#endregion
-
通過Reflect(反射): 對(duì)源對(duì)象的屬性進(jìn)行操作。
// Reflect(反射) // 讀取對(duì)象指定屬性的值 Reflect.get(object, '屬性名') // 修改對(duì)象指定屬性的值 Reflect.set(object, '屬性名', '新屬性值') // 刪除對(duì)象指定屬性 Reflect.deleteProperty(object, '屬性名')
//模擬Vue3中實(shí)現(xiàn)響應(yīng)式 //#region const p = new Proxy(person,{ //有人讀取p的某個(gè)屬性時(shí)調(diào)用 get(target,propName){ console.log(`有人讀取了p身上的${propName}屬性`) return Reflect.get(target,propName) }, //有人修改p的某個(gè)屬性、或給p追加某個(gè)屬性時(shí)調(diào)用 set(target,propName,value){ console.log(`有人修改了p身上的${propName}屬性,我要去更新界面了!`) Reflect.set(target,propName,value) }, //有人刪除p的某個(gè)屬性時(shí)調(diào)用 deleteProperty(target,propName){ console.log(`有人刪除了p身上的${propName}屬性,我要去更新界面了!`) return Reflect.deleteProperty(target,propName) } }) //#endregion
-
MDN文檔中描述的Proxy與Reflect:
-
Proxy:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy
-
Reflect:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect
new Proxy(data, { // 攔截讀取屬性值 get (target, prop) { return Reflect.get(target, prop) }, // 攔截設(shè)置屬性值或添加新屬性 set (target, prop, value) { return Reflect.set(target, prop, value) }, // 攔截刪除屬性 deleteProperty (target, prop) { return Reflect.deleteProperty(target, prop) } }) proxy.name = 'tom'
-
-
?? reactive對(duì)比ref
- 從定義數(shù)據(jù)角度對(duì)比:
- ref用來定義:基本類型數(shù)據(jù)。
- reactive用來定義:對(duì)象(或數(shù)組)類型數(shù)據(jù)。
- 備注:ref也可以用來定義對(duì)象(或數(shù)組)類型數(shù)據(jù), 它內(nèi)部會(huì)自動(dòng)通過
reactive
轉(zhuǎn)為代理對(duì)象。
- 從原理角度對(duì)比:
- ref通過
Object.defineProperty()
的get
與set
來實(shí)現(xiàn)響應(yīng)式(數(shù)據(jù)劫持)。 - reactive通過使用Proxy來實(shí)現(xiàn)響應(yīng)式(數(shù)據(jù)劫持), 并通過Reflect操作源對(duì)象內(nèi)部的數(shù)據(jù)。
- ref通過
- 從使用角度對(duì)比:
- ref定義的數(shù)據(jù):操作數(shù)據(jù)需要
.value
,讀取數(shù)據(jù)時(shí)模板中直接讀取不需要.value
。 - reactive定義的數(shù)據(jù):操作數(shù)據(jù)與讀取數(shù)據(jù):均不需要
.value
。
- ref定義的數(shù)據(jù):操作數(shù)據(jù)需要
- 一般將數(shù)據(jù)封裝在一個(gè)data對(duì)象中,利用reactive函數(shù)將該對(duì)象變?yōu)轫憫?yīng)式數(shù)據(jù)對(duì)象
?? setup的兩個(gè)注意點(diǎn)
- setup執(zhí)行的時(shí)機(jī)
- 在beforeCreate之前執(zhí)行一次,this是undefined,在setup無法使用this。
- setup的參數(shù)
- props:值為對(duì)象,包含:組件外部傳遞過來,且組件內(nèi)部聲明接收了的屬性。
- context:上下文對(duì)象
- attrs: 值為對(duì)象,包含:組件外部傳遞過來,但沒有在props配置中聲明接收的屬性, 相當(dāng)于
this.$attrs
。存放沒有被組件props配置項(xiàng)接收的數(shù)據(jù),如果組件外部傳遞過來的數(shù)據(jù)都被組件props配置項(xiàng)接收,則該對(duì)象為空。 - slots: 收到的插槽內(nèi)容, 相當(dāng)于
this.$slots
。Vue3中具名插槽使用v-slot:插槽名
- emit: 分發(fā)自定義事件的函數(shù), 相當(dāng)于
this.$emit
。Vue3綁定的自定義事件在組件中需要使用emits配置項(xiàng)接收。
- attrs: 值為對(duì)象,包含:組件外部傳遞過來,但沒有在props配置中聲明接收的屬性, 相當(dāng)于
App.vue
<template>
<Demo @hello="showHelloMsg" msg="你好啊" school="尚硅谷">
<!-- Vue3中具名插槽使用 `v-slot:插槽名` -->
<template v-slot:qwe>
<span>尚硅谷</span>
</template>
<template v-slot:asd>
<span>尚硅谷</span>
</template>
</Demo>
</template>
<script>
import Demo from './components/Demo'
export default {
name: 'App',
components:{Demo},
setup(){
// 自定義事件的處理函數(shù)
function showHelloMsg(value){
alert(`你好啊,你觸發(fā)了hello事件,我收到的參數(shù)是:${value}!`)
}
return {
showHelloMsg
}
}
}
</script>
Demo.vue
<template>
<h1>一個(gè)人的信息</h1>
<h2>姓名:{{person.name}}</h2>
<h2>年齡:{{person.age}}</h2>
<button @click="test">測(cè)試觸發(fā)一下Demo組件的Hello事件</button>
</template>
<script>
import {reactive} from 'vue'
export default {
name: 'Demo',
props:['msg','school'],
emits:['hello'], // 綁定的自定義事件在組件中需要使用emits配置項(xiàng)接收
setup(props,context){
// console.log('---setup---',props)
// console.log('---setup---',context)
// console.log('---setup---',context.attrs) //相當(dāng)與Vue2中的$attrs
// console.log('---setup---',context.emit) //觸發(fā)自定義事件的。
console.log('---setup---',context.slots) //插槽
//數(shù)據(jù)
let person = reactive({
name:'張三',
age:18
})
//方法
function test(){
// 觸發(fā)自定義事件
context.emit('hello',666)
}
//返回一個(gè)對(duì)象(常用)
return {
person,
test
}
}
}
</script>
?? computed函數(shù)
-
與Vue2.x中computed配置功能一致,在Vue3中可以使用Vue2中的寫法(向下兼容)。在Vue3中計(jì)算屬性使用computed函數(shù)。
-
寫法
import {computed} from 'vue' setup(){ ... //計(jì)算屬性——簡寫 let fullName = computed(()=>{ return person.firstName + '-' + person.lastName }) //計(jì)算屬性——完整 let fullName = computed({ get(){ return person.firstName + '-' + person.lastName }, set(value){ const nameArr = value.split('-') person.firstName = nameArr[0] person.lastName = nameArr[1] } }) }
<template>
<h1>一個(gè)人的信息</h1>
姓: <input type="text" v-model="person.firstName"><br/>
名: <input type="text" v-model="person.lastName"><br/>
<p>全名: {{person.fullName}}</p>
</template>
<script>
import {reactive, computed} from 'vue'
export default {
name: 'Demo',
setup(){
//數(shù)據(jù)
let person = reactive({
firstName: '張',
lastName: '三'
})
// 計(jì)算屬性
person.fullName = computed(()=>{
return person.firstName + '-' + person.lastName
})
//返回一個(gè)對(duì)象
return {
person,
}
}
}
</script>
?? watch函數(shù)
-
與Vue2.x中watch配置功能一致,在Vue3中可以使用Vue2中的寫法(向下兼容)。
-
兩個(gè)小“坑”:
- 監(jiān)視r(shí)eactive定義的響應(yīng)式數(shù)據(jù)時(shí):oldValue無法正確獲取、強(qiáng)制開啟了深度監(jiān)視(deep配置失效)。
- 監(jiān)視r(shí)eactive定義的響應(yīng)式數(shù)據(jù)中某個(gè)屬性時(shí):deep配置有效。
//數(shù)據(jù)
let sum = ref(0)
let msg = ref('你好啊')
let person = reactive({
name:'張三',
age:18,
job:{
j1:{
salary:20
}
}
})
//情況一:監(jiān)視r(shí)ef定義的響應(yīng)式數(shù)據(jù)
// 第一個(gè)參數(shù)為監(jiān)視的數(shù)據(jù),第二個(gè)參數(shù)為回調(diào)函數(shù),第三個(gè)參數(shù)為配置屬性
watch(sum,(newValue,oldValue)=>{
console.log('sum變化了',newValue,oldValue)
},{immediate:true})
//情況二:監(jiān)視多個(gè)ref定義的響應(yīng)式數(shù)據(jù)
// newValue,oldValue為數(shù)組類型數(shù)據(jù)
watch([sum,msg],(newValue,oldValue)=>{
console.log('sum或msg變化了',newValue,oldValue)
})
/* 情況三:監(jiān)視r(shí)eactive定義的響應(yīng)式數(shù)據(jù)
若watch監(jiān)視的是reactive定義的響應(yīng)式數(shù)據(jù),則無法正確獲得oldValue??!
如果需要獲取監(jiān)視對(duì)象中某個(gè)屬性的oldValue,可以采用將該屬性單獨(dú)取出使用ref的形式
若watch監(jiān)視的是reactive定義的響應(yīng)式數(shù)據(jù),則強(qiáng)制開啟了深度監(jiān)視
*/
// 如果監(jiān)視的對(duì)象為使用ref函數(shù)實(shí)現(xiàn)響應(yīng)式,則需要監(jiān)視`變量.value`,對(duì)于對(duì)象類型數(shù)據(jù)ref借助了reactive
watch(person,(newValue,oldValue)=>{
console.log('person變化了',newValue,oldValue)
},{immediate:true,deep:false}) //此處的deep配置不再奏效
//情況四:監(jiān)視r(shí)eactive定義的響應(yīng)式數(shù)據(jù)中的某個(gè)屬性
watch(()=>person.job,(newValue,oldValue)=>{
console.log('person的job變化了',newValue,oldValue)
},{immediate:true,deep:true})
//情況五:監(jiān)視r(shí)eactive定義的響應(yīng)式數(shù)據(jù)中的某些屬性
watch([()=>person.job,()=>person.name],(newValue,oldValue)=>{
console.log('person的job變化了',newValue,oldValue)
},{immediate:true,deep:true})
//特殊情況
// 監(jiān)視r(shí)eactive所定義的對(duì)象中的某個(gè)屬性,且該屬性也為對(duì)象類型
watch(()=>person.job,(newValue,oldValue)=>{
console.log('person的job變化了',newValue,oldValue)
},{deep:true}) //此處由于監(jiān)視的是reactive所定義的對(duì)象中的某個(gè)屬性,所以deep配置有效
- 對(duì)于使用ref函數(shù)定義的數(shù)據(jù),如果為基本數(shù)據(jù)類型,則使用watch監(jiān)視時(shí),不使用
變量.value
的形式,因?yàn)椴荒鼙O(jiān)視一個(gè)字面量,監(jiān)視的需要為一個(gè)存放數(shù)據(jù)的結(jié)構(gòu);如果為對(duì)象類型,此時(shí)value的值為Proxy對(duì)象,如果沒有開啟深度監(jiān)視,只要value對(duì)應(yīng)的對(duì)象的引用沒有改變相當(dāng)于值沒有修改,解決方法:- 開啟深度監(jiān)視,監(jiān)視Proxy對(duì)象中屬性的變化
- 監(jiān)視
變量.value
,由于Proxy對(duì)象時(shí)ref函數(shù)借助reactive函數(shù)生成的,監(jiān)視變量.value
相當(dāng)于監(jiān)視r(shí)eactive函數(shù)生成的Proxy對(duì)象
?? watchEffect函數(shù)
- watch的套路是:既要指明監(jiān)視的屬性,也要指明監(jiān)視的回調(diào)。
- watchEffect的套路是:不用指明監(jiān)視哪個(gè)屬性,監(jiān)視的回調(diào)中用到哪個(gè)屬性,那就監(jiān)視哪個(gè)屬性。
- watchEffect有點(diǎn)像computed:
- 兩個(gè)函數(shù)中所依賴的數(shù)據(jù)發(fā)生變化,都會(huì)重新執(zhí)行一次回調(diào)。
- 但computed注重的計(jì)算出來的值(回調(diào)函數(shù)的返回值),所以必須要寫返回值。
- 而watchEffect更注重的是過程(回調(diào)函數(shù)的函數(shù)體),所以不用寫返回值。
//watchEffect所指定的回調(diào)中用到的數(shù)據(jù)只要發(fā)生變化,則直接重新執(zhí)行回調(diào)。 watchEffect(()=>{ const x1 = sum.value const x2 = person.age console.log('watchEffect配置的回調(diào)執(zhí)行了') })
?? 生命周期
-
Vue3.0中可以繼續(xù)使用Vue2.x中的生命周期鉤子,但有有兩個(gè)被更名:
-
beforeDestroy
改名為beforeUnmount
-
destroyed
改名為unmounted
App.vue
<template> <button @click="isShowDemo = !isShowDemo">切換隱藏/顯示</button> <Demo v-if="isShowDemo"/> </template> <script> import {ref} from 'vue' import Demo from './components/Demo' export default { name: 'App', components:{Demo}, setup() { let isShowDemo = ref(true) return {isShowDemo} } } </script>
Demo.vue
<template> <h2>當(dāng)前求和為:{{sum}}</h2> <button @click="sum++">點(diǎn)我+1</button> </template> <script> import { ref } from 'vue' export default { name: 'Demo', setup() { //數(shù)據(jù) let sum = ref(0) //返回一個(gè)對(duì)象(常用) return { sum } }, //通過配置項(xiàng)的形式使用生命周期鉤子 //#region beforeCreate() { console.log('---beforeCreate---') }, created() { console.log('---created---') }, beforeMount() { console.log('---beforeMount---') }, mounted() { console.log('---mounted---') }, beforeUpdate() { console.log('---beforeUpdate---') }, updated() { console.log('---updated---') }, beforeUnmount() { console.log('---beforeUnmount---') }, unmounted() { console.log('---unmounted---') } //#endregion } </script>
-
-
Vue3.0也提供了 Composition API 形式的生命周期鉤子,與Vue2.x中鉤子對(duì)應(yīng)關(guān)系如下:
-
beforeCreate
===>setup()
-
created
=======>setup()
-
beforeCreate
與created
沒有對(duì)應(yīng)組合式API,setup()
相當(dāng)于beforeCreate
與created
,對(duì)于配置項(xiàng)setup()
的執(zhí)行時(shí)機(jī)早于配置項(xiàng)beforeCreate
與created
。 -
beforeMount
===>onBeforeMount
-
mounted
=======>onMounted
-
beforeUpdate
===>onBeforeUpdate
-
updated
=======>onUpdated
-
beforeUnmount
==>onBeforeUnmount
-
unmounted
=====>onUnmounted
-
如果組合式API的生命周期與配置項(xiàng)形式的生命周期一起寫,組合式API的生命周期的執(zhí)行時(shí)機(jī)優(yōu)先于配置項(xiàng)形式的生命周期。
-
一般情況下統(tǒng)一使用組合式API的生命周期,或者統(tǒng)一使用配置項(xiàng)形式的生命周期
<template> <h2>當(dāng)前求和為:{{sum}}</h2> <button @click="sum++">點(diǎn)我+1</button> </template> <script> import {ref,onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted} from 'vue' export default { name: 'Demo', setup(){ console.log('---setup---') //數(shù)據(jù) let sum = ref(0) //通過組合式API的形式去使用生命周期鉤子 onBeforeMount(()=>{ console.log('---onBeforeMount---') }) onMounted(()=>{ console.log('---onMounted---') }) onBeforeUpdate(()=>{ console.log('---onBeforeUpdate---') }) onUpdated(()=>{ console.log('---onUpdated---') }) onBeforeUnmount(()=>{ console.log('---onBeforeUnmount---') }) onUnmounted(()=>{ console.log('---onUnmounted---') }) //返回一個(gè)對(duì)象(常用) return {sum} }, //通過配置項(xiàng)的形式使用生命周期鉤子 //#region beforeCreate() { console.log('---beforeCreate---') }, created() { console.log('---created---') }, beforeMount() { console.log('---beforeMount---') }, mounted() { console.log('---mounted---') }, beforeUpdate(){ console.log('---beforeUpdate---') }, updated() { console.log('---updated---') }, beforeUnmount() { console.log('---beforeUnmount---') }, unmounted() { console.log('---unmounted---') }, //#endregion } </script>
-
?? 自定義hook函數(shù)
-
什么是hook?—— 本質(zhì)是一個(gè)函數(shù),把setup函數(shù)中使用的Composition API進(jìn)行了封裝。
-
類似于vue2.x中的mixin。
-
自定義hook的優(yōu)勢(shì): 復(fù)用代碼, 讓setup中的邏輯更清楚易懂。
src\hooks\usePoint.js
import {reactive,onMounted,onBeforeUnmount} from 'vue'
export default function() {
//實(shí)現(xiàn)鼠標(biāo)“打點(diǎn)”相關(guān)的數(shù)據(jù)
let point = reactive({
x:0,
y:0
})
//實(shí)現(xiàn)鼠標(biāo)“打點(diǎn)”相關(guān)的方法
function savePoint(event){
point.x = event.pageX
point.y = event.pageY
console.log(event.pageX,event.pageY)
}
//實(shí)現(xiàn)鼠標(biāo)“打點(diǎn)”相關(guān)的生命周期鉤子
onMounted(()=>{
window.addEventListener('click',savePoint)
})
onBeforeUnmount(()=>{
window.removeEventListener('click',savePoint)
})
return point
}
Demo.vue
<template>
<h2>當(dāng)前求和為:{{sum}}</h2>
<button @click="sum++">點(diǎn)我+1</button>
<hr>
<h2>當(dāng)前點(diǎn)擊時(shí)鼠標(biāo)的坐標(biāo)為:x:{{point.x}},y:{{point.y}}</h2>
</template>
<script>
import {ref} from 'vue'
import usePoint from '../hooks/usePoint'
export default {
name: 'Demo',
setup(){
//數(shù)據(jù)
let sum = ref(0)
let point = usePoint()
//返回一個(gè)對(duì)象(常用)
return {sum,point}
}
}
</script>
?? toRef
- 作用:創(chuàng)建一個(gè) ref 對(duì)象,其value值指向另一個(gè)對(duì)象中的某個(gè)屬性。
- 語法:
const name = toRef(person,'name')
- 應(yīng)用: 要將響應(yīng)式對(duì)象中的某個(gè)屬性單獨(dú)提供給外部使用時(shí)。
- 擴(kuò)展:
toRefs
與toRef
功能一致,但可以批量創(chuàng)建多個(gè) ref 對(duì)象,創(chuàng)建一個(gè)對(duì)象中所有屬性對(duì)應(yīng)的 ref 對(duì)象,語法:toRefs(person)
<template>
<h4>{{person}}</h4>
<h2>姓名:{{name}}</h2>
<h2>年齡:{{age}}</h2>
<h2>薪資:{{job.j1.salary}}K</h2>
<button @click="name+='~'">修改姓名</button>
<button @click="age++">增長年齡</button>
<button @click="job.j1.salary++">漲薪</button>
</template>
<script>
import {ref,reactive,toRef,toRefs} from 'vue'
export default {
name: 'Demo',
setup(){
//數(shù)據(jù)
let person = reactive({
name:'張三',
age:18,
job:{
j1:{
salary:20
}
}
})
// const name1 = person.name
// console.log('%%%',name1)
// const name2 = toRef(person,'name')
// console.log('####',name2)
const x = toRefs(person)
console.log('******',x)
//返回一個(gè)對(duì)象(常用)
return {
person,
// name:toRef(person,'name'),
// age:toRef(person,'age'),
// salary:toRef(person.job.j1,'salary'),
...toRefs(person)
}
}
}
</script>
?? shallowReactive 與 shallowRef
- shallowReactive:只處理對(duì)象最外層屬性的響應(yīng)式(淺響應(yīng)式)。
- shallowRef:只處理基本數(shù)據(jù)類型的響應(yīng)式, 不進(jìn)行對(duì)象的響應(yīng)式處理,對(duì)于對(duì)象類型數(shù)據(jù)直接將該對(duì)象作為ref對(duì)象的value的值。
- 什么時(shí)候使用?
- 如果有一個(gè)對(duì)象數(shù)據(jù),結(jié)構(gòu)比較深, 但變化時(shí)只是最外層屬性變化 ===> shallowReactive。
- 如果有一個(gè)對(duì)象數(shù)據(jù),后續(xù)功能不會(huì)修改該對(duì)象中的屬性,而是生成新的對(duì)象來替換,即對(duì)于該對(duì)象數(shù)據(jù)在后續(xù)功能中不會(huì)修改其屬性,而是會(huì)將該對(duì)象整個(gè)進(jìn)行替換 ===> shallowRef。
?? readonly 與 shallowReadonly
- readonly 與 shallowReadonly 均接收一個(gè)響應(yīng)式數(shù)據(jù)為參數(shù)。
- readonly: 讓一個(gè)響應(yīng)式數(shù)據(jù)變?yōu)橹蛔x的(深只讀),傳入的響應(yīng)式數(shù)據(jù)不管有幾層,都不能進(jìn)行修改。
- shallowReadonly:讓一個(gè)響應(yīng)式數(shù)據(jù)變?yōu)橹蛔x的(淺只讀),傳入的響應(yīng)式數(shù)據(jù)只有最外層數(shù)據(jù)不能進(jìn)行修改。
- 應(yīng)用場景: 不希望數(shù)據(jù)被修改時(shí)。將數(shù)據(jù)交給其他組件并且不希望這個(gè)組件對(duì)數(shù)據(jù)進(jìn)行更改。
?? toRaw 與 markRaw
- toRaw:
- toRaw 接收一個(gè)響應(yīng)式對(duì)象為參數(shù),只能接收reactive生成的響應(yīng)式對(duì)象,不能處理ref生成的響應(yīng)式數(shù)據(jù)
- 作用:將一個(gè)由
reactive
生成的響應(yīng)式對(duì)象轉(zhuǎn)為普通對(duì)象。 - 使用場景:用于讀取響應(yīng)式對(duì)象對(duì)應(yīng)的普通對(duì)象,對(duì)這個(gè)普通對(duì)象的所有操作,不會(huì)引起頁面更新。
- markRaw:
- 接收一個(gè)對(duì)象類型數(shù)據(jù)為參數(shù)
- 作用:標(biāo)記一個(gè)對(duì)象,使其永遠(yuǎn)不會(huì)再成為響應(yīng)式對(duì)象。向一個(gè)已經(jīng)是響應(yīng)式對(duì)象的數(shù)據(jù)追加一個(gè)屬性,該屬性的值為對(duì)象類型數(shù)據(jù),vue會(huì)為其自動(dòng)添加響應(yīng)式,當(dāng)不希望該屬性的值為響應(yīng)式時(shí)可以使用該函數(shù),減小開銷。
- 應(yīng)用場景:
- 有些值不應(yīng)被設(shè)置為響應(yīng)式的,例如復(fù)雜的第三方類庫等,如果向響應(yīng)式對(duì)象追加一個(gè)第三方類庫對(duì)象(一般屬性多且層次多),開銷會(huì)很大。
- 當(dāng)渲染具有不可變數(shù)據(jù)源的大列表時(shí),跳過響應(yīng)式轉(zhuǎn)換可以提高性能。
?? customRef
-
作用:創(chuàng)建一個(gè)自定義的 ref,并對(duì)其依賴項(xiàng)跟蹤和更新觸發(fā)進(jìn)行顯式控制。
-
實(shí)現(xiàn)防抖效果:
<template>
<input type="text" v-model="keyword">
<h3>{{keyword}}</h3>
</template>
<script>
import {ref,customRef} from 'vue'
export default {
name:'Demo',
setup(){
// let keyword = ref('hello') //使用Vue準(zhǔn)備好的內(nèi)置ref
//自定義一個(gè)myRef
function myRef(value,delay){
let timer
//通過customRef去實(shí)現(xiàn)自定義
return customRef((track,trigger)=>{
return{
get(){ // 讀取數(shù)據(jù)時(shí)調(diào)用
track() //告訴Vue這個(gè)value值是需要被“追蹤”的,告訴vue追蹤數(shù)據(jù)的變化
return value
},
set(newValue){ // 修改數(shù)據(jù)時(shí)調(diào)用
clearTimeout(timer) // 觸發(fā)就清除原先的定時(shí)器不執(zhí)行之前定時(shí)器的回調(diào)
timer = setTimeout(()=>{
value = newValue // 修改數(shù)據(jù)
trigger() //告訴Vue去更新界面,重新解析模板
},delay)
}
}
})
}
let keyword = myRef('hello',500) //使用程序員自定義的ref
return {
keyword
}
}
}
</script>
?? provide 與 inject
-
作用:實(shí)現(xiàn)祖與后代組件間通信
-
套路:父組件有一個(gè)
provide
選項(xiàng)來提供數(shù)據(jù),后代組件有一個(gè)inject
選項(xiàng)來開始使用這些數(shù)據(jù) -
具體寫法:
-
祖組件中:
setup(){ ...... let car = reactive({name:'奔馳',price:'40萬'}) // 給后代組件傳遞數(shù)據(jù) // 第一個(gè)參數(shù)為對(duì)傳遞數(shù)據(jù)的命名,第二個(gè)參數(shù)為傳遞的數(shù)據(jù) provide('car',car) ...... }
-
后代組件中:
setup(props,context){ ...... // 獲取祖組件傳遞過來命名為car的數(shù)據(jù) const car = inject('car') return {car} ...... }
-
?? 響應(yīng)式數(shù)據(jù)的判斷
- isRef: 檢查一個(gè)值是否為一個(gè) ref 對(duì)象
- isReactive: 檢查一個(gè)對(duì)象是否是由
reactive
創(chuàng)建的響應(yīng)式代理 - isReadonly: 檢查一個(gè)對(duì)象是否是由
readonly
創(chuàng)建的只讀代理 - isProxy: 檢查一個(gè)對(duì)象是否是由
reactive
或者readonly
方法創(chuàng)建的代理
?? Composition API 的優(yōu)勢(shì)
?? Options API (配置式API)存在的問題
使用傳統(tǒng)OptionsAPI中,新增或者修改一個(gè)需求,就需要分別在data,methods,computed里修改 。同一個(gè)需求的數(shù)據(jù)、方法等較分散不集中。
?? Composition API (組合式API)的優(yōu)勢(shì)
我們可以更加優(yōu)雅的組織我們的代碼,函數(shù)。讓相關(guān)功能的代碼更加有序的組織在一起。
?? Fragment
-
在Vue2中: 組件必須有一個(gè)根標(biāo)簽
-
在Vue3中: 組件可以沒有根標(biāo)簽, 內(nèi)部會(huì)將多個(gè)標(biāo)簽包含在一個(gè)Fragment虛擬元素中
-
好處: 減少標(biāo)簽層級(jí), 減小內(nèi)存占用
?? Teleport
-
什么是Teleport?——
Teleport
是一種能夠?qū)⑽覀兊?strong>組件html結(jié)構(gòu)移動(dòng)到指定位置的技術(shù)。<!-- 移動(dòng)位置可以標(biāo)簽元素或標(biāo)簽選擇器 --> <teleport to="移動(dòng)位置"> <div v-if="isShow" class="mask"> <div class="dialog"> <h3>我是一個(gè)彈窗</h3> <button @click="isShow = false">關(guān)閉彈窗</button> </div> </div> </teleport>
<template>
<div>
<button @click="isShow = true">點(diǎn)我彈個(gè)窗</button>
<!-- 將html結(jié)構(gòu)移動(dòng)到body中 -->
<teleport to="body">
<div v-if="isShow" class="mask">
<div class="dialog">
<h3>我是一個(gè)彈窗</h3>
<h4>一些內(nèi)容</h4>
<h4>一些內(nèi)容</h4>
<h4>一些內(nèi)容</h4>
<button @click="isShow = false">關(guān)閉彈窗</button>
</div>
</div>
</teleport>
</div>
</template>
<script>
import {ref} from 'vue'
export default {
name:'Dialog',
setup(){
let isShow = ref(false)
return {isShow}
}
}
</script>
<style>
.mask{
position: absolute;
top: 0;bottom: 0;left: 0;right: 0;
background-color: rgba(0, 0, 0, 0.5);
}
.dialog{
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
text-align: center;
width: 300px;
height: 300px;
background-color: green;
}
</style>
?? Suspense
// 靜態(tài)引入只要組件不引入成功,當(dāng)前整個(gè)組件都不會(huì)進(jìn)行顯示
// import Child from './components/Child'//靜態(tài)引入
// 異步引入引入的組件等加載完成再進(jìn)行顯示,當(dāng)前組件可以不用等待引入成功即可先進(jìn)行顯示
import {defineAsyncComponent} from 'vue'
const Child = defineAsyncComponent(()=>import('./components/Child')) //異步引入
-
當(dāng)異步引入的組件引入完成,組件會(huì)突然顯示,造成用戶體驗(yàn)效果不佳
-
Suspense 標(biāo)簽可以在等待異步組件時(shí)渲染一些額外內(nèi)容,讓應(yīng)用有更好的用戶體驗(yàn)
-
如果組件為異步引入且使用了Suspense,則組件的setup可以返回Promise實(shí)例對(duì)象,即當(dāng)Suspense和異步組件配合時(shí),setup可以是一個(gè)async函數(shù)。
-
使用步驟:
-
異步引入組件
import {defineAsyncComponent} from 'vue' const Child = defineAsyncComponent(()=>import('./components/Child.vue'))
-
使用
Suspense
包裹組件,并配置好default
與fallback
<template> <div class="app"> <h3>我是App組件</h3> <Suspense> <!-- 用于放置真正需要顯示的組件 --> <template v-slot:default> <Child/> </template> <!-- 用于放置等待異步組件時(shí)渲染的內(nèi)容 --> <template v-slot:fallback> <h3>加載中.....</h3> </template> </Suspense> </div> </template>
-
?? Vue3全局API的轉(zhuǎn)移
-
Vue 2.x 有許多全局 API 和配置。
-
例如:注冊(cè)全局組件、注冊(cè)全局指令等。
//注冊(cè)全局組件 Vue.component('MyButton', { data: () => ({ count: 0 }), template: '<button @click="count++">Clicked {{ count }} times.</button>' }) //注冊(cè)全局指令 Vue.directive('focus', { inserted: el => el.focus() }
-
-
Vue3.0中對(duì)這些API做出了調(diào)整:
-
將全局的API,即:
Vue.xxx
調(diào)整到應(yīng)用實(shí)例(app
)上2.x 全局 API( Vue
)3.x 實(shí)例 API ( app
)Vue.config.xxxx app.config.xxxx Vue.config.productionTip 移除 Vue.component app.component Vue.directive app.directive Vue.mixin app.mixin Vue.use app.use Vue.prototype app.config.globalProperties
-
?? Vue3其他改變
-
data選項(xiàng)應(yīng)始終被聲明為一個(gè)函數(shù)。
-
過度類名的更改:
-
Vue2.x寫法
.v-enter, .v-leave-to { opacity: 0; } .v-leave, .v-enter-to { opacity: 1; }
-
Vue3.x寫法
.v-enter-from, .v-leave-to { opacity: 0; } .v-leave-from, .v-enter-to { opacity: 1; }
-
-
移除keyCode作為 v-on 的修飾符,同時(shí)也不再支持
config.keyCodes
,不支持自定義按鍵別名。 -
移除
v-on.native
修飾符-
父組件中綁定事件
<my-component v-on:close="handleComponentEvent" v-on:click="handleNativeClickEvent" />
-
子組件中聲明自定義事件
<script> export default { emits: ['close'] // 子組件接收的事件為自定義事件,沒有接收的事件為原生事件 } </script>
-
-
移除過濾器(filter)
過濾器雖然這看起來很方便,但它需要一個(gè)自定義語法,打破大括號(hào)內(nèi)表達(dá)式是 “只是 JavaScript” 的假設(shè),這不僅有學(xué)習(xí)成本,而且有實(shí)現(xiàn)成本!建議用方法調(diào)用或計(jì)算屬性去替換過濾器。文章來源:http://www.zghlxwxcb.cn/news/detail-790916.html
-
…文章來源地址http://www.zghlxwxcb.cn/news/detail-790916.html
到了這里,關(guān)于[Vue]Vue3學(xué)習(xí)筆記(尚硅谷)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!