上篇由于时间太晚了,就没有写了,这篇接着上篇的续写, Vue3中的composition API的知识总结(一)
菜鸡的码农还是要拼命的奋斗啊。。
toRefs()
将响应式对象转换为普通对象,其中结果对象的每个 property 都是指向原始对象相应 property 的 ref
。
见名知意:· 把什么转化成ref对象
目的:
所以在,开发过程中,我们有时候会对对象进行结构
const obj = reactive({name: 'james', age: '21'})
let { name, age } = obj
//但是这样的结构是,name 和 age 都变成了普通的对象, 就不在是响应式的了
基本使用:
import { toRefs } from 'vue'
setup() {
const info = reactive<InfoType>({name: 'james', age: 12})
const {name, age} = toRefs(info)
const btn = () => {
// ref 和 reactive之间建立连接,所以下面两句是等价的,都是修改同一个地址中的值
info.age++
age.value++
}
return {
age,
btn
}
}
解释:
- 上面的代码中,
age的默认值为12
,但是当执行之后,age的值变为14
toRefs
将ref
和reactive
之间建立连接,执行同一个地址
源码:
function toRefs(object) {
//提示必须是一个reactive对象
if (!isProxy(object)) {
console.warn(`toRefs() expects a reactive object but received a plain one.`);
}
const ret = shared.isArray(object) ? new Array(object.length) : {};
for (const key in object) {
//toRefs 就是通过toRef 循环实现的
ret[key] = toRef(object, key);
}
return ret;
}
通过源码分析, toRefs
就是对reactive对象进行循环调用toRef()
看下面的解释的toRef函数
toRef()
可以用来为源响应式对象上的某个 property 新创建一个 ref
。然后,ref 可以被传递,它会保持对其源 property 的响应式连接。
基本使用:
//toRef 两个参数
//第一个参数: reactive对象
//第二个参数: 转化为响应式的属性名
const state = reactive({
foo: 1,
bar: 2
})
const fooRef = toRef(state, 'foo')
fooRef.value++
console.log(state.foo) // 2
state.foo++
console.log(fooRef.value) // 3
源码:
function toRef(object, key) {
return isRef(object[key])
? object[key]
: new ObjectRefImpl(object, key); //新建一个响应式属性
}
unRef()
-
如果参数是一个
ref
,则返回内部值,否则返回参数本身。 -
这是
val = isRef(val) ? val.value : val
的语法糖函数。
isRef()
检查值是否为一个 ref 对象。
customRef()
创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。它需要一个工厂函数,该函数接收 track
和 trigger
函数作为参数,并且应该返回一个带有 get
和 set
的对象。
shallowRef()
创建一个跟踪自身 .value
变化的 ref,但不会使其值也变成响应式的。
就是如果ref传递的一个复杂数据类型,不会对复杂数据类型进行监听。
基本使用:
//shallowRef 浅层监听
const info = shallowRef({name: 'james'})
const changeAge = () => {
info.value.name = 'kobe' //这里深度修改了name的值,不在在UI上更新
triggerRef(info) //triggerRef就是用来强制跟新shallowRef的
}
triggerRef()
与shallowRef()配套使用
shallowRef()
不让界面更新,triggerRef()
就是强制让页面更新。
computed()
跟Options API的computed属性是一样的效果,就是进行一些数据进行,计算转变。
基本使用:
这里没有使用computed函数
,那么fullName就不是响应式的
setup() {
const firstName = ref('12')
const lastName = ref('34')
//这里firstName和lastName都是响应式的,但是fullName不是响应式的,不能这样写
const fullName = firstName + lastName
}
使用computed函数
, 参数可以传递一个函数,或则对象的形式
函数的形式:
const firstName = ref('lebron')
const lastName = ref('james')
const fullName = computed(() => firstName.value +"_"+ lastName.value)
对象形式:
const fullName = computed({
get: () => firstName.value + '_' + lastName.value,
set: (newValue) => {
//改变值,修改逻辑
}
})
watchEffect()
在响应式地跟踪其依赖项时立即运行一个函数,并在更改依赖项时重新运行它。
特点:
-
立即会执行一次
-
自动收集依赖
const name = ref('jamse') const age = ref(12) //这个函数会最开始执行一次的,目的存在也是收集依赖,放到一个数组中 watchEffect(() => { console.log(name.value) })
上面的
watchEffect
中,使用name属性,那么就收集了name, 当name发生改变时,就会执行, 改变age,就不会执行,因为age没有在收集的依赖中
类型声明:
interface WatchEffectOptions {
flush?: 'pre' | 'post' | 'sync' // 默认:'pre'
onTrack?: (event: DebuggerEvent) => void
onTrigger?: (event: DebuggerEvent) => void
}
interface DebuggerEvent {
effect: ReactiveEffect
target: any
type: OperationTypes
key: string | symbol | undefined
}
type InvalidateCbRegistrator = (invalidate: () => void) => void
type StopHandle = () => void
function watchEffect(
effect: (onInvalidate: InvalidateCbRegistrator) => void,
options?: WatchEffectOptions
): StopHandle
分析上面的定义类型:
-
StopHandle:
watchEffect函数的返回值是返回一个停止监听函数
//watchEffect返回一个停止侦听的函数 const stop = watchEffect(() => { console.log(age.value) }) //上面我是监听的age响应式数据 //如果当age到了25岁时,就不在侦听了 if(age.value >=25) { stop() //执行stop函数,就停止侦听 }
-
onInvalidate
: 清除副作用假如,我们在这个函数里面发送网络请求,当age改变一次,发送一次,当age改变的过快时,上一个请求就没执行完,又执行下一次了,所以,这里我们就需要清除上一次的副作用
在我们给watchEffect传入的函数被回调时,其实我们可以可以获取到一个参数: onInvalidata
watchEffect((onInvalidata) => { //onInvalidata就是一个清除副作用的函数 //onInvalidata每次都会执行一次 const timer = setTimeout(() => { console.log('网络请求') }, 1000) onInvalidata(() => { //这里面进行相应的副作用操作 clearTimeout(timer) }) console.log(age.value) })
-
WatchEffectOptions
: 配置项,用的时候,再去查看,这里就不深究了(其实,是我不知道,还没有用到过)
watch()
跟Options API的特性是一样的。
特点:
- 惰性地执行副作用;
- 更具体地说明应触发侦听器重新运行的状态;
- 访问被侦听状态的先前值和当前值。
监听单个数据源
写法一: 传入一个getters函数
const info = reactive({name: 'james', age: 12})
//当监听某一个属性的时候 (监听info.name的改变)
watch(() => info.name, (newValue, oldValue) => {
console.log(newValue, oldValue)
})
写法二: 传入一个可响应式对象: reactive 和 ref(比较常见ref)
情况一: reactive对象获取到的newValue
和oldValue
本身都是reactive对象
setup() {
const info = reactive({name: 'james', age: 12})
//直接监听reactive本身
watch(info, (newValue, oldValue) => {
console.log(newValue, oldValue);
//newValue, oldValue的值是一样的,都是指向同一个地址
})
const btn = () => { //执行btn的时候,会触发watch函数
info.name = 'kobe'
info.age = 13
}
return {
...toRefs(info),
btn
}
}
情况二: 监听reactive的普通对象(应该很少使用吧)
//如果我们想拿到普通对象
watch(() => {
return {...info}
}, (newValue, oldValue) => {
console.log(newValue, oldValue)
//newValue, oldValue 都是普通对象
})
情况三: ref对象获取到的newValue和oldValue都是value值的本身
setup() {
const info2 = ref('james')
watch(info2, (newValue, oldValue) => {
//拿到的都是value值
console.log(newValue, oldValue); // kobe james
})
const btn = () => {
info2.value = 'kobe'
}
return {
info2,
btn
}
}
监听多个数据源
传递一个数组,源码内部自己遍历,在判断是reactive或则是ref
const info = reactive({name: 'james'})
const name = ref('21')
watch([info, name], (newValue, oldValue) => {
console.log(newValue, oldValue)
//newValue, oldValue都是数组
})
深度侦听
特点:
-
对于reactive响应式数据,默认就有深度侦听
-
但是对于结构reactive后的对象,是没有深度侦听的, 需要配置deep属性
setup() {
const info = reactive({name: 'james', age: 12, kk: {name: 'kk'}})
const info2 = ref('james')
watch([info, info2], (newValue, oldValue) => {
console.log(newValue, oldValue);
}, {
immediate: true, //理解执行
// deep: true //深度监听
})
const btn = () => {
// info.name = 'kobe'
// info.age = 13
info.kk.name = 'BB'
// info2.value = 'kobe'
}
return {
...toRefs(info),
info2,
btn
}
}
针对于普通对象
const info = reactive({name: 'james'})
const name = ref('21')
watch(() => {
return {...info} //普通对象
}, (newValue, oldValue) => {
console.log(newValue, oldValue)
}, {
deep: true, //深度侦听
immediate: true //立即执行
})
生命周期函数
因为
setup
是围绕beforeCreate
和created
生命周期钩子运行的,所以不需要显式地定义它们。换句话说,在这些钩子中编写的任何代码都应该直接在setup
函数中编写。
在setup中多次使用同一个生命周期,会进行合并。
生命钩子函数接受一个函数作为参数
setup() {
onMounted(() => {
console.log('第一个onMounted执行');
})
onMounted(() => {
console.log('第二个onMounted执行');
})
}
provide() 和 inject()
基本用法:
-
provide
: 提供共享数据provide
函数允许你通过两个参数定义 property:- name (
<String>
类型) - value
- name (
-
inject
: 注入共享数据inject
函数有两个参数:- 要 inject 的 property 的 name
- 默认值 (可选)
案例演示:
父组件
//Father.vue
import { provide, ref } from 'vue'
export default {
setup() {
let name = ref('james')
let count = ref(1)
provide('name', name) //第一个参数标识符, 第二个参数为value值
provide('count', count)
}
}
子组件
//Son.vue
import { inject } from 'vue'
export default {
setup() {
const name = inject ('name', 'kobe') //第二个参数为默认值,如果父组件没有提供
const count = inject('count')
}
//这里的count也是一个ref对象
//警告:千万不要在子组件修改父组件提供的值(必须要符合单向数据流)
//原因:如果多个子组件只用的话,就是兄弟之间相互修改了(编程之大忌)
}
优化父组件
使用readonly包裹数据,修改的时候,就会直接报错
import { provide, ref, readonly } from 'vue'
export default {
setup() {
let name = ref('james')
let count = ref(1)
provide('name', readonly(name)) //第一个参数标识符, 第二个参数为value值
provide('count', readonly(count))
}
}
ref拿取DOM
在Vue2中,我们就是通过ref来拿取DOM节点,那么Vue3该如何拿取节点呢? 还是通过ref函数来获取
代码示例:
setup() {
const titleRef = ref<HTMLDivElement>()
const btn = () => {
console.log(titleRef.value)
}
return {
titleRef,
btn
}
}
这里还可以使用watchEffect()
watchEffect(() => {
console.log(titleRef.value);
//这样就拿到div这个节点
}, { //flush有三个属性值
flush: "post" //默认的时候,延后执行,改变的时候在执行
//默认值为pre //默认会立即执行,当改变的时候,也会执行
//还有一个值为sync 同步触发(少用,不推荐,低效)
})
总结
写到这里,我感觉Vue3有太多的知识了,现在只是随便笔记,看下官网,就花费了我两天的时间,还有很多的新特性没有总结到。如果以后,使用到了,在补充吧。现在的我,对Vue3的新知识,还不是很熟练的掌握,需要长期的练习,加油吧!