有没有小伙伴跟笔者一样vue3
项目做了好几个了,但是一直没有去总结vue3
的新特性呢?
今天笔者通过对比vue2
来总结vue3
新特性,希望可以让你们在回顾vue2
知识点的时候还能学习vue3
新的知识。相信你认真看完一定会有收获。
正所谓工欲善其事,必先利其器。在讲解vue3
新特性之前,笔者先来介绍几个插件。这样会大大提高我们的开发效率和体验。
使用vscode
开发vue2
项目的小伙伴肯定都认识Vetur
这个神级插件。但是在vue3
中这个插件就显得捉襟见肘了,比如vue3
的多片段这个插件就会报错。
这个时候就需要使用Volar
,Volar
可以理解为Vue3
版本的Vetur
,代码高亮,语法提示,基本上Vetur
有的它都有。
在vue2
中我们一直使用Vue 2 Snippets
,在vue3
我们推荐使用Vue 3 Snippets
,因为它支持vue3
的同时完全向前兼容vue2
,所以小伙伴们赶快去升级吧。
vue2
版本的chrome devtools
不再支持vue3
,vue3
我们需要单独下载Vue.js devtools beta。(下载devtools
是需要梯子的哦,如果没有可以联系笔者)。
在下载Vue.js devtools beta
之前,我们需要先卸载vue2
版本的Vue.js devtools
,不然会有警告。
vue3
固然好用但是我们还是不能盲目追求新东西,在使用vue3
开发之前我们需清楚的知道它的兼容性。
vue2
不支持 IE8 及以下版本,因为 Vue
使用了 IE8 无法模拟的 ECMAScript 5 特性。但它支持所有兼容 ECMAScript 5 的浏览器。
vue3
不支持 IE11 及以下版本。
在vue2
中,我们只要定义在data()
方法中的数据就是响应式数据。或者使用Vue.observable()
方法来定义响应式数据。
还可以使用this.$set( target, propertyName/index, value )
或Vue.set( target, propertyName/index, value )
来给对象或数组添加响应式属性。使用this.$delete( target, propertyName/index)
或Vue.delete( target, propertyName/index)
来给对象或数组删除响应式属性。
在vue2
中使用Vue.observable()
方法。
const state = Vue.observable({ count: 0 })
但在vue3
中主要是使用ref
和reactive
来定义响应式数据。由于vue3
使用的是proxy
进行响应式监听,所以新增、删除属性也都是响应式的,也就不需要使用上面的set delete
了。
接受一个内部值并返回一个响应式且可变的 ref
对象。ref
对象仅有一个 .value
property
,指向该内部值。
一般用来定义基本类型的响应式数据。注意这里说的是一般,并不是说ref就不能定义引用类型的响应式数据。
使用ref
定义的响应式数据在setup
函数中使用需要加上.value
,但在模板中可以直接使用。
isRef
检查值是否为一个 ref
对象。
<template>
<h3>count1</h3>
<div>count1: {{ count1 }}</div>
<button @click="plus">plus</button>
<button @click="decrease">decrease</button>
<div>user1: {{ user1.name }}</div>
<button @click="updateUser1Name">update user1 name</button>
</template>
<script>
import { defineComponent, ref, isRef } from "vue";
export default defineComponent({
setup() {
const count1 = ref(0);
const plus = () => {
count1.value++;
};
const decrease = () => {
count1.value--;
};
const user1 = ref({ name: "randy1" });
const updateUser1Name = () => {
// ref定义的变量需要使用.value修改
user1.value.name += "!";
};
console.log(isRef(count1)); // true
return {
count1,
plus,
decrease,
user1,
updateUser1Name
};
},
});
</script>
ref
除了定义响应式数据还可以定义模板引用,类似vue2
的this.$refs
这个后面笔者在讲模板引用的时候会细说。
<template>
<div ref="root">This is a root element</div>
</template>
<script>
import { ref, onMounted } from 'vue'
export default {
setup() {
// 创建
const root = ref(null)
onMounted(() => {
// 获取子组件
console.log(root.value) // This is a root element
})
return {
root
}
}
}
</script>
创建一个跟踪自身 .value
变化的 ref
,但不会使其值也变成响应式的。
这句话怎么理解呢?就是我们使用shallowRef
创建出来的数据不是响应式的,也就是我们的修改页面并不会重新渲染。但是我们直接修改数据.value
是会响应式的。
下面我们来看例子。
const sRef1 = shallowRef(0);
console.log("shallowRef:", sRef1.value); // 0
// 假设点击页面按钮,触发该方法
const changeShallowRef1 = () => {
// 直接修改value,页面会同步修改,也就是会响应式
sRef1.value++;
};
const sRef2 = shallowRef({ name: "demi1" });
// 假设点击页面按钮,触发该方法
const changeShallowRef2 = () => {
// 不直接修改value,而是修改属性值
sRef2.value.name = "randy";
sRef2.value.address = { city: "汨罗" }; // 添加新属性
// 这里数据虽然改变了,但是页面不会更新,也就是说不会响应式
console.log(sRef2.value); // {address: {city: '汨罗'}, name: "randy"}
// 但是我们直接重新赋值,页面会立马重新渲染
sRef2.value = {name: "randy", address: {city: '汨罗'}}
};
// 假设点击页面按钮,触发该方法
const changeShallowRef3 = () => {
// 但是我们直接重新赋值,页面会立马重新渲染
sRef2.value = {name: "randy", address: {city: '汨罗'}}
};
通过上面的例子我们可以发现,当响应式数据是基本数据类型的时候ref
和shallowRef
没有差别。但是如果数据是引用数据类型的话ref
的数据是响应式的而shallowRef
不是,shallowRef
需要给value
重新赋值才会触发响应式。
reactive
用来定义引用类型的响应式数据。注意,不能用来定义基本数据类型的响应式数据,不然会报错。
reactive
定义的对象是不能直接使用es6
语法解构的,不然就会失去它的响应式,如果硬要解构需要使用toRefs()
方法。
isReactive
用来检查对象是否是由 reactive
创建的响应式代理。
<template>
<div>
<h3>user2</h3>
<div>user2: {{ user2.name }}</div>
<button @click="updateUser2Name">update user2 name</button>
<h3>user3</h3>
<div>user3 name: {{ name }} user3 age: {{ age }}</div>
<button @click="updateUser3Name">update user3 name</button>
<h3>count2</h3>
<div>count2: {{ count2 }}</div>
<button @click="plus2">plus2</button>
<button @click="decrease2">decrease2</button>
</div>
</template>
<script>
import { defineComponent, reactive, toRefs, isReactive } from "vue";
export default defineComponent({
setup() {
const _user = { name: "randy2" }
const user2 = reactive(_user);
const updateUser2Name = () => {
// reactive定义的变量可以直接修改
user2.name += "!";
// 原始对象的修改并不会响应式,也就是页面并不会重新渲染
// _user.name += "!";
// 代理对象被改变的时候,原始对象会被修改
// console.log(_user);
};
// 使用toRefs可以响应式解构出来,在模板能直接使用啦。
const user3 = reactive({ name: "randy3", age: 24 });
const updateUser3Name = () => {
user3.name += "!";
};
// 使用reactive定义基本数据类型会报错
const count2 = reactive(0);
const plus2 = () => {
count2.value++;
};
const decrease2 = () => {
count2.value--;
};
// 检查对象是否是由 reactive 创建的响应式代理。
console.log(isReactive(user2)); // true
console.log(isReactive(count2)); // false
return {
user2,
updateUser2Name,
// ...user3, // 直接解构不会有响应式
...toRefs(user3),
updateUser3Name,
count2,
plus2,
decrease2,
};
},
});
</script>
reactive
将解包所有深层的 refs
,同时维持 ref
的响应性。
怎么理解这句话呢,就是使用reactive
定义响应式对象,里面的属性是ref
定义的话可以直接赋值而不需要再.value
,并且数据的修改是响应式的。
const count = ref(1)
// 可以直接定义,而不是{count: count.value}
const obj = reactive({ count })
// 这种写法也是支持的
// const obj = reactive({})
// obj.count = count
// ref 会被解包
console.log(obj.count === count.value) // true
// 它会更新 `obj.count`
count.value++
console.log(count.value) // 2
console.log(obj.count) // 2
// 它也会更新 `count` ref
obj.count++
console.log(obj.count) // 3
console.log(count.value) // 3
浅响应式,创建一个响应式代理,它跟踪其自身 property
的响应性,但不执行嵌套对象的深层响应式转换 (暴露原始值)。
并且与 reactive
不同,任何使用 ref
的 property
都不会被代理自动解包。
简单理解就是响应式只会在第一层,不会深层响应式。类似于浅拷贝。
const user1 = shallowReactive({
name: "demi1",
address: { city: "汨罗", count: 10 },
});
// 假设点击页面按钮,触发该方法
const changeUser1 = () => {
// 响应式,页面会发生变化
user1.name = "demi1 !!!";
};
// 假设点击页面按钮,触发该方法
const changeUser2 = () => {
// 非响应式,也就是页面不会发生变化
user1.address.city = "岳阳";
user1.address.count++;
// 这里数据虽然改变了,但是页面不会更新,也就是说不会响应式
console.log(user1); // {address: {city: '岳阳', count: 11}, name: "demi1 !!!"}
};
console.log(isReactive(user1)); // true
console.log(isReactive(user1.address)); // false
接受一个对象 (响应式或纯对象) 或 ref
数据 并返回原始对象的只读代理。只读代理是深层的:任何被访问的嵌套 property
也是只读的。
怎么理解这句话呢,就是说只要是对象不管是普通对象还是reactive
定义的对象或者是ref
定义的数据,定义成readonly
后就不能被修改了。
这里需要特别注意,是
readonly
返回的对象变成只读,源对象不会受到影响,所以修改源对象还是可以的。
isReadonly
用来检查对象是否是由 readonly
创建的只读代理。
// ref定义的数据会被限制,不能被修改
let name1 = ref("readonly randy");
// readOnlyName1才是只读的
let readOnlyName1 = readonly(name1);
const changeName1 = () => {
readOnlyName1.value += "!";
// 这里直接修改源对象还是可以的
// name1.value += "!";
};
// 基本数据类型数据会无效,能被修改
let readOnlyName2 = readonly("readonly randy");
readOnlyName2 = "randy";
console.log(readOnlyName2); // randy
// reactive定义的对象会被限制,不能被修改
const reactiveUser1 = reactive({ name: "readonly randy" });
let readonlyUser1 = readonly(reactiveUser1);
const changeUserName1 = () => {
readonlyUser1.name += "!";
// 这里直接修改源对象还是可以的
// reactiveUser1.name += "!";
};
// 普通对象也会被限制,不能被修改
let readonlyUser2 = readonly({ name: "readonly randy" });
readonlyUser2.name = "randy";
console.log(readonlyUser2.name); // readonly randy
console.log(isReadonly(readOnlyName1)); // true
console.log(isReadonly(readOnlyName2)); // false
console.log(isReadonly(readonlyUser1)); // true
console.log(isReadonly(readonlyUser2)); // true
与 reactive
一样,如果任何 property
使用了 ref
,当它通过代理访问时,则被自动解包。
const raw = {
count: ref(123)
}
// 这里就类似const copy = reactive(raw)
const copy = readonly(raw)
console.log(raw.count.value) // 123
console.log(copy.count) // 123
浅只读,创建一个 proxy
,使其自身的 property
为只读,但不执行嵌套对象的深度只读转换 (暴露原始值)。
并且与 readonly
不同,任何使用 ref
的 property
都不会被代理自动解包。
简单理解就是只读的限制只会在第一层,不会深层只读。类似于浅拷贝。
const user2 = shallowReadonly(
reactive({
name: "demi2",
address: { city: "汨罗2", count: 10 },
})
);
console.log(isReadonly(user2)); // true
console.log(isReadonly(user2.address)); // false
const changeUser2 = () => {
// 响应式,页面会同步修改
user2.address.city = "岳阳";
user2.address.count++;
// 非响应式,也就是页面不会重新渲染该值
user2.name = "demi1 !!!";
};
检查对象是否是由 reactive
或 readonly
创建的 proxy
。
// ref定义的所以返回false
let name1 = ref("readonly randy");
// ref是对象,所以返回true
let readOnlyName1 = readonly(name1);
// readonly失败返回false
let readOnlyName2 = readonly("readonly randy");
const reactiveUser1 = reactive({ name: "readonly randy" });
let readonlyUser1 = readonly(reactiveUser1);
let readonlyUser2 = readonly({ name: "readonly randy" });
// 检查对象是否是由 reactive 或 readonly 创建的 proxy。
console.log(isProxy(name1)); // false
console.log(isProxy(readOnlyName1)); // true
console.log(isProxy(readOnlyName2)); // false
console.log(isProxy(reactiveUser1)); // true
console.log(isProxy(readonlyUser2)); // true
上面的例子有些小伙伴看了会比较懵逼,为什么readonly
有些是true
有些又是false
呢?其实你弄懂了readonly
就大概会清楚了。readonly
是不能处理基本数据类型的,所以readonly
不成功就会返回false
。
返回 reactive
或 readonly
代理的原始对象。这是一个“逃生舱”,可用于临时读取数据而无需承担代理访问/跟踪的开销,也可用于写入数据而避免触发更改。不建议保留对原始对象的持久引用。请谨慎使用。
const foo = {}
const reactiveFoo = reactive(foo)
console.log(toRaw(reactiveFoo) === foo) // true
标记一个对象,使其永远不会转换为 proxy
。返回对象本身。
因为不会被proxy
,也就是说不会响应式,相当于一个普通值。
const info = markRaw({ sex: "male" });
console.log(isReactive(reactive(info))); // false
const user3 = reactive({ name: "randy", info });
const changeUser3 = () => {
user3.info.sex = "female";
// 这里数据虽然变了,但是页面并不会重新渲染,也就是说不会响应式
console.log(user3); // {info: {sex: 'female'}, name: "randy"}
};
如果参数是一个 ref
,则返回内部值,否则返回参数本身。这是 val = isRef(val) ? val.value : val
的语法糖函数。
const user1 = ref({ name: "randy1" });
console.log("unref: ", unref(user1), user1.value);
可以用来为源响应式对象上的某个 property
新创建一个 ref
。然后,ref
可以被传递,它会保持对其源 property
的响应式连接。
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
当你要将 prop
的 ref
传递给复合函数时,toRef
很有用:
export default {
setup(props) {
useSomeFeature(toRef(props, 'foo'))
}
}
即使源 property
不存在,toRef
也会返回一个可用的 ref
。这使得它在使用可选 prop
时特别有用,可选 prop
并不会被 toRefs
处理。
将响应式对象转换为普通对象,其中结果对象的每个 property
都是指向原始对象相应 property
的 ref
。
const state = reactive({
foo: 1,
bar: 2
})
const stateAsRefs = toRefs(state)
/*
stateAsRefs 的类型:
{
foo: Ref,
bar: Ref
}
*/
// ref 和原始 property 已经“链接”起来了
state.foo++
console.log(stateAsRefs.foo.value) // 2
stateAsRefs.foo.value++
console.log(state.foo) // 3
当从组合式函数返回响应式对象时,toRefs
非常有用,这样消费组件就可以在不丢失响应性的情况下对返回的对象进行解构/展开:
function useFeatureX() {
const state = reactive({
foo: 1,
bar: 2
})
// 操作 state 的逻辑
// 返回时转换为ref
return toRefs(state)
}
export default {
setup() {
// 可以在不失去响应性的情况下解构
const { foo, bar } = useFeatureX()
return {
foo,
bar
}
}
}
toRefs
只会为源对象中包含的 property
生成 ref
。如果要为特定的 property
创建 ref
,则应当使用 toRef
。
创建一个自定义的 ref
,并对其依赖项跟踪和更新触发进行显式控制。它需要一个工厂函数,该函数接收 track
和 trigger
函数作为参数,并且应该返回一个带有 get
和 set
的对象。
这个在我们自定义响应式的时候非常有用。比如我们在获取、设置值的时候做些特殊处理。
这个在vue2
中是没办法直接修改响应值的实现的,但是在vue3
可以。
下面是一个延迟响应式的例子。
<template>
<div class="customref">
<div>{{ text }}</div>
<input v-model="text" />
</div>
</template>
<script>
import { defineComponent, customRef } from "vue";
export default defineComponent({
setup() {
const useDebouncedRef = (value, delay = 2000) => {
let timeout;
return customRef((track, trigger) => {
return {
get() {
track();
return value;
},
set(newValue) {
clearTimeout(timeout);
timeout = setTimeout(() => {
value = newValue;
trigger();
}, delay);
},
};
});
};
return {
text: useDebouncedRef("randy"),
};
},
});
</script>
手动执行与 shallowRef
关联的任何作用 (effect
)。
const shallow = shallowRef({
greet: 'Hello, world'
})
// 第一次运行时输出"Hello, world"
watchEffect(() => {
console.log(shallow.value.greet)
})
// 这不会触发作用 (effect),因为 ref 是浅层的
shallow.value.greet = 'Hello, universe'
// 触发watchEffect 输出"Hello, universe"
triggerRef(shallow)
为了让相关代码更紧凑vue3
提出了组合式api
,组合式api
能将同一个逻辑关注点相关代码收集在一起。 组合式api
的入口就是setup
方法。
用官方语言说,setup
是一个组件选项,在组件被创建之前,props
被解析之后执行。它是组合式 API
的入口。
setup
的写法有两种,可以跟vue2
一样直接导出也可以导出defineComponent
对象。若要对传递给 setup()
的参数进行类型推断,你需要使用 defineComponent。
// 直接export
export default {
setup(){}
}
import { defineComponent } from 'vue'
// 导出defineComponent对象
export default defineComponent({
setup(){}
})
从生命周期的角度来看,它会在beforeCreate
之前执行。也就是创建组件会依次执行setup
、beforeCreate
、create
。
在 setup
中你应该避免使用 this
,因为它不会找到组件实例。setup
的调用发生在 data
property、computed
property 或 methods
被解析之前,所以它们无法在 setup
中被获取。
setup
选项是一个接收 props
和 context
的函数。
setup
函数中的第一个参数是 props
。props
就是我们父组件给子组件传递的参数。
正如在一个标准组件中所期望的那样,setup
函数中的 props
是响应式的,当传入新的 prop
时,它将被更新。
import { defineComponent } from 'vue'
export default defineComponent({
props: {
title: String
},
setup(props) {
console.log(props.title)
}
})
因为
props
是响应式的,你不能使用 ES6 解构,它会消除 prop 的响应性。
如果需要解构请使用toRefs
方法。
import { defineComponent, toRefs } from 'vue'
export default defineComponent({
props: {
title: String
},
setup(props) {
const { title } = toRefs(props)
console.log(title.value)
}
})
如果 title
是可选的 prop
,则传入的 props
中可能没有 title
。在这种情况下,toRefs
将不会为 title
创建一个 ref
。你需要使用 toRef
替代它:
import { defineComponent, toRef } from 'vue'
export default defineComponent({
props: {
title: String
},
setup(props) {
const title = toRef(props, 'title')
console.log(title.value)
}
})
context
是一个普通的 JavaScript
对象,也就是说,它不是响应式的,这意味着你可以安全地对 context
使用 ES6
解构。
import { defineComponent } from 'vue'
export default defineComponent({
setup(props, context) {
// Attribute (非响应式对象,等同于 $attrs)
console.log(context.attrs)
// 插槽 (非响应式对象,等同于 $slots)
console.log(context.slots)
// 触发事件 (方法,等同于 $emit)
console.log(context.emit)
// 暴露公共 property (函数)
console.log(context.expose)
}
})
attrs
和 slots
是有状态的对象,它们总是会随组件本身的更新而更新。这意味着你应该避免对它们进行解构,并始终以 attrs.x
或 slots.x
的方式引用 property
。请注意,与 props
不同,attrs
和 slots
的 property
是非响应式的。如果你打算根据 attrs
或 slots
的更改应用副作用,那么应该在 onBeforeUpdate
生命周期钩子中执行此操作。
这里我们重点说下expose
的使用。
假如我们想在父组件中直接调用子组件的方法该怎么做呢?我们就可以在子组件中使用expose
把属性或方法暴露出去。在父组件我们就可以通过子组件的ref
直接调用了。
使用的时候需要注意:
当组件没定义expose
暴露内容的时候,通过ref
获取到的就是组件自身的内容,也就是setup
函数return
的内容。
当定义了expose
暴露内容的时候,通过ref获取到的就是组件expose
暴露内容,并且setup
函数return
的内容会失效,也就是会被覆盖。
// 子组件
<template>
<div>
<h2>child</h2>
</div>
</template>
<script>
import { defineComponent } from "vue";
export default defineComponent({
setup(props, { expose }) {
const childSay = () => {
console.log("childSay");
};
const sex = 'male'
// 如果定义了会覆盖return中的内容
// expose({
// sex
// });
return {
childSay,
childSay
}
},
});
</script>
// 父组件
<template>
<div>
<LifeChild ref="childRef"/>
</div>
</template>
<script>
import {
defineComponent,
onMounted,
ref,
} from "vue";
import LifeChild from "@/components/LifeChild";
export default defineComponent({
components: {
LifeChild,
},
setup() {
const childRef = ref(null);
onMounted(() => {
// 使用子组件暴露的属性和方法
childRef.value.childSay(); // childSay
console.log(childRef.value.sex); // male
});
return {
childRef,
};
},
});
</script>
setup
返回的所有内容都暴露给组件的其余部分 (计算属性、方法、生命周期钩子等等) 以及组件的模板。所以我们在模板中需要使用到的数据都需要通过setup
方法return
出来。
上面的话怎么理解呢?就是我们在模板,或者vue2
选项式写法的计算属性、方法、生命周期钩子等等中使用的数据都需要在setup
方法中通过return
返回出来。
如果 setup
返回一个对象,那么该对象的 property
以及传递给 setup
的 props
参数中的 property
就都可以在模板中访问到。
<template>
<div>{{ collectionName }}: {{ number }} {{ user.name }}</div>
</template>
<script>
import { ref, reactive, defineComponent } from 'vue'
export default defineComponent({
props: {
// 这个属性能直接在模板中使用
collectionName: String
},
setup(props) {
// 在setup函数中需要通过props获取。
console.log(props.collectionName)
// 定义响应式数据
const number = ref(0)
const user = reactive({ name: 'randy' })
// 暴露给 template
return {
number,
user
}
}
})
</script>
这里我们通过ref
、reactive
创建了响应式数据,具体差别后面会再细说。
setup
还可以返回一个渲染函数,该函数可以直接使用在同一作用域中声明的响应式状态。
import { h, ref, reactive } from 'vue'
export default {
setup() {
const number = ref(0)
const user = reactive({ name: 'randy' })
// 请注意这里我们需要显式使用 ref 的 value
return () => h('div', [number.value, user.name])
}
}
返回一个渲染函数将阻止我们返回任何其它的东西。我们可以通过我们上面介绍的 expose
来解决这个问题,给它传递一个对象,其中定义的 property
将可以被外部组件实例访问。
要使用这个语法,需要将 setup
attribute 添加到 代码块上:
<script setup>
console.log('hello script setup')
</script>
里面的代码会被编译成组件 setup()
函数的内容。这意味着与普通的 只在组件被首次引入的时候执行一次不同,
中的代码会在每次组件实例被创建的时候执行。
当使用 的时候,任何在
声明的顶层的绑定 (包括变量,函数声明,以及 import 引入的内容) 都能在模板中直接使用:
<script setup>
// 变量
const msg = 'Hello!'
// 函数
function log() {
console.log(msg)
}
</script>
<template>
<div @click="log">{{ msg }}</div>
</template>
import 导入的内容也会以同样的方式暴露。意味着可以在模板表达式中直接使用导入的 helper 函数,并不需要通过 methods
选项来暴露它:
<script setup>
import { capitalize } from './helpers'
</script>
<template>
<div>{{ capitalize('hello') }}</div>
</template>
响应式状态需要明确使用响应式 APIs来创建。和从 setup()
函数中返回值一样,ref
值在模板中使用的时候会自动解包:
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
<template>
<button @click="count++">{{ count }}</button>
</template>
范围里的值也能被直接作为自定义组件的标签名使用:
<script setup>
import MyComponent from './MyComponent.vue'
</script>
<template>
<MyComponent />
</template>
将 MyComponent
看做被一个变量所引用。如果你使用过 JSX,在这里的使用它的心智模型是一样的。其 kebab-case 格式的
同样能在模板中使用。不过,我们强烈建议使用 PascalCase 格式以保持一致性。同时也有助于区分原生的自定义元素。
由于组件被引用为变量而不是作为字符串键来注册的,在 中要使用动态组件的时候,就应该使用动态的
:is
来绑定:
<script setup>
import Foo from './Foo.vue'
import Bar from './Bar.vue'
</script>
<template>
<component :is="Foo" />
<component :is="someCondition ? Foo : Bar" />
</template>
一个单文件组件可以通过它的文件名被其自己所引用。例如:名为 FooBar.vue
的组件可以在其模板中用
引用它自己。
请注意这种方式相比于 import 导入的组件优先级更低。如果有命名的 import 导入和组件的推断名冲突了,可以使用 import 别名导入:
import { FooBar as FooBarChild } from './components'
可以使用带点的组件标记,例如
来引用嵌套在对象属性中的组件。这在需要从单个文件中导入多个组件的时候非常有用:
<script setup>
import * as Form from './form-components'
</script>
<template>
<Form.Input>
<Form.Label>label</Form.Label>
</Form.Input>
</template>
全局注册的自定义指令将以符合预期的方式工作,且本地注册的指令可以直接在模板中使用,就像上文所提及的组件一样。
但这里有一个需要注意的限制:必须以 vNameOfDirective
的形式来命名本地自定义指令,以使得它们可以直接在模板中使用。
<script setup>
const vMyDirective = {
beforeMount: (el) => {
// 在元素上做些操作
}
}
</script>
<template>
<h1 v-my-directive>This is a Heading</h1>
</template>
<script setup>
// 导入的指令同样能够工作,并且能够通过重命名来使其符合命名规范
import { myDirective as vMyDirective } from './MyDirective.js'
</script>
defineProps
和 defineEmits
在 中必须使用
defineProps
和 defineEmits
API 来声明 props
和 emits
,它们具备完整的类型推断并且在 中是直接可用的:
<script setup>
const props = defineProps({
foo: String
})
const emits = defineEmits(['change', 'delete'])
//触发事件 类似 context.emit()
emits('change', {name: 'randy'})
</script>
defineProps
和 defineEmits
都是只在 中才能使用的编译器宏。他们不需要导入且会随着
处理过程一同被编译掉。
defineProps
接收与 props
相同的值,defineEmits
也接收 emits
相同的值。
传入到 defineProps
和 defineEmits
的选项会从 setup 中提升到模块的范围。因此,传入的选项不能引用在 setup 范围中声明的局部变量。这样做会引起编译错误。但是,它可以引用导入的绑定,因为它们也在模块范围内。
使用 的组件是默认关闭的,也即通过模板 ref 或者
$parent
链获取到的组件的公开实例,不会暴露任何在 中声明的绑定。
为了在 组件中明确要暴露出去的属性,使用
defineExpose
编译器宏,他也是不需要导入且会随着 处理过程一同被编译掉。
<script setup>
import { ref } from 'vue'
const a = 1
const b = ref(2)
defineExpose({
a,
b
})
</script>
当父组件通过模板 ref 的方式获取到当前组件的实例,获取到的实例可以获取到a 、b
属性 (ref 会和在普通实例中一样被自动解包)。跟前面说的expose
是一样的。
在 使用
slots
和 attrs
的情况应该是很罕见的,因为可以在模板中通过 $slots
和 $attrs
来访问它们。在你的确需要使用它们的罕见场景中,可以分别用 useSlots
和 useAttrs
两个辅助函数:
<script setup>
import { useSlots, useAttrs } from 'vue'
const slots = useSlots()
const attrs = useAttrs()
</script>
useSlots
和 useAttrs
是真实的运行时函数,它会返回与 setupContext.slots
和 setupContext.attrs
等价的值,同样也能在普通的组合式 API 中使用。
一起使用 可以和普通的
一起使用。普通的
在有这些需要的情况下或许会被使用到:
声明的选项,例如 inheritAttrs
或通过插件启用的自定义的选项。<script>
// 普通