vue3&composition Api
vue3&composition Api
Jinsetup
setup 是 Vue3 中一个新的配置项,值为函数
组件中使用的数据、方法等都要配置在 setup 中
setup 函数返回值:对象,函数等数据,可在模板中直接使用
setup 函数参数:props,context
props
:自定义属性,组件外部传进来,且组件内部声明接收的属性(父传子)context
:上下文对象,也称之为是一个SetupContext,它里面包含三个属性attrs
: 所有非 prop 的 attribute,组件外部传进来,且组件内部没有声明接收的属性,相当于this.$attrs
slot
:父组件传递过来的插槽,相当于this.$slots
emit
:触发自定义事件,相当于this.$emit
export default {
name: 'App',
props: ['title'],
// 声明自定义事件,虽然不声明也能运行
emits: ['changeCount'],
setup(props, context) {
let name = 'Vue3'
function sayHello() {}
function test() {
// 创建自定义事件
context.emit('changeCount', 888)
}
// 在 setup 函数 定义的值,函数
// return 出去可以在模板上使用
return {
name,
sayHello,
test,
}
},
}
注意
- setup 在
beforeCreate
钩子之前执行,this
为undefined
- setup 不要和 Vue2 配置混用。Vue2 的配置可以访问到 setup 的属性方法,反过来不行;如有重名,setup 优先
- setup 不能是 async 函数,因为 async 函数返回的是 promise 不是对象,会导致模板无法访问属性方法
- 若要返回 promise 实例,需要
Suspense
和异步组件的配合
定义响应式数据
ref 函数
语法:
const msg = ref('hello world')
返回: ref 会返回一个可变的响应式对象,该对象作为一个 响应式的引用 维护着它内部的值
ref
函数可以接收基本数据类型和引用数据类型原理:
- 基本类型数据的响应式还是靠
Object.defineProperty()
完成 - 对象类型数据使用 ES6 的 Proxy 实现响应式,Vue3 把相关操作封装在
reactive
函数中 - 按照之前的办法,对于对象数据,应该遍历每一层的属性添加
getter
、setter
,但 Vue3 使用 Proxy 把内部数据一口气监测了
- 基本类型数据的响应式还是靠
注意:
- 但是在 setup 函数内部,它依然是一个
ref引用
, 所以对其进行操作时,我们依然需要
使用ref.value
的方式 - 在模板中引入 ref 的值时,Vue会自动帮助我们进行解包操作,所以我们并
不需要
在模板中通过ref.value
的方式来使用
- 但是在 setup 函数内部,它依然是一个
reactive函数
使用和 ref 大致一样
reactive API对传入的类型是有限制的,它要求我们必须传入的是一个对象或者数组类型
ref 和reactive
- 定义数据:
- ref 用于定义基本类型数据
- reactive 用于定义对象或数组类型数据
- ref 也可定义对象或数组类型数据,内部通过 reactive 转为代理对象
- 一般使用 reactive 函数,可以把所有数据封装为一个对象
- 原理:
- ref 通过
Object.defineProperty()
实现响应式 - reactive 通过 Proxy 实现响应式,Reflect 操作源对象数据
- 使用角度:
- ref 定义数据,访问数据需要
.value
,模板中不需要 - reactive 定义的数据,都不需要
- 开发中选择 reactive/ref :
- 通过服务器的建议使用 ref
- 本地的数据使用 reactive
readonly的使用
单项数据流
概念:所有的 props 都遵循着单向绑定原则,props 因父组件的更新而变化,自然地将新的状态向下流往子组件,而不会逆向传递。这避免了子组件意外修改父组件的状态的情况,不然应用的数据流将很容易变得混乱而难以理解
简单理解:父组件传到子组件中的原始数据,不可以在子组件修改,为了避免混乱不清楚哪一个组件修改数据的,统一返回父组件修改
readonly
readonly
: 让一个响应式数据变为只读的(深只读)shallowReadonly
:让一个响应式数据变为只读的(浅只读)- 应用场景: 不希望数据被修改时,如你用了别人的响应式数据,但是别人不希望你修改时
setup() {
let sum = ref(0)
let person = reactive({...})
sum = readonly(sum)
person = shallowReadonly(person)
return {
sum,
person
}
}
判断 Reactive 的 API
isProxy
检查对象是否是由 reactive 或 readonly创建的 proxy。isReactive
检查对象是否是由 reactive创建的响应式代理:如果该代理是 readonly 建的,但包裹了由 reactive 创建的另一个代理,它也会返回 true;
isReadonly
检查对象是否是由 readonly 创建的只读代理。toRaw
返回 reactive 或 readonly 代理的原始对象(不建议保留对原始对象的持久引用。请谨慎使用)。shallowReactive
创建一个响应式代理,它跟踪其自身 property 的响应性,但不执行嵌套对象的深层响应式转换 (深层还是原生对象)。shallowReadonly
创建一个 proxy,使其自身的 property 为只读,但不执行嵌套对象的深度只读转换(深层还是可读、可写的)。
toRefs
使用ES6的解构语法,对 reactive 返回的对象进行解构获取值,那么之后无论是修改结构后的变量,还是修 reactive返回的state对象,数据都不再是响应式的
使用 toRef
可以将reactive返回的对象中的属性都转成 ref
如果我们只希望转换一个reactive对象中的属性为 ref , 那么可以使用 toRef
的方法
const state = reactive({
name: 'jay',
age: 18
})
// 使用 toRefs/toRef 数据成响应式
// toRefs
const {name, age} = toRefs(state)
// toRef
const name = toRef(state,'name')
setup中不能使用this
- this 并没有指向当前组件实例;
- 并且在 setup 被调用之前,data、computed、methods 等都没有被解析;
- 所以无法在 setup 中获取 this;
computed计算属性
- (简写)接收一个getter函数,并为 getter 函数返回的值,返回一个不变的 ref 对象
- (完整)接收一个具有 get 和 set 的对象,返回一个可变的(可读写)ref 对象
import { reactive, computed } from 'vue'
export default {
setup() {
let person = reactive({
firtName: 'jay',
lastName: 'FT'
})
// 简写模式
const fullName = computed(()=>{
return firtName.value + '' + lastName.value
})
// 完整写法
const fullname = computed({
get: ()=> {
return firtName.value + ' ' + lastName.value
},
set: newValue => {
const names = newValue.split(' ')
firtName.value = names[0]
lastName.value = names[1]
}
})
function setFullname() {
fullname.value = "jay FT"
}
}
}
生命周期注册函数
在 setup 里,可以使用直接导入的 onX 函数注册生命周期钩子
import { onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from 'vue'
setup(){
console.log('---setup---')
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---')
})
return {sum}
},
选项式 API | Hook inside setup |
---|---|
beforeCreate | setup() |
create | setup() |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeUnmount | onBeforeUnmount |
unmounted | onUnmounted |
activted | OnActivted |
deactivated | Ondeactivated |
setup
函数是 围绕beforeCreate
和create
生命周期钩子运行的,所有不需要显式的定义它们。在这些钩子的代码直接在setup
函数编写
provide/inject
实现祖先组件与后代组件之间通信
// 祖先组件传递数据
import { provide, reactive, ref } from 'vue'
setup() {
let car = reactive({...})
let sum = ref(0)
provide('sum', sum)
provide('car', car)
}
// ======================
// 后代组件接收数据
import { inject } from 'vue'
setup() {
const car = inject('car')
const sum = inject('sum')
return { car, sum }
}
watch/watchEffect
watch
// 数据
let sum = ref(0)
let msg = ref('hello')
let infoRef = ref({
name: 'Jay',
age: 18,
song: {
fantasy: {
year: 2002
}
}
})
// reactive响应数据
let info = reactive({
name: 'Jay',
age: 18,
song: {
fantasy: {
year: 2002
}
}
})
// 侦听 ref 定义响应式数据
watch(sum, (newVal, oldVal) => {
console.log(newVal, oldVal)
})
// 侦听多个 ref 数据、
watch([sum, msg], (newVal, oldVal) => {
// newVal,oldVal 也是数组
console.log(newVal, oldVal)
})
// 侦听对象数据 需要开启深度监听
// 此时监听的是 RefImpl 实例
// Ref 实例的 value 是 Proxy 对象,存的是地址
// 因此无法监听 person 内部属性的变化
watch(infoRef, (newVal, oldVal) => {}, { deep: true })
// 侦听 reactive 定义的响应式数据某个属性
// 强制开启了深度侦听,deep 配置不生效!
// 如果监视的属性还是对象,则需要开启深度监听
// 侦听多个属性也是使用数组形式
watch(
() => info.song,
(newVal, oldVal) => {
console.log(newVal, oldVal)
},
{ deep: true }
)
可选项
immediate: true
页面加载触发一次
deep: true
深度侦听
watchEffect 函数
watchEffect
不需要指明监听哪个属性,回调里用到哪个属性,就自动监听哪个属性computed
注重计算的值,即回调函数的返回值,因此必须有返回值watchEffect
更注重过程,即回调函数的函数体,因此可没有返回值watchEffect
没有开启深度监听,也不能开启深度监听!watchEffect
内部自行修改数据,不会重新调用回调,因此不会出现递归调用
// 回调中用到的数据只要发生变化,则直接重新执行回调
watchEffect(() => {
let total = sum.value
let p = person
console.log('watchEffect...')
})
watch/watchEffect区别
- 1.watch必须制定数据源, watchEffect自动收集依赖
- 2.watch监听到改变, 可以拿到改变前后value
- 3.watchEffect默认直接执行一次, watch在不设置immediate第一次是不执行
script setup语法糖
<script setup>
是在单文件组件 (SFC) 中使用组合式 API 的编译时语法糖,当同时使用 SFC 与组合式 API 时则推荐该语法。
<script setup>
里面的代码会被编译成组件 setup()
函数的内容:
这意味着与普通的
<script>
只在组件被首次引入的时候执行一次不同<script setup>
中的代码会在每次组件实例被创建的时候执行。顶层的绑定会被暴露给模板:任何在
<script setup>
声明的顶层的绑定 (包括变量,函数声明,以及 import 引入的内容都能在模板中直接使用导入组件直接使用:
<script setup>
范围里的值也能被直接作为自定义组件的标签名使用
defineProps && defineEmits
为了在声明 props 和 emits 选项时获得完整的类型推断支持,我们可以使用 defineProps 和 defineEmits API,它们将自动地在
<script setup>
中可用
<script setup>
const props = defineProps({
name: {
type: String,
default: 'Jay'
},
age: {
type: Number,
default: 1
}
})
const emit = defineEmits(['changeAge'])
function changeAge() {
emit('changeAge', 200)
}
</script>
defineExpose
通过模板 ref 或者 $parent 链获取到的组件的公开实例,不会暴露任何在
<script setup>
中声明的绑定
通过 defineExpose 编译器宏来显式指定在 <script setup>
组件中要暴露出去的 property
function foo() {
console.log('foo执行')
}
defineExpose({
foo
})
Compositon API 的优势
Options API 存在的问题
使用传统 Options API 中,新增或者修改一个需求,就需要分别在 data,methods,computed 等地方修改
composition API 的优势
可以更加优雅地组织代码、函数,让相关功能的代码更加有序的组织在一起。说白了就是让同一个功能的代码整合到一起,日后修改代码直接找对应的功能模块。