一、创建Vue3.0工程
1. 使用 vue-cli 创建
// 查看 @vue/cli 版本,确保在 4.5.0 以上
vue -V
// 创建
vue create vue3_test
2. 使用 vite 创建
npm init vite-app <project-name>
二、分析工程结构
1. main.js
2. App.vue
- vue3 组件中的模板结构可以没有根标签
三、常用的 Composition Api
1. 拉开序幕的 setup
- 理解:Vue3.0 中的一个新的配置项,值为一个函数
setup
是所有Composition Api(组合Api)
"表演的舞台"- 组件中用到的:数据、方法等,都要配置在
setup
中 setup
函数的两种返回值:若返回一个对象,则对象中的属性、方法,在模板中均可直接使用!!!
- 若返回一个渲染函数:则可以自定义渲染内容(了解)
注意点:
尽量不要与 vue2 配置混用
vue2 配置(data、methosd、computed...)中
可以访问到
setup中的属性、方法但是在 setup中
不能访问到
vue2 配置(data、methos、computed...)如果有重名,
setup
优先
2. setup不能是一个 async 函数,因为返回值不在是 return 的对象,而是 promise,模板看不到 return 对象中的属性
2. ref函数
作用:定义一个响应式的数据
语法:
const xxx ref(initValue)
- 创建一个包含响应式数据的
引用对象(reference对象)
- JS 中操作数据:
xxx.value
- 模板中读取数据:不需要.value,直接:
<div></div>
- 创建一个包含响应式数据的
备注:
- 接收的数据可以是:基本类型、对象类型
- 基本类型的数据:响应式依然是靠
Object.defineProperty()
的get
与set
完成的 - 对象类型的数据:内部“求助”了 Vue3 中的一个新函数——
reactive
函数
3. reactive 函数
- 作用:定义一个
对象类型
的响应式数据(基本类型不要用,使用 ref 函数) - 语法:
const 代理对象 = reactive(源对象)
接收一个对象或数组,返回一个代理对象(proxy的实例对象,简称proxy对象)
- reactive 定义的响应式数据是深层次的
let job = reactive({
type: '前端',
a: {
b: {
c: {
d: '111'
}
}
}
})
function changeInfo () {
job.a.b.c.d = '222'
}
// job.a.b.c.d 222
- 内部是基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据进行操作
4.Vue3.0 中的响应式原理
vue2的响应式
实现原理:
- 对象类型:通过
Object.defineProperty()
对属性的读取、修改进行拦截(数据劫持) - 数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)
Object.defineProperty(data, 'count', { get(){}, set(){} })
- 对象类型:通过
存在问题:
- 新增属性、删除属性,界面不会更新
- 直接通过下标修改数组,界面不会自动更新
this.$set(this.p, 'name', '123') this.$delete(this.p, 'name')
vue3的响应式
实现原理:
- 通过
Proxy(代理)
:拦截对象中任意属性的变化,包括:属性值的读写、属性的添加、属性的删除等。 - 通过
Reflect(反射)
:对被代理对象的属性进行操作 MDN文档中描述的 Proxy 与 Reflect:
new Proxy(data, { // 拦截读取属性值 get(target, propName) { return Reflect.get(target, propName) }, // 拦截设置属性值或添加新属性 set(target, propName, value) { return Reflect.set(target, propName, value) }, // 拦截删除属性 deleteProperty(target, propName) { return Reflect.deleteProperty(target, propName) } })
- 通过
5. reactive 对比 ref
- 从定义数据角度对比:
- ref 用来定义:
基本数据类型
code> - reactive用来定义:
对象(或数组)类型数据
- 备注:ref 也可以用来定义
对象(或数组)类型数据
,它内部会自动通过reactive
转为代理对象
- ref 用来定义:
- 从原理角度对比:
- ref 通过
Object.defineProperty()
的get
与set
来实现响应式(数据劫持)。 - reactive 通过使用
Proxy
来实现响应式(数据劫持),并通过Reflect
操作源对象
内部的数据。
- ref 通过
- 从使用角度对比:
- ref 定义的数据:操作数据需要
.value
,读取数据时,替中直接读取,不需要.value
- reactive 定义的数据:操作数据与读取数据,均不需要
.value
- ref 定义的数据:操作数据需要
6. setup 的两个注意点
setup执行的时机
- 在 beforeCreate 之前执行一次,this 是 undefined
setup的参数
props:
值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性context:
上下文对象attrs:
值为对象,包含组件外部传递过来,但没有在 props 配置中声明的属性,相当于this.$attrs
slots:
收到的插槽内容,相当于this.$slots
emit:
分发自定义事件的函数,相当于this.$emit
// 子组件 <template> <h1>个人信息</h1> <h3>姓名:</h3> <h3>年龄:</h3> <button @click="sayHello">说话</button> <slot name="hello"></slot> </template> <script> import { reactive } from 'vue'; export default { name: 'Demo-app', props: { msg: { type: String, defaule: '' } }, emits: ['hello'], setup (props, context) { console.log(props); console.log(context.slots); let person = reactive({ name: '张三', age: 20 }) function sayHello () { context.emit('hello', `我叫${person.name},你好啊`) } return { person, sayHello } } } </script>
// 父组件 <template> <Demo msg="黑科技" @hello="test"> <template v-slot:hello> <span>hello</span> </template> </Demo> </template> <script> import Demo from './components/Demo.vue' export default { name: 'App', components: { Demo }, setup () { function test (val) { alert(val) } return { test } } } </script>
7. 计算属性与监视
- computed 函数
import { computed } from 'vue';
// 计算属性,没有考虑修改计算属性的值(简写)
person.name = computed(() => {
return `${person.firstName}-${person.lastName}`
})
// 计算属性,完整写法
person.name = computed({
get () {
return `${person.firstName}-${person.lastName}`
},
set (val) {
const nameArr = val.split('-');
person.firstName = nameArr[0];
person.lastName = nameArr[1];
}
})
2. watch 函数
- 与 vue2 中的配置功能一直
- 两个小坑:
- 监视 reactive 定义的响应式数据时:oldValue 无法正确获取、强制开启了深度监视(deep配置无效)
- 监视 reactive 定义的响应式数据中某个属性时:deep配置有效
import { ref, watch } from 'vue';
export default {
name: 'Demo1-app',
setup () {
let num = ref(0);
// 情况一:监视 ref 所定义的一个响应式数据
watch(num, (newVal, oldVal) => {
console.log(`num的值改变了--${newVal}-${oldVal}`);
})
return {
num
}
}
}
watch
接收三个参数,第三个为配置项 {immediate: true, deep: true}
let num = ref(0);
let msg = ref('你好');
// 情况二:监视 ref 所定义的多个响应式数据
watch([num, msg], (newVal, oldVal) => {
console.log(newVal, oldVal);
}, { immediate: true })
// newVal [1, '你好'] oldVal [0, '你好']
// 情况三:监视 reactive 所定义的一个响应式数据,注意:此处无法正确的获取 oldVal
// 强制开启了深度监听(deep配置无效)
watch(person, (newVal, oldVal) => {
console.log(newVal, oldVal);
}, {deep: false}) // 无效
// newVal: Proxy {name: '张三~~', age: 21}
// oldVal: Proxy {name: '张三~~', age: 21}
// 此处 newVal 与 oldVal相同,如果实际情况需要使用 oldVal,则可以把 对象属性提出来用 ref 定义
// 情况四:监听 reactive 所定义的一个响应式数据中的某个属性
watch(() => person.age, (newVal, oldVal) => {
console.log(newVal, oldVal);
})
// 此时 oldVal 是正确的
// 情况五: 监听 reactive 所定义的一个响应式数据中的某些属性
watch([() => person.name, () => person.age], (newVal,oldVal) => {
console.log(newVal, oldVal);
})
// newVal ['张三~', 20]
// oldVal ['张三', 20]
// 特殊情况
// 此处由于是监视的 reactive所定义的数据中对象的某个属性, deep 生效
watch(() => person.job, (newVal, oldVal) => {
console.log(newVal.j1.salary, oldVal.j1.salary);
}, {deep: true})
// newVal 22 oldVal 22