Vue Composition API(更新中)
setup函数简介
函数主要参数:
- props
- context
props
就是父组件传递过来的属性,当然我们需要事先在组件中配置接收props
props:{
name:{
type:String,
default:""
},
age:{
type:Number,
default:10
}
},
setup(props, context) {
console.log(props);
}
context
是个对象,也可以称之为SetupContext
,包含三个对象:
attrs
所有非props中配置的attr
,比如再父组件中对组件设置的id
class
等属性slots
父组件传递过来的插槽,一般我们使用render
函数渲染组件的时候会用到emit
当我们租价内部想向父组件发出事件时会用到
注意:setup
函数中不能使用this
,因为 this
并没有指向当前组件实例,并且在setup
被调用之前,data
、computed
、methods
等都没有被解析.所以无法在setup
中获取this
函数返回值
setup
函数返回值首先应该是一个对象(不是的话会报警告)可以用作template
模板中使用,用来代替data
选项,我们也可以在返回对象中添加函数,也就代替methods
选项了
setup(props, context) {
console.log(props, context);
const increment = () => {
//...
}
return {
message:"hello setup",
title:"zhang",
increment
}
注意我们在setup
返回的数据是没有响应式特性了,所以要想使用响应式的数据我们可以使用以下API:
Reactive API
使用方式
import { reactive } from "vue"
setup() {
const counterState = reactive({counter:100});
const increment = () => {
counterState.counter++;
}
return {
increment,
counterState
}
}
<div>{{counterState.counter}}</div>
这样就可以实现响应式了,但是ractive
这个API只能传入一个对象或者数组类型,传入基本类型会报警告,而且使用的时候还需要以对象取属性方式手动解包,所以我们还可以使用另一个API ref
Ref API
ref
会返回一个可变的响应式对象。该对象作为一个响应式的引用维护着它内部的值,这就是ref
名称的来源
import { ref } from "vue"
setup() {
const counter = ref(100)
const increment = () => {
//这里使用是不会为我们自动解包
counter.value++
}
return {
increment,
counter
}
}
<!--模板中vue为了让我们书写更加简便,给我们自动做了解包处理(实际写法为 counter.value)-->
<div>{{counter}}</div>
另外将ref
处理后的返回值放到ractive
的对象中也是会为我们解包的
const name = ref("zhangsan");
const infoState = reactive({
age:18,
name//实际为name:name.value
});
readonly
readonly
返回一个原生对象的代理,这个代理是只读的,有时候我们传递给其他组件数据,但是只想让其展示,不做修改就可以这样使用
//一般readonly方法会传入三个类型的参数
/*
1. 普通对象
2. reactive返回的对象
3. ref返回的对象
*/
setup(props, context) { //普通对象
const info = {name:"lilisi"};
const readOnlyInfo = readonly(info);
//reactive
const info2 = reactive({name:"wang"});
const readOnlyInfo2 = readonly(info2);
//ref
const info3 = ref("saber");
const readOnlyInfo3 = readonly(info3);
const increment = () => {
readOnlyInfo.name = "saber";
readOnlyInfo2.name = "haha";
readOnlyInfo3.value = "hello";
}
return {
readOnlyInfo,
readOnlyInfo2,
readOnlyInfo3
}
}
模板中展示:
<div>{{readOnlyInfo.name}}---{{readOnlyInfo2.name}}---{{readOnlyInfo3}}</div>
readonly
返回的对象都是不允许修改的, 是经过readonly
处理的原来的对象是允许被修改的,所以我们要修改的话就要修改原来的对象,当然数据都是响应式的,毕竟本质上就是readonly
返回的对象的setter方法被劫持了而已
Reactive 判断的API
isProxy
判断对象是否是reactive
或者readonly
创建的proxy
const state = reactive({name:'zhangsan'});
const read = readonly({age:18});
const counter = ref(100);
console.log(isProxy(state));//true
console.log(isProxy(read));//true
console.log(isProxy(counter));//false
isReactive
检查对象是否由reactive
创建的响应式代理,如果该代理是readonly
创建的但包裹了由reactive
创建的另一个代理,也会返回true
const state = reactive({name:'zhangsan'});
const read = readonly(state);
const counter = ref(100);
console.log(isReactive(state));//true
console.log(isReactive(read));//true
console.log(isReactive(counter));//false
isReadonly
检查对象是否由readonly
创建的只读代理
const state = reactive({name:'zhangsan'});
const read = readonly(state);
const counter = ref(100);
console.log(isReadonly(state));//false
console.log(isReadonly(read));//true
console.log(isReadonly(counter));//false
toRaw
返回reactive
或者readonly
代理的原始对象(但是不建议过久的保留对原始对象的引用),该API需要谨慎使用
const state = reactive({name:'zhangsan'});
const read = readonly(state);
console.log(toRaw(state));
console.log(state);
console.log(toRaw(read));
console.log(read);
返回结果:
shallowReactive
创建一个响应式代理,它跟踪对象自身的property
的响应式,但是不执行嵌套对象的深层次响应式转换(浅响应式转换)
shallowReadonly
创建一个只读代理但是只是浅层次的property
为只读,不执行嵌套对象的深层次只读只读转换
Ref相关API
toRefs
有时候我们想使用解构语法来获取值,对于reactive
返回的代理对象我们使用解构后其数据就不再是响应式的
const state = reactive({name:"zhangsan", age:18});
const {name, age} = state;//此时name和age不再具有响应式特性
通过toRefs
这个函数可以将reactive
返回的对象中的属性转成ref
,所以我们解构出来的name
和age
都是等同于通过ref
创建的对象了,就具有响应式特性
const state = reactive({name:"zhangsan", age:18})
;const {name, age} = toRefs(state);//此时name和age具有响应式特性
toRef
我们有时候不希望将reactive
返回的对象中的所有属性都转换成ref
,就只需要转换其中的一个属性为ref
,我们就可以使用这个API
const state = reactive({name:"zhangsan", age:18});
const {name} = toRef(state, "name");
unref
有时候我们需要获取一个ref中的value,可以通过该方法
该方法其实就是一个语法糖,实际的代码如下:
val = isRef(val) ? val.value : val
isRef
判断值是否为ref
对象
const state = reactive({name:'zhangsan'});
const read = ref(100);
console.log(isRef(state));//false
console.log(isRef(read));//false
console.log(isRef(read.value));//false
shallowRef
创建一个浅层的ref
对象
const info = shallowRef({name: "zhangsan"});//修改name值
info.value.name = "lls";//该操作不是响应式的
triggerRef
可以手动触发和shallowRef
相关联的副作用,比如shallowRef
的副作用就是进行视图的更新,但是深层次属性修改并不会触发该副作用,所以我们可以手动触发该副作用来更新视图
const info = shallowRef({name:"zhangsan"});
setTimeout(() => {
info.value.name = "lls";//修改是不会更新视图的
triggerRef(info);//手动来触发更新视图
}, 5000)
customRef
创建一个自定义的ref
,并对其依赖项跟踪和更新触发进行显示控制,该API需要一个工厂函数,函数需要接收track
和trigger
函数作为参数,同时该工厂函数需要返回一个对象,该对象带有get
和set
方法
这个其实还比较难理解,这里展示一个例子来简单解释下如何使用:对双向绑定的属性进行节流操作
import { createApp, customRef } from 'vue';
// 自定义ref
export default function (value, delay = 300) {
let timer = null;
return customRef((track, trigger) => {
return {
get() {
track(); // 收集依赖
return value;
},
set(newValue) {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
value = newValue;
trigger();
}, delay);
}
};
});
}
<input type="text" v-model="message"> {{message}}
import debounceRef from './hook/useDebounceRef';
setup() {
const message = debounceRef("Hello World");
return {
message
}
}
computed函数
在Composition API
中,我们可以在setup
函数中使用computed
方法来编写一个计算属性
写法一:
接收一个getter
函数,并以getter
函数返回的值返回一个不变的ref
对象(只读)
const firstName = ref("Kobe");
const lastName = ref("Bryant");
const fullName = computed(() => firstName.value + " " + lastName.value);
写法二:
接收一个具有get
和set
的对象,返回一个可变的(可读写)ref
对象
const firstName = ref("Kobe");
const lastName = ref("Bryant");
const fullName = computed({
get: () => firstName.value + " " + lastName.value,
set(newValue) {
const names = newValue.split(" ");
firstName.value = names[0];
lastName.value = names[1];
}
});
setup中使用ref
这里的ref
指的是我们用来获取组件或者元素的ref
属性
我们需要定义一个ref对象,然后绑定到元素和组件的ref属性上即可(注意这里不能动态绑定该属性)
const title = ref(null);
<h2 ref="title">哈哈哈</h2>
我们使用title.value
就可以获取该元素dom
对象了
数据的侦听
在Composition API
中,我们可以使用watchEffect
和watch
来完成响应式数据的侦听
watchEffect基本使用
watchEffect
传入的函数会被立即执行一次,并且在执行的过程中会收集依赖,只有收集的依赖发生变化时,watchEffect
传入的函数才会再次执行
setup() {
const name = ref("why");
const age = ref(18);
const changeName = () => name.value = "kobe"
const changeAge = () => age.value++
watchEffect(() => {
console.log("name:", name.value, "age:", age.value);
});
结果是控制台先打印一次,然后修改操作执行时再执行
watchEffect
是自动收集响应式数据的依赖,函数内部会自动检测收集响应式的对象,不用我们自己去指定
watchEffect的停止监听watchEffect
函数会返回一个函数,我们执行该函数就可以停止监听
const name = ref("why");
const age = ref(18);
const stop = watchEffect(() => {
console.log("name:", name.value, "age:", age.value);
});
//执行该函数就会停止监听
stop()
watchEffect清除副作用
有时候我们监听某个数据发生变化再发送网络请求或者其他耗时的操作,但是我们在请求结果还未达到等情况时又触发了侦听,或者停止了侦听器,我们应该把上次的网络请求任务取消掉,这就是副作用
我们可以在给传入watchEffect
的回调函数中添加一个参数,我们一般命名为onInvalidate
该参数是一个函数,接收一个回调函数,当副作用即将重新执行或者侦听器被停止时会执行该函数传入的回调函数,我们就可以在该回调函数中做一些清除工作,如取消监听,清除定时器等
// watchEffect: 自动收集响应式的依赖
const name = ref("zhangsan");
const age = ref(18);
const stop = watchEffect((onInvalidate) => {
const timer = setTimeout(() => {
console.log("网络请求成功~");
}, 2000)
// 根据name和age两个变量发送网络请求
onInvalidate(() => {
// 在这个函数中清除额外的副作用
clearTimeout(timer);
console.log("onInvalidate");
})
console.log("name:", name.value, "age:", age.value);
});
watchEffect的执行机制
实例代码如下:
<h2 ref="title">哈哈哈</h2>
setup() {
const title = ref(null);
watchEffect(() => {
console.log(title.value);
})
return {
title
}
}
}
默认情况下组件的更新会在副作用函数执行之前,比如我们在watchEffect
中通过ref
属性获取元素dom
,因为watchEffect
传入的回调函数会默认立即执行一次,但这时候dom
并没有挂载,所以打印对象为null
,当组件挂载完毕后为给我们title.value
赋值,就会触发侦听更新然后再次打印, 结果如下
我们希望第一次打印的时候就获取到dom
元素,所以我们就需要修改副作用函数的执行时机watchEffect
函数的第二个参数就是一个配置对象,里面有个属性flush
有三个值可设置
- pre 默认值,会在元素挂载或者更新前就会执行
- post 会在组件的首次渲染完成后执行
- sync 强制效果始终同步触发。然而,这是低效的,应该很少使用
setup() {
const title = ref(null);
watchEffect(() => {
console.log(title.value);
}, {
flush: "post"
})
return {
title
}
}
}
这样第一次就可以获取dom
元素了
watch
watch
的API完全等同于组件watch
选项的Property
watch
需要侦听特定的数据源,并在回调函数中执行副作用
和watchEffect
相比,watch
是惰性的,默认只在侦听源发生改变才会执行(第一次不会执行),而且能够更详细的说明当那些状态发生改变时,触发侦侦听器执行,当然可以访问侦听源的变化前后的值
侦听单个数据源
watch
侦听函数的数据源有两种类型:
这里以一个按钮修改name来演示侦听结果
const changeData = () => {
info.name = "kobe";
//name.value = "kobe";
}
一个是getter函数,该函数必须返回可响应式的对象(reactive
/ref
)
const info = reactive({name: "why", age: 18});
watch(() => info.name, (newValue, oldValue) => {
console.log("newValue:", newValue, "oldValue:", oldValue);
})
输出结果:
另一个是直接传入一个可响应式的对象(reactive
/ref
)一般为ref
watch(info, (newValue, oldValue) => {
console.log("newValue:", newValue, "oldValue:", oldValue);
},)
我们可以发现上面的输出结果还是代理对象,如果我们希望newValue
和oldValue
是一个普通的对象,可以使用getter函数返回响应式对象的副本
watch(() => {
return {...info}
}, (newValue, oldValue) => {
console.log("newValue:", newValue, "oldValue:", oldValue);
})
监听一个ref
对象 ,ref
对象获取newValue
和oldValue
是value
值的本身
const name = ref("why");
watch(name, (newValue, oldValue) => {
console.log("newValue:", newValue, "oldValue:", oldValue);
})
侦听多个数据源
使用数组对多个数据源进行侦听
const info = reactive({name: "why", age: 18});
const name = ref("why");
watch([() => ({...info}), name], ([newInfo, newName], [oldInfo, oldName]) => {
console.log(newInfo, newName, oldInfo, oldName);
})
watch的选项
我们可以设置深层次的监听和立即执行监听
watch(name, (newValue, oldValue) => {
console.log("newValue:", newValue, "oldValue:", oldValue);
}, {deep:true, immediate:true});
生命周期函数
setup
函数中也有特定的函数来代替以往的options API
函数,这些函数都是以onX
命名
我们用的时候需要自行导入
import { onMounted, onUpdated, onUnmounted, ref } from 'vue';
onMounted(() => {
console.log("App Mounted1");
})
onMounted(() => {
console.log("App Mounted2");
})
onUpdated(() => {
console.log("App onUpdated");
})
onUnmounted(() => {
console.log("App onUnmounted");
})
注意setup
函数 是围绕 beforeCreate
和 created
生命周期钩子运行的,所以不需要显式地定义它们。换句话说,在这些钩子中编写的任何代码都应该直接在 setup
函数中编写。
Provide和inject
setup
中也可以编写provide
和inject
使用provide
我们需要显式的导入该函数
provide
函数允许你通过两个参数定义 property
:
- name(String类型)
- value
import { provide } from 'vue'
setup() {
provide('location', 'North Pole')
provide('geolocation', {
longitude: 90,
latitude: 135
})
}
使用inject
也需要显式的导入函数
inject
函数也有两个参数:
- 要
inject
的property
的name
- 默认值 (可选)
import { inject } from 'vue'
const userLocation = inject('location', 'The Universe')
const userGeolocation = inject('geolocation')
为了增加 provide
值和 inject
值之间的响应性,我们可以在 provide
值时使用ref
或 reactive
const location = ref('North Pole')
const geolocation = reactive({
longitude: 90,
latitude: 135
})
provide('location', location)
provide('geolocation', geolocation)
另外我们修改响应式的数据最好在数据的提供位置来修改,比如将修改的方法共享,在后代组件中调用