组合式api
setup
// setup 的结构,
setup(props, { slots, attrs, emit }) {
let nameRef = ref('dj')
// 里面的数据需要 return 出去,才能在外面使用
return {
name: nameRef
}
}
ref知识点
- ref
一般用于定义基本数据类型数据 返回了 { value: xxx } 这样的结构的响应式数据 在 setup 中使用 .value 来访问 在视图中直接使用,不用加 .value - isRef 检查某个值是否为 ref
- unref 如果参数是 ref,则返回内部值,否则返回参数本身。这是 val = isRef(val) ? val.value : val 计算的一个语法糖。
const count = ref(0); // { value: 0 }
isRef(count); // true
unref(count); // 0
unref("world"); // world
customRef
customRef 可以创建一个自定义的 ref,显式声明对其依赖追踪和更新触发的控制方式。
在get中可以调用 track() 方法
在set中可以调用 trigger() 方法
<template>
<input v-model="name" />
</template>
<script lang="ts" setup>
import { customRef, onRenderTracked, onRenderTriggered } from "vue";
const name = customRef((track, trigger) => {
let val = "";
return {
get() {
track(); // 如果有人访问name,就执行 track() 方法
return val;
},
set(v) {
val = v;
trigger(); // 如果有人修改name,就执行 trigger() 方法
},
};
});
// 仅供开发时调试 ref 时使用
onRenderTracked((e) => {
console.log('name被访问了 ‘, e);
});
onRenderTriggered((e) => {
console.log('name被修改了 ', e)
});
</script>
reactive
定义响应式变量,一般用于定义引用数据类型。如果是基本数据类型,建议使用ref来定义。
reactive 生成的响应式的对象,修改里面的值时,不需要加value了
// computed中使用data.count会造成类型推理的循环,由于ts的局限性vue3暂时无法解决这个问题;解决方案是我们需要显式的给data指定一个类型
const data: DataProps = reactive({
count: 0,
increase: () => {
data.count++;
},
double: computed(() => data.count * 2),
});
toRef
把一个 reactive对象中的某个属性变成 ref 变量
import { toRef, reactive, isRef } from 'vue';
const obj = reactive({
name: 'dj',
age: 18
});
const name= toRef(obj, 'name');
console.log(isRef(name)); // true
toRefs
将一个响应式对象转换为一个普通对象,这个普通对象的每个属性都是指向源对象相应属性的 ref。每个单独的 ref 都是使用 toRef() 创建的。
可以用来解决父组件传过来的props数据解构问题。
import { toRefs, reactive, isRef } from 'vue';
const obj = reactive({
user: 'dj',
age: 18
});
const { user, age } = toRefs(obj);
console.log(isRef(user));
console.log(isRef(age));
shallowRef
对复杂层级的对象,只将其第一层变成 ref 响应。 (性能优化)
<template>
<button @click="handle">修改</button>
<div>{{ obj.a }}</div
</template>
<script lang="ts" setup>
import { shallowRef, isRef, triggerRef } from 'vue';
let obj = shallowRef({ a: { b: { c: 1 } }, d: 2 });
console.log(isRef(obj));
console.log(isRef(obj.value.a));
console.log(isRef(obj.value.a.b));
const handle = () => {
obj.value.a.b.c ++;
triggerRef(obj); // 修改加上这一句才会更新
}
</script>
triggerRef
强制更新一个 shallowRef 对象的渲染,例子看上一个知识点。
computed
接受一个 getter 函数,返回一个只读的响应式 ref 对象。该 ref 通过 .value 暴露 getter 函数的返回值。它也可以接受一个带有 get 和 set 函数的对象来创建一个可写的 ref 对象。
重点:computed 的返回值是一个 ref 对象
import { ref, computed } from 'vue';
const count = ref(1);
// 默认只有get方法
const double = computed(() => count.value * 2);
// 可以添加 get set 方法
const doubleCount = computed({
get() {
return count.value * 2;
},
set(val) {
count.value = val;
}
})
watch
可以侦听单一源和多个源
// 一个源
import { ref, watch } from 'vue';
const count = ref(0);
watch(count, (newCount, oldCount) => {})
// 多个源
import { ref, watch } from 'vue';
const a = ref(0);
const b = ref(1);
watch([a, b], ([newA, newB], [oldA, oldB]) => {})
watch 还可以添加第三个参数,第三个可选的参数是一个对象,支持以下这些选项
- immediate 在侦听器创建时立即触发回调。第一次调用时旧值是 undefined。
- deep 如果源是对象,强制深度遍历,以便在深层级变更时触发回调
- flush 调整回调函数的刷新时机。
- onTrack / onTrigger 调试侦听器的依赖。
watch 的返回值是一个停止监听的方法
const stop = watch(count, (newCount, oldCount) => {
console.log(count, newCount, oldCount);
})
// 当已不再需要该侦听器时
stop()
watchEffect
相当于是 react中的 useEffect(),用于执行各种副作用。
const stop = watchEffect(fn),默认其 flush:'pre',前置执行的副作用。
watchPostEffect,等价于 watchEffect(fn, {flush:'post'}),后置执行的副作用。
watchSyncEffect,等价于 watchEffect(fn, {flush:'sync'}),同步执行的副作用。
watchEffect 会自动收集其内部响应式依赖,当响应式依赖发变化时,这个watchEffect将再次执行,直到你手动 stop() 掉它。
<template>
<h1 v-text='num'></h1>
<button @click='stopAll'>停止掉所有的副作用</button>
</template>
<script setup>
import { ref, watchEffect } from 'vue'
let num = ref(0)
// 等价于 watchPostEffect
const stop1 = watchEffect(()=>{
// 在这里你用到了 num.value
// 那么当num变化时,当前副作用将再次执行
// 直到stop1()被调用后,当前副作用才死掉
console.log('---effect post', num.value)
}, { flush:'post'} )
// 等价于 watchSyncEffect
const stop2 = watchEffect(()=>{
// 在这里你用到了 num.value
// 那么当num变化时,当前副作用将再次执行
// 直到stop2()被调用后,当前副作用才死掉
console.log('---effect sync', num.value)
}, { flush:'sync'})
const stop3 = watchEffect(()=>{
// 如果在这里用到了 num.value
// 你必须在定时器中stop3(),否则定时器会越跑越快!
// console.log('---effect pre', num.value)
setInterval(()=>{
num.value++
}, 1000)
})
const stopAll = () => {
stop1()
stop2()
stop3()
}
</script>
provide / inject
在组件树中通信
// 祖先组件
<script setup>
import { ref, provide } from 'vue'
const msg = ref('Hello World')
// 向组件树中注入数据
// 第一个参数是要注入的 key,可以是一个字符串或者一个 symbol,第二个参数是要注入的值
provide('msg', msg)
</script>
// 后代组件
<script setup>
import { inject } from 'vue'
/* 第一个参数是注入的 key。Vue 会遍历父组件链,通过匹配 key 来确定所提供的值。
* 如果父组件链上多个组件对同一个 key 提供了值,那么离得更近的组件将会“覆盖”链上更远的组件所提供的值。
* 如果没有能通过 key 匹配到值,`inject()` 将返回 `undefined`,除非提供了一个默认值。
* 第二个参数是默认值
*/
const msg = inject('msg', 'Hello Vue')
</script>
通信
父子组件通信
在父组件申明数据,通过v-bind传到子组件,子组件通过emit通知父组件。
方法一:
// 父组件
<template>
<Children :num="num" @changeNum="changeNum"> </Children>
</template>
<script>
setup() {
let num = ref(0);
const changeNum = (val) => {
num.value = val;
};
return {
num,
changeNum
}
}
</script>
// 子组件
<template>
<div>{{ num }}</div>
<button @click="handleChange">改变num的值</button>
</template>
<script>
props: {
num: {
type: Number,
},
},
setup(props, context) {
const handleChange = () => {
context.emit("changeNum", props.num + 1);
};
return {
handleChange,
};
},
</script>
方法二:
// 父组件
<template>
<Children :num="num" @changeNum="changeNum"></Children>
</template>
<script>
setup() {
const obj = reactive({
num: 0,
changeNum: (val) => {
console.log("父组件触发: ", val);
obj.num = val;
},
});
return {
...toRefs(obj),
}
}
</script>
// 子组件
<template>
<div>{{ num }}</div>
<button @click="handleChange">改变num的值</button>
</template>
<script>
props: {
num: {
type: Number,
},
},
setup(props, context) {
const obj = reactive({
handleChange: () => {
console.log(props.num);
context.emit("changeNum", props.num + 1);
},
});
return {
...toRefs(obj),
}
},
</script>