1.快速开始
- 通过脚手架vite创建vue项目
npm init vite-app hello-vue3
cd hello-vue3
npm install
npm run dev
- 通过vue-cli创建vue3.0项目(略)
2. 入口文件main的区别
vue3.0
// 引入的不再是vue的构造函数了,而是名为createApp的工厂函数*
// createApp 里面的方法比vue2.0中引入的Vue构造函数少,相比起来会更加“轻”
import { createApp } from 'vue'
import App from './App.vue'
*// createApp(App)创建实例对象,mount将对象挂载到页面*
createApp(App).mount('#app')
3.vue3.0的新特性
1. 拉开序幕的setup
-
理解:是一个新的配置项,是一个函数
-
组件中所有的数据,方法等均要配置到setup中
-
setup函数有两种返回值
-
返回一个对象,对象中的属性和方法均可在模板中访问到
-
还可以返回一个渲染函数,则可自定义渲染内容
<h1>我是</h1>
//导入渲染函数 import { h } from 'vue' // 返回 return () => h('h1', '2021928')
输出
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4pkAkJ8w-1635500034724)(C:\Users\Fxl\AppData\Roaming\Typora\typora-user-images\image-20210928115440349.png)]
-
注意点:
-
vue3虽然可以兼容vue2的语法,但是尽量不要混用
在vue2的data, methods中可以访问到setup的属性和方法
但是setup中不能访问到vue2的配置
2. 执行时机: 在beforeCreate之前执行 3. 参数:
props: 值为对象,包含组件外部传递过来且在组件内部声明了的属性
props: [‘msg’, ‘sex’],
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ESefYVeE-1635500034727)(C:\Users\Fxl\AppData\Roaming\Typora\typora-user-images\image-20210928174002905.png)]
context: 上下文对象
attrs:值为对象,包含外部传递过来但是没在内部props配置中声明的属性
slots: 收到的插槽内容,vue3中使用v-slot
emit: 分发自定义事件的函数
2. 响应式ref函数
因为在vue2中数据放在data函数内,使用数据劫持对数据进行了响应,但是在vue3中使用setup函数中直接声明数据,没有响应式,于是就诞生了ref函数,要将一个数据变成响应式数据
语法: const xxx = ref(数据)
ref会创建包含响应式的一个引用对象,将数据值放在对象的value内,在取值时使用 xxx.value,
在模板中使用时直接
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Anq6CQJ5-1635500034731)(C:\Users\Fxl\AppData\Roaming\Typora\typora-user-images\image-20210928140904168.png)]
备注:
- 接收的数据可以是基本类型也可以是引用类型
- 对于基本类型数据,响应依旧是靠Object.defineProperty()的set与get来实现的
- 对于引用类型的数据,内部求助了一个新函数reactive函数
3. reactive 函数
- 作用: 定义一个对象类型的响应式数据,基本类型不能使用reactive
- 语法:接受一个对象或者数组,返回一个proxy代理对象
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P4EE6QrL-1635500034737)(C:\Users\Fxl\AppData\Roaming\Typora\typora-user-images\image-20210928143742164.png)]
- reactive定义的对象是深层次的
- 内部基于ES6的proxy实现
4. vue3.0中的响应式原理
vue2的响应式原理:
对象类型: 通过object.defineProperty()对属性的读取,修改进行拦截
数组类型: 通过重写更新数组的一系列方法进行拦截
存在的问题:
- 只能监听到数据的修改与读取,当对象里增加,删除属性时页面不能更改,可以使用$set(对象,属性,值), $delete(对象,属性)的方式增加和删除数据
- 直接通过数组下表修改数组,页面不会自动更新
vue3的响应式
通过Proxy: 拦截对象中的任意属性变化,包括: 属性值的读写,属性的添加,属性的删除等
let person = {
name: "张三",
age:32
}
// 模拟vue3中实现响应式
const p = new Proxy(person, {
// 当用户获取某个属性时触发
get(target, propName) {
console.log(`有人读取了p身上的${propName}属性`);
// target是原对象,p是通过proxy代理的对象,propName是读取的属性名,因为propName是变量所以只能使用target[propName]来读取
return target[propName]
},
// 当用户修改或者新增属性时触发
set(target, propName, value) {
console.log(`有人修改了p身上的${propName}属性`);
// target是原对象,p是通过proxy代理的对象,propName是读取的属性名,因为propName是变量所以只能使用target[propName]来读取
target[propName] = value
},
// 当有人删除属性时触发
deleteProperty(target, propName) {
console.log(`有人删除了p身上的${propName}属性`);
return delete target[propName]
}
})
通过Reflect(反射): 对源对象的属性进行操作
let person = {
name: "张三",
age:32
}
// 模拟vue3中实现响应式
const p = new Proxy(person, {
// 当用户获取某个属性时触发
get(target, propName) {
console.log(`有人读取了p身上的${propName}属性`);
// target是原对象,p是通过proxy代理的对象,propName是读取的属性名,因为propName是变量所以只能使用target[propName]来读取
return Reflect.get(target, propName)
},
// 当用户修改或者新增属性时触发
set(target, propName, value) {
console.log(`有人修改了p身上的${propName}属性`);
// target是原对象,p是通过proxy代理的对象,propName是读取的属性名,因为propName是变量所以只能使用target[propName]来读取
return Reflect.set(target, propName, value)
},
// 当有人删除属性时触发
deleteProperty(target, propName) {
console.log(`有人删除了p身上的${propName}属性`);
return Reflect.defineProperty(target, prop)
}
})
4.计算属性
vue3.0的计算属性与vue2.0的计算属性一致,但是在vue3内需要引入
计算属性设计的初衷就是为了避免在模板中进行复杂计算,所以,对于任何包含响应式数据的复杂逻辑,都应该使用计算属性
计算属性的 Setter
计算属性默认只有 getter,不过在需要时你也可以提供一个 setter:
import { computed } from 'vue'
// ...
setup() {
let person = reactive({
firstName:'张'+ ' ',
lastName:'三',
})
// let fullName = computed(
// () => {
// return person.firstName + person.lastName
// }
// )
person.fullName = computed(
{
get(){
return person.firstName + person.lastName
},
set(newValue) {
const names = newValue.split(' ')
person.firstName = names[0]
person.lastName = names[names.length - 1]
}
}
)
// 返回一个对象
return {
person
}
现在再运行 vm.fullName = 'John Doe'
时,setter 会被调用,vm.firstName
和 vm.lastName
也会相应地被更新
5.监听器
当数据变化时执行异步或者开销较大的操作是,就要使用watch, watch有三个参数第一个参数是要监听的属性,第二个是数据变化是的回调函数,第三个为配置文件
<h2>当前求和为: {{sum}}</h2><button @click="add">点我加1</button><div>{{person.name}}</div><div @click="addAge">{{person.age}}</div><input type="text" v-model="person.job.j1.salary">
<script> import { ref, watch, reactive } from 'vue'export default { name: 'HelloWorld', setup() { let sum = ref(1) let sum2 = ref(3) let person = reactive({ name: '张三', age: 21, job: { j1: { salary: 20 } } }) function add() { sum.value += 1 } function add1() { sum2.value += 1 } //1. 监听一个响应式数据的时候 watch(sum,(newValue,oldValue) => { console.log('sum变化了', newValue,oldValue); } ,{ immediate:true }) // 2. watch还可以监听多个响应式数据,当监听的任意一个数据变化时都会监听到并且执行回调函数 watch([sum, sum2],(newValue,oleValue) => { console.log('sum变化了', newValue,oleValue); }) /* 3.监听reactive所定义的一个响应式对象的全部属性, 注意:这里无法获取到oldvalue,强制开启了深度监听 */ watch(person, (newvalue, oldvalue)=>{ console.log("person变化了", newvalue, oldvalue); }) /* 4. 监听reactive所定义的一个响应式对象里面的某一个属性,只能定义一个函数将对象里的属性返回出去 */ watch(()=>person.age, (newvalue, oldvalue)=>{ console.log("person的age变化了", newvalue, oldvalue); }) /* 5. ractive所定义的一个响应式对象里面的一些属性 */ watch([()=>person.age, ()=> person.name], (newvalue, oldvalue)=>{ console.log("person的age变化了", newvalue, oldvalue); }) //6. 特殊情况 //当见识的是reactive定义的对象中的某个属性时且该值为对象时,deep配置是有效的 // 当改变了job里面j1里面的salary并不能监听到变化,只有配置deep:true watch([()=>person.job, ()=> person.name], (newvalue, oldvalue)=>{ console.log("person的age变化了", newvalue, oldvalue); },{deep:true}) // 返回一个对象 return { add, sum, add1, sum2, person, addAge } },</script>
6.watchEeffct函数
watchEffect的套路是,不用指明监视哪个属性,监视的回调中用到哪个属性,就监视哪个属性
当person.count改变时,会重新运行watchEffect函数
watchEffect(()=>{ console.log(person.count,"count改变了"); })
7.生命周期
beforeCreate — created — beforeMount — mounted —beforeUpdate — updated—beforeUnmount — Unmounted
与vue2不同的是将beforeDestroy和destoried改成了beforeUnmount — Unmounted ,而且全部除了beforeCreate 和 created之外都可以在setup函数中使用,不过要改变名称,
选项式 API 的生命周期选项和组合式 API 之间的映射
beforeCreate
-> 使用setup()
created
-> 使用setup()
beforeMount
->onBeforeMount
mounted
->onMounted
beforeUpdate
->onBeforeUpdate
updated
->onUpdated
beforeUnmount
->onBeforeUnmount
unmounted
->onUnmounted
8.自定义hook函数
Vue3 的 hook函数 相当于 vue2 的 mixin, 不同在与 hooks 是函数,目的是让setup中的逻辑更清晰
Vue3 的 hook函数 可以帮助我们提高代码的复用性, 让我们能在不同的组件中都利用 hooks 函数
例子 :
首先在src目录下建立一个 hooks 文件夹
申明一个我们要复用的方法的名字.js 文件
下面是 useMousePosition.ts 代码,讲一个方法的逻辑封装到函数内,当使用时就直接引用该函数
import {onBeforeUnmount, onMounted, ref} from 'vueexport default function () { const x = ref(-1) ; // x 绑定为响应式数据 const y = ref(-1); const clickHandler=(event)=>{ x.value = event.pageX y.value = event.pageY } onMounted(()=>{ window.addEventListener('click', clickHandlker) }) onBeforeUnmount(()=>{ window.removeEventListner('click', clickHandler) }) return { x, y }}
下面是 App.vue组件使用我们的 userMousePosition 这个 hooks 方法
<template><div> <h2>x: {{x}}, y: {{y}}</h2></div></template><script>import { ref} from "vue"/* 在组件中引入并使用自定义hook自定义hook的作用类似于vue2中的mixin技术自定义Hook的优势: 很清楚复用功能代码的来源, 更清楚易懂*/import useMousePosition from './hooks/useMousePosition'export default { setup() { const {x, y} = useMousePosition() // 这里就用了 hooks 函数, 从而提高了复用性 return { x, y, } }}</script>
9.toRef , toRefs
toRef作用就是将一个不是响应式的数据变成响应式,可以用来为源响应式对象上的某个 property 新创建一个 ref
。然后,ref 可以被传递,它会保持对其源 property 的响应式连接。
toRefs可以将一个对象的多个属性变成响应式数据
初始代码
<div> <h3>{{person.name}}</h3> <h3>{{person.age}}</h3> <h3>{{person.job.j1.salary}}</h3> <button @click="changeName">修改名称</button> <button @click="changeAge">修改年龄</button> <button @click="changeSla">修改薪资</button> </div>
将person返回出去,在template中需要使用person.name这样使用值
setup() { let person = reactive({ name: '张三', age: 20, job: { j1: { salary: 30 } } }) function changeName() { person.name += '~' } function changeAge() { person.age += 1 } function changeSla() { person.job.j1.salary++ } return { person, changeName, changeAge, changeSla } },
将person的某个属性返回出去(错误版本),虽然template里面可以直接使用name,age,但是这样相当于将字符串直接赋值给name,并不是响应式数据,这时候就要使用toRef函数
return { name: person.name, age: person.age, salary: person.job.j1.salary, changeName, changeAge, changeSla }
使用toRef将属性返回
let name = toRef(person, 'name') let age = toRef(person, 'age') let salary = toRef(person.job.j1, 'salary') return { name, age, salary, changeName, changeAge, changeSla }
但是如果会返回很多个属性,使用toRef未免太复杂,这时候就可以使用toRefs, 而且如果想让对象里面的数据实现响应式的话就只能在toRefs之后才能对对象进行解构赋值,不然只能等到普通的数值并不是响应式数据
// 第一种方式,将某几个属性暴露let {name, age, job} = toRefs(person) return { name, age, job, changeName, changeAge, changeSla }//第二种,将所有属性都暴露出去return { ...toRefs(person), changeName, changeAge, changeSla }
模板内使用:
<h3>{{name}}</h3><h3>{{age}}</h3><h3>{{job.j1.salary}}</h3>
10.不常用的api
shallowReactive 与shallowRef
按照字面意思来说就是浅层次的响应,shallowReactive声明的对象只能响应到第一层,如果对象里面的对象就无法响应,如下person里面job对象下的那些属性就无法响应。
let person = shallowReactive({ name: '张三', age: 20, job: { j1: { salary: 30 } } })
shallowRef与ref的区别是shallowRef只考虑基本数据类型的数据,但是ref如果传入的是对象的话,它会去使用reactive去处理传进来的对象,
什么时候用:
如果有一个对象数据,结构比较深,但变化只是外层属性变化就可以使用shallowReactive
如果有一个对象数据,后续功能不会修改该对象的属性,而是生成新的对象来替换
11.readonly 和shallowReadonly
readonly: 让一个响应式的数据变为只读的(深只读)
shallowReadonly: 让一个响应式的数据变为只读的(浅只读)
应用场景: 不希望数据被修改时使用
12. toRaw与markRaw
toRaw:
作用: 将一个有reactive生成的响应式对象转为普通对象
使用场景: 用于读取响应式对象对应的普通对象,对这个普通对象的所有操作不会影响页面更新
markRaw:
作用:标记一个对象使其永远不会成为响应式对象
使用场景:
有些值不该被设置为响应式的,例如复杂的第三方类库等
当渲染具有不可变数据源的大列表是,跳过响应式可以提高性能