使用 vite 创建 Vue 3.x 工程
创建工程
npm init vite-app <project-name>
进入工程目录
cd <project-name>
安装依赖
npm install
## or
yarn
运行
npm run dev
## or
yarn dev
与 Vue 2.x 的一些不同之处
main.js 中
-
引入的不再是 Vue 构造函数,引入的是一个名为 createApp 的工厂函数。
import { createApp } from 'vue' import App from './App'
-
创建应用实例对象 —— app
const app = createApp(App)
app 类似于之前的 vm,但比 vm 更 “轻”。
-
挂载应用实例对象
app.mount('#app')
-
简写
// 创建实例并挂载 createApp(App).mount('#app')
组件中
-
vue3.x 组件中的模板结构,可以没有根标签。
-
新增 setup 配置项。
-
当父组件给子组件传递了参数,但子组件没有使用 props 配置项接收,会出现警告。
- 当父组件绑定了自定义事件,但子组件没有使用 emits 配置项接收,会出现警告。
组合式 Composition API
setup 函数
vue 3 中的一个新配置项,是一个函数。
-
组件中所用到的属性、方法等,都要配置在 setup 中。
-
setup 函数的两种返回值:
-
若返回一个
对象
,则对象中的属性和方法,在模板中可以直接使用。(重点掌握)export default { setup() { // 属性 let name = '张三' let age = 18 // 方法 function sayHello() { alert(`我叫${name},我${age}岁了,你好啊!`) } // 返回一个对象(常用) return { name, age, sayHello, } } }
-
若返回一个
渲染函数
,则可以直接渲染模板。(了解)import { h } from 'vue' export default { ... setup() { // 返回一个函数(渲染函数) return () => h('h1', '尚硅谷') } }
-
-
尽量不要与 vue 2 配置混用。
- vue 2.x 配置(data,methods,computed …)中
可以访问
setup 中的属性和方法。 - setup 配置中,
无法访问
vue 2.x 配置中的 (data,methods,computed …) - 如有重名,以 setup 配置项优先。
- vue 2.x 配置(data,methods,computed …)中
-
setup 不能是一个
async
函数- 因为被
async
修饰的函数,返回值不再是普通的对象,而是Promise
对象,会导致模板中获取不到 return 对象中的属性和方法。 - 如需要返回一个 Promice 对象实例,则需要 suspense 和异步组件配合。
- 因为被
ref() 函数
接受一个内部值,返回一个响应式的、可更改的 ref 对象,此对象只有一个指向其内部值的属性
.value
。
作用
- 定义
响应式
变量。
语法
const name = ref('张三') // 创建一个 ref 实例对象,它的 .value 值为 '张三'
const age = ref(18) // 创建一个 ref 实例对象,它的 .value 值为 18
详细信息
- 被 ref 包装后的对象,是一个
RefImpl
实例对象,它是一个包含响应式
数据(value
)的引用对象
( 简称 ref 对象 )。
- 在
RefImpl
对象的原型对象
上有value
的get
和set
方法。
- ref 对象是可更改的,也就是说可以为
.value
赋予新的值。 - ref 对象是响应式的,即所有对
.value
的操作都将被追踪。
ref 对象接收数据的类型
接收的数据可以是 基本类型
,也可以是 对象类型
。
-
基本类型
的数据,响应式原理依靠Object.defineProperty()
的get
和set
完成。 -
对象类型
的数据,内部 求助 了 vue 3.x 中的一个新函数 ——reactive
函数。
将 基本类型
赋值给 ref
读取数据
name.value // '张三'
age.value // 18
<template>
<h2>姓名:{{ name }}</h2>
<h2>年龄:{{ age }}</h2>
</template>
修改数据
name.value = '李四'
age.value = 48
将 对象
赋值给 ref
这个对象将通过 reactive() 转为具有
深层次
响应式的对象。
let job = ref({
type: '前端工程师',
salary: '30k',
})
.value
的值是一个 Proxy(Object) 对象。
job:
job.value:
读取数据
job.value.type // '前端工程师'
job.value.salary // '30k'
<template>
<h3>工作种类:{{ job.type }}</h3>
<h3>工作薪水:{{ job.salary }}</h3>
</template>
修改数据
job.value.type = 'UI设计师'
job.value.salary = '15k'
reactive() 函数
定义一个
对象类型
的响应式数据。(基本类型
用ref
函数 )
-
不能将
基本类型
赋值给 reactive 函数。const number = reactive(666) // 报错 value cannot be made reactive: 666
-
这个对象将通过 reactive() 转为具有
深层次
响应式的对象。 -
内部基于
ES6
的Proxy
实现,通过代理对象
操作源对象
内部数据。
引入 reactive
import { reactive } from 'vue'
Object 类型
定义数据
const job = reactive({
type: '前端工程师',
salary: '30k',
a: {
b: {
c: 666,
}
}
})
job:
读取数据
job.type
job.salary
job.a.b.c
<h3>工作种类:{{ job.type }}</h3>
<h3>工作薪水:{{ job.salary }}</h3>
<h3>测试数据:{{ job.a.b.c }}</h3>
修改数据
job.type = 'UI 设计师'
job.salary = '15k'
job.a.b.c = 888
Array 类型
数组中的每一个数据都是
响应式
的。
定义数据
// Array类型
const hobby = reactive(['抽烟', '喝酒', '烫头'])
hobby:
读取数据
hobby[0] // '抽烟'
<h3>爱好:{{ hobby }}</h3>
修改数据
hobby[0] = '学习'
基本数据类型
可以将基本数据类型放在对象中,再赋值给 reactive 函数。
定义数据
const person = reactive({
name: '张三',
age: 18,
job: {
type: '前端工程师',
salary: '30k',
a: {
b: {
c: 666,
},
},
},
hobby: ['抽烟', '喝酒', '烫头'],
})
person:
读取数据
<h2>姓名:{{ person.name }}</h2>
<h2>年龄:{{ person.age }}</h2>
<h3>工作种类:{{ person.job.type }}</h3>
<h3>工作薪水:{{ person.job.salary }}</h3>
<h3>测试数据:{{ person.job.a.b.c }}</h3>
<h3>爱好:{{ person.hobby }}</h3>
修改数据
function changeInfo() {
person.name = '李四'
person.age = 48
person.job.type = 'UI 设计师'
person.job.salary = '15k'
person.job.a.b.c = 888
person.hobby[0] = '学习'
}
vue 3.x 中的响应式原理
vue 2.x 中的响应式
实现原理
Object 类型
- 通过 Object.defineProperty() 对属性的读取、修改进行拦截(数据劫持)。
Array 类型
- 通过重写更新数组的一系列方法,来实现拦截。
- 对数组的变更方法进行了包装。
存在问题
export default {
data() {
return {
person: {
name: '张三',
age: 18,
hobby: ['抽烟', '喝酒', '烫头']
}
}
}
}
-
新增、删除属性时,界面不会更新。
-
新增属性
-
现象
console.log(this.person.sex) // undefined this.person.sex = '女' console.log(this.person.sex) // '女',说明属性已添加,但是视图未更新。
-
解决方案
// $set() 向某个数据对象中,追加一个响应式属性。 this.$set(this.person, 'sex', '女')
-
-
删除属性
-
现象
console.log(this.person.name) // '张三' delete this.person.name console.log(this.person.name) // undefined,说明属性已被删除,但是视图未更新。
-
解决方案
// $delete() 从某个数据对象中,移除一个响应式属性。 this.$delete(this.person, 'name')
-
-
-
直接通过
下标
修改数组元素,界面不会更新。-
现象
console.log(this.person.hobby[0]) // '抽烟' this.person.hobby[0] = '学习' console.log(this.person.hobby[0]) // '学习',数组元素值已经修改,但是视图未更新。
-
解放方案
this.$set(this.person.hobby,0,'学习') // or this.person.hobby.splice(0,1,'学习')
-
模拟 vue 2.x 实现响应式
// 源对象
const person = {
name: '张三',
age: 18,
hobby: ['抽烟', '喝酒', '烫头'],
}
// 模拟 vue2 中实现响应式
let p = {} // 代理对象
Object.defineProperty(p,'name',{
configurable:true,
get(){ // 有人读取 name 时调用
console.log('name属性值被读取了。')
return person.name
},
set(value){ // 有人修改 name 时调用
console.log('name属性值被修改了,即将更新视图。')
person.name = value
}
})
vue 3.x 中的响应式
实现原理
window.Proxy() 代理
- 通过
Proxy
拦截对象中,任意属性的变化。- 读取属性值。
- 修改属性值。
- 添加属性并赋值。
- 删除属性。
window.Reflect() 反射
- 通过
Reflect
对源对象(被代理对象 target)
的属性进行 CRUD 操作。
模拟 vue 3.x 实现响应式
// 源对象
const person = {
name: '张三',
age: 18,
hobby: ['抽烟', '喝酒', '烫头'],
}
// 模拟 vue3 中实现响应式
const p = new Proxy(person, {
// 读取:当读取p的某个属性时被调用。
get(target, propName) {
console.log(`有人读取了 p 身上的 ${propName} 属性。`)
return Reflect.get(target,propName)
},
// 新增、修改:当修改p的某个属性时被调用,或给p追加属性时被调用。
set(target, propName, newValue) {
console.log(`有人修改了 p 身上的 ${propName} 属性,即将更新视图。`)
Reflect.set(target,propName,newValue)
},
// 删除:当移除p的某个属性时被调用。
deleteProperty(target, propName) {
console.log(`有人删除了 p 身上的 ${propName} 属性,即将更新视图。`)
return Reflect.deleteProperty(target,propName)
},
})
ref () 与 reactive () 对比
定义数据
ref
:基本
类型。reactive
:对象或数组
类型。ref
也可以用来定义对象(或数组)
类型数据,它内部会自动通过reactive
转为 代理对象。
原理
ref
通过Object.defineProperty( )
的get
与set
来实现响应式(数据劫持)。reactive
通过使用Proxy
实现响应式(数据劫持),并通过Reflect
操作源对象
内部的数据。
使用
ref
定义的数据,操作数据需要.value
,读取数据时模板中直接读取不需要.value
。reactive
定义的数据,操作数据时,均不需要.value
。
setup 两个注意点
setup 执行的时机
- 在
beforeCreate
之前执行一次,this
是undefined
。
setup 的参数
props
- 值为对象,包含组件外部传来,且组件内部
props
配置中,声明接收了的属性。
context
attr
值为对象,包含组件外部传来,但没有在props
配置中声明的属性。emit
分发自定义事件的函数,相当于this.$emit
。slots
收到的插槽内容,相当于this.$slots
。
computed() 函数
引入
import {computed} from 'vue'
创建一个只读的计算属性
setup(){
...
// 计算属性 - 简写(只读)
const fullName = computed(()=>{
return person.firstName + '-' + person.lastName
})
return { fullName }
}
创建一个可写的计算属性Ò
setup(){
...
// 计算属性 - 完整(可写)
const fullName = computed({
get(){
return person.firstName + '-' + person.lastName
},
set(value){
const nameList = value.split('-')
person.firstName = nameList[0]
person.lastName = nameList[1]
}
})
return { fullName }
}
watch() 函数
引入
import {watch} from 'vue'
情况一
监视 ref 定义的一个响应式数据。
sum = ref(0)
watch(sum,(newValue,oldValue)=>{
console.log('sum的值变化了。',newValue,oldValue)
},{immediate:true})
注意:要监视 sum,不要监视 sum.value。
情况二
监视多个 ref 定义的响应式数据。
-
分别监视
sum = ref(0) msg = ref('你好') watch(sum,(newValue, oldValue)=>{ console.log('sum的值变化了。', newValue, oldValue) }) watch(msg,(newValue, oldValue)=>{ console.log('sum的值变化了。', newValue, oldValue) })
-
整体监视
watch([sum,msg],(newValue,oldValue)=>{ console.log('sum的值变化了。', newValue, oldValue) })
备注:此时 newValue 和 oldValue 都是数组。newValue[0] 为 sum 的 newValue,newValue[1] 为 msg 的 newValue,oldValue[0] 为 sum 的 oldValue,oldValue[1] 为 msg 的 oldValue。
情况三
监视 reactive 定义的响应式数据。
- 若 watch 监视的是 reactive 定义的响应式数据,则无法正确获得oldValue
- 若 watch 监视的是r eactive 定义的响应式数据,则强制开启了深度监视 ,无法关闭。
const person = reactive({
name: '小仙女',
age: 18,
job:{
j1:{
salary:20
}
}
})
watch(person,(newValue, oldValue)=>{
console.log('person变化了。', newValue, oldValue) // oldValue的值无法正确获取,等于 newValue。
},{immediate:true,deep:false}) // 此处的 deep 配置不生效。
备注:监视的是 reactive 定义的响应式数据本身,deep 配置不生效。
情况四
监视 reactive 定义的响应式数据中的某个属性。
- 监视某个属性,则需要使用
()=>person.job
的方式。
watch(()=>person.name,(newValue, oldValue)=>{
console.log('person的name变化了。', newValue, oldValue)
})
watch(
() => person.job,
(newValue, oldValue) => {
console.log('person.name变化了。', newValue, oldValue)
},
{ deep: true } // 此种写法 deep 配置生效。
)
watch(
person.job,
(newValue, oldValue) => {
console.log('person.name变化了。', newValue, oldValue)
},
{ deep: false } // 此种写法 deep 强制开启,无法关闭。
)
情况五
监视 reactive 定义的响应式数据中的某些属性。
写法一:此种写法 deep 配置生效。()=>person.job
watch([()=>person.job,()=>person.name],(newValue, oldValue)=>{
console.log('person的job或name变化了。', newValue, oldValue)
},{immediate:true,deep:true}) // 此处的 deep 配置生效。
备注:监视的是 reactive
定义的对象中的某个属性
,而这个属性是一个对象,所以 deep 配置生效。
写法二:此种写法 deep 属性强制开启,无法关闭。person.job
watch(
[person.job, () => person.name],
(newValue, oldValue) => {
console.log('person的job或name变化了。', newValue, oldValue)
},
{ immediate: true, deep: false }
) // 此处的 deep 配置不生效。
watchEffect() 函数
与 watch() 对比
- watch() 函数:既要指明监视的对象,也要指明监视的回调。
- watchEffect() 函数:无需指明监视哪个对象,需要指明监视的回调。回调中用到哪个对象,就监视哪个对象。
与 computed() 对比
- computed() 注重计算出来的值(回调函数的返回值),所以必须要写返回值。
- watchEffect() 注重过程(回调函数的函数体),所以无需写返回值。
导入
import {watchEffect} from 'vue'
使用
// watchEffect监视回调函数中应用到的数据,只要发生变化,则执行回调。
watchEffect(()=>{
const x1 = sum.value
const x2 = person.age
console.log('watchEffect配置的回调执行了。')
})
生命周期函数
生命周期选项
-
beforeCreate
-
created
-
beforeMount
-
mounted
-
beforeUpdate
-
updated
-
beforeUmount
( vue 2.x 中的beforeDestroy
) -
umounted
( vue 2.x 中的destroyed
)
Composition API 形式的生命周期钩子
setup()
——beforeCreate
setup()
——created
onBeforeMount
——beforeMount
onMounted
——mounted
onBeforeUpdate
——beforeUpdate
onUpdated
——updated
onBeforeUnmount
——beforeUnmount
onUnmounted
——unmounted
自定义 hook() 函数
什么是 hook
- 本质是一个函数。
- 把 setup 函数中使用的 Composition API 进行封装。
- 类似于 vue 2.x 中的 mixin。
作用
- 复用代码,让 setup 中的逻辑更清晰。
补充知识
#region
在 vscode 中,#region 和 #endregion 包裹的代码块,可以自定义折叠。