Vue 组合式API
Vue3.2 中的 setup 语法糖,保证你看的明明白白! (qq.com)
一文掌握 vue3.2 setup 语法糖 (qq.com)
setup
setup 选项
<script>
import { ref } from "vue";
export default {
props: {
title: String
},
// 把选项式APIh和绑定在this的数据对象通过参数提供
setup(props, { emit, expose, attrs, slots }) {
// 定义响应式数据
const count = ref(0);
const list = reactive([1, 2, 3]);
// 定义方法
function increment() {
count.value++;
list[0]++;
}
// 访问props
console.log(props.title);
// 不向外部组件暴露
expose();
// 有选择地暴露
expose({ count: count });
// 返回值会暴露给模板和其他的选项式 API 钩子
return {
count,
list,
increment
};
},
mounted() {
console.log(this.count); // 0
},
};
</script>
<template>
<button @click="count++">{{ count }}</button>
</template>
<script setup>
推荐使用 <script setup>
语法,比 setup 选项更加简洁易用。下文均以 <script setup>
语法作为示例。
<script setup>
// 引入对象
import { capitalize } from './helpers'
// 引入组件
import Foo from './Foo.vue'
import Bar from './Bar.vue'
// 变量
const msg = 'Hello!'
// 函数
function log() {
console.log(msg)
}
</script>
<template>
<div>{{ capitalize('hello') }}</div>
<button @click="log">{{ msg }}</button>
<!-- 静态组件 -->
<Foo />
<!-- 动态组件 -->
<component :is="Bar" />
</template>
响应式
其他高级API没有罗列出来,参考:响应性 API:进阶 | Vue.js (vuejs.org)
ref()
定义响应式基本数据,对应 data 选项。
<template>
<!-- 模板中正常使用,不用加.value -->
<button @click="increment()">{{ num }}</button>
</template>
<script setup>
import { ref } from 'vue'
const num = ref(0);
const str = ref("abc");
const boo = ref(true);
function increment() {
// 使用时需要加.value
num.value++;
str.value += num.value;
boo.value = !boo.value;
}
</script>
获取DOM元素或子组件的引用,对应 this.$refs。
<template>
<p ref="p">hello</p>
</template>
<script setup>
import { ref } from 'vue'
const p = ref();
</script>
isRef()
检查某个值是否为 ref。
<script setup>
import { ref, isRef } from 'vue'
const num = ref(1);
const count = 2;
console.log(isRef(num)); // true
console.log(isRef(count)); // false
</script>
unref()
如果参数是 ref,则返回内部值,否则返回参数本身。
<script setup>
import { ref, unref } from 'vue'
const num = ref(1);
const count = 2;
console.log(unref(num)); // 1
console.log(unref(count)); // 2
</script>
toRef()
可用于为响应式对象上的 property 创建 ref。这样创建的 ref 与其源 property 保持同步。
<script setup>
import { reactive, toRef } from 'vue'
const state = reactive({
foo: 1,
bar: 2
});
const fooRef = toRef(state, 'foo');
// 更改该 ref 会更新源属性
fooRef.value++;
console.log(state.foo); // 2
// 更改源属性也会更新该 ref
state.foo++;
console.log(fooRef.value); // 3
</script>
toRefs()
将一个响应式对象转换为一个普通对象,这个普通对象的每个 property 都是指向源对象相应 property 的 ref。
<script setup>
import { reactive, toRefs } from 'vue'
const state = reactive({
foo: 1,
bar: 2
});
// 直接将生成的普通对象解构,每个属性都是ref
const {foo, bar} = toRefs(state);
// 更改该 ref 会更新源属性
foo.value++;
console.log(state.foo); // 2
// 更改源属性也会更新该 ref
state.foo++;
console.log(foo.value); // 3
</script>
reactive()
定义响应式对象,对应 data 选项。
<template>
<button @click="increment()">{{ list[0] }}</button>
</template>
<script setup>
import { ref, reactive } from 'vue'
const list = reactive([1, 2, 3]);
const obj = reactive({ a: 1 });
function increment() {
list[0]++;
obj.a++;
}
</script>
ref 在 reactive 中的解包
<script setup>
import { ref, reactive } from 'vue'
// 普通对象,ref自动解包,即不需要.value
const obj = reactive({ a: ref(1) });
obj.b = ref(2);
console.log(obj.a); // 1
console.log(obj.b); // 2
// 数组和Map,ref不会解包,需要.value
const books = reactive([ref('Vue 3 Guide')])
console.log(books[0].value)
const map = reactive(new Map([['count', ref(0)]]))
console.log(map.get('count').value)
</script>
isReactive()
检查一个对象是否是由 reactive() 或 shallowReactive() 创建的代理。
<script setup>
import { ref, reactive, isReactive } from 'vue'
const a = ref(0);
const b = reactive({ a: 1 });
console.log(isReactive(a)); // false
console.log(isReactive(b)); // true
</script>
readonly()
接受一个对象 (不论是响应式还是一般的) 或是一个 ref,返回一个原值的只读代理。
<script setup>
import { reactive, readonly } from 'vue'
const original = reactive({ count: 0 })
const copy = readonly(original)
// 源对象的属性值可改
original.count++;
// 只读代理的属性不可更改,更改会失败并会得到一个警告
copy.count++; // warning!
</script>
isReadonly()
检查一个对象是否是由 readonly() 或 shallowReadonly() 创建的代理。
<script setup>
import { ref, readonly, isReadonly } from 'vue'
const a = ref(0);
const b = readonly(a);
console.log(isReadonly(a)); // false
console.log(isReadonly(b)); // true
</script>
isProxy()
检查一个对象是否是由 reactive()、readonly()、shallowReactive() 或 shallowReadonly() 创建的代理。
<script setup>
import { ref, reactive, readonly, isProxy } from 'vue'
const a = ref(0);
const b = reactive({ a: 1 });
const c = readonly(a);
console.log(isProxy(a)); // false
console.log(isProxy(b)); // true
console.log(isProxy(c)); // true
</script>
功能方法
defineProps()
对应 props 选项。
<template>
<!-- 使用props数据 -->
<h1>{{ msg }}</h1>
</template>
<script setup>
import { defineProps } from 'vue'
// 无类型推断
const props = defineProps(["msg"]);
// vue类型推断
const props = defineProps({
msg: String,
});
// TypeScript类型推断
const props = defineProps<{
msg: string;
}>();
// 使用props数据
console.log(props.msg);
</script>
defineEmits()
对应 emits 选项。
<script setup>
import { defineProps } from 'vue'
// 无类型推断
const emit = defineEmits(['change', 'update'])
// TypeScript类型推断
const emit = defineEmits<{
(e: "change", id: number): void;
(e: "update", value: string): void;
}>();
function handle() {
emit('change', 1001);
}
</script>
defineExpose()
对应 expose 选项。
<script setup>
import { ref, defineExpose } from 'vue'
const a = 1
const b = ref(2)
// 指定暴露的属性方法,默认全部不暴露
defineExpose({ a, b });
</script>
nextTick()
对应 this.$nextTick() 。
<script setup>
import { reactive, nextTick } from 'vue'
const state = reactive({ count: 0 });
function increment() {
state.count++
nextTick(() => {
// 访问更新后的 DOM
});
}
</script>
computed()
计算属性,对应 computed 选项。
<script setup>
import { ref, nextTick } from 'vue'
const count = ref(1);
// 只读计算属性
const countDouble = computed(() => count.value + 1, {
// 调试事件,只在开发模式下工作
onTrack(e) {
// 当 count.value 被追踪为依赖时触发
},
onTrigger(e) {
// 当 count.value 被更改时触发
}
});
// 可写计算属性
const countPlus = computed({
get: () => count.value + 1,
set: (val) => {
count.value = val - 1
}
})
</script>
watch()
侦听一个或多个响应式数据源,并在数据源变化时调用所给的回调函数。
<script setup>
import { ref, reactive, watch } from 'vue'
const foo = ref(0);
const bar = reactive({ count: 0 });
// 监听单个数据源
watch(foo, (newFoo, oldFoo, onCleanup) => {
/* ... */
},// 可选选项
{
// 是否创建时立即触发回调,默认false
immediate: false,
// 数据源是对象是,是否深层级监听变更,默认true
deep: true,
// 刷新时机:pre 组件更新之前(默认),post 组件更新之后,sync 异步
flush: 'pre',
// 当被追踪为依赖时触发
onTrack(e) {},
// 当依赖更改时触发
onTrigger(e) {},
});
// 监听多个数据源
watch([foo, bar], ([foo, bar], [prevFoo, prevBar]) => {
/* ... */
})
</script>
watchEffect()
立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行。
<script setup>
import { ref, watchEffect } from 'vue'
const count = ref(1);
// 创建一个侦听器
const unwatch = watchEffect((onCleanup) => {
console.log(count.value);
// 清理回调会在该副作用下一次执行前被调用
onCleanup(()=> {
console.log("清理回调");
});
},
// 可选选项
{
// 刷新时机:pre 组件更新之前(默认),post 组件更新之后,sync
flush: 'pre',
// 当被追踪为依赖时触发
onTrack(e) {},
// 当依赖更改时触发
onTrigger(e) {}
});
// 停止侦听器
unwatch()
</script>
watchPostEffect()
watchEffect() 使用 flush: 'post'
选项时的别名。
watchSyncEffect()
watchEffect() 使用 flush: 'post'
选项时的别名。
provide()
供给一个值,可以被后代组件注入。
<script setup>
import { ref, provide } from 'vue'
import { fooSymbol } from './injectionSymbols'
// 供给静态值
provide('foo', 'bar')
// 供给响应式的值
const count = ref(0)
provide('count', count)
// 供给时将 Symbol 作为 key
provide(fooSymbol, count)
</script>
inject()
<script setup>
import { inject } from 'vue'
import { fooSymbol } from './injectionSymbols'
// 注入值的默认方式
const foo = inject('foo')
// 注入响应式的值
const count = inject('count')
// 通过 Symbol 类型的 key 注入
const foo2 = inject(fooSymbol)
// 注入一个值,若为空则使用提供的默认值
const bar = inject('foo', 'default value')
// 注入一个值,若为空则使用提供的工厂函数
const baz = inject('foo', () => new Map())
// 注入时为了表明提供的默认值是个函数,需要传入第三个参数
const fn = inject('function', () => {}, false)
</script>
useSlots()
对应 this.$slots 。
<script setup>
import { useSlots } from 'vue'
const slots = useSlots();
</script>
useAttrs()
对应 this.$attrs 。
<script setup>
import { useAttrs } from 'vue'
const attrs = useAttrs();
</script>
useRoute()
<script setup>
import { useRoute } from 'vue-router'
const route = useRoute();
</script>
useRouter()
<script setup>
import { useRouter } from 'vue-router'
const router = useRouter();
</script>
useStore()
<script setup>
import { useStore } from 'vuex'
const store = useStore();
</script>
生命周期钩子
onBeforeMount()
注册一个钩子,在组件被挂载之前被调用。
<script setup>
import { onBeforeMount } from 'vue'
onBeforeMount(() => {
// 组件完成了其响应式状态的设置,但还没有创建 DOM 节点
});
</script>
onMounted()
注册一个回调函数,在组件挂载完成后执行。
<template>
<div ref="el"></div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
const el = ref()
onMounted(() => {
el.value // <div>
})
</script>
onBeforeUpdate()
注册一个钩子,在组件即将因为响应式状态变更而更新其 DOM 树之前调用。
<script setup>
import { onBeforeUpdate } from 'vue'
onBeforeUpdate(() => {
// 在 Vue 更新 DOM 之前访问 DOM 状态
});
</script>
onUpdated()
注册一个回调函数,在组件因为响应式状态变更而更新其 DOM 树之后调用。
<template>
<button id="count" @click="count++">{{ count }}</button>
</template>
<script setup>
import { ref, onUpdated } from 'vue'
const count = ref(0)
onUpdated(() => {
// 文本内容应该与当前的 `count.value` 一致
console.log(document.getElementById('count').textContent)
})
</script>
onBeforeUnmount()
注册一个钩子,在组件实例被卸载之前调用。
<script setup>
import { onBeforeUnmount } from 'vue'
onBeforeUnmount(() => {
// 组件实例依然还保有全部的功能
});
</script>
onUnmounted()
注册一个回调函数,在组件实例被卸载之后调用。
<script setup>
import { onUnmounted } from 'vue'
let timer = setInterval(() => {
// ...
}, 5000);
onUnmounted(() => {
// 手动清理一些副作用
clearInterval(intervalId)
});
</script>
onErrorCaptured()
注册一个钩子,在捕获了后代组件传递的错误时调用。
<script setup>
import { onErrorCaptured } from 'vue'
// err 错误对象
// instance 触发该错误的组件实例
// info 错误来源类型的信息字符串
onErrorCaptured((err, instance, info) => {
});
</script>
onActivated()
注册一个回调函数,若组件实例是 <KeepAlive>
缓存树的一部分,当组件被插入到 DOM 中时调用。
onDeactivated()
注册一个回调函数,若组件实例是 <KeepAlive>
缓存树的一部分,当组件从 DOM 中被移除时调用。
onRenderTracked()
注册一个调试钩子,当响应式依赖被组件的渲染作用追踪后调用。Dev only
onRenderTriggered()
注册一个调试钩子,当响应式依赖触发了组件渲染作用的运行之后调用。Dev only
onServerPrefetch()
注册一个异步函数,在组件实例在服务器上被渲染之前调用。 SSR only