新特性
- 组合式 API setup
import { ref, toRefs, readonly, watch, computed } from "vue";
export default {
name: "demo",
// 添加组合式API,在组件创建前执行所以,没有this
setup(props) {
// 使用 `toRefs` 创建对prop的 `user` property 的响应式引用
const { user } = toRefs(props);
// 将属性变为响应式的 ref(attr)
const list = ref([]); // { value: [] }
const GetData = async () => {
list.value = await FetchList(props.user);
};
// 生命周期函数
// on `mounted` call `GetData`
onMounted(GetData);
// 监听 user 的变化,重新请求 GetData
watch(user, GetData);
// 计算属性
const keyword = ref("");
const resList = computed(() => {
return list.value.filter((item) => item.name.includes(keyword.value));
});
// 返回对象可以组件内this.调用
return {
list,
GetData,
keyword,
resList,
};
},
};
如上所示,相同模块的功能被整合到了一起,下面把这部分功能提取到单独的文件 combined.js
// -- combined.js --
import { ref, toRefs, watch, computed } from "vue";
export default function Combined(user) {
const list = ref([]); // { value: [] }
const GetData = async () => {
list.value = await FetchList(props.user);
};
const keyword = ref("");
const resList = computed(() => {
return list.value.filter((item) => item.name.includes(keyword.value));
});
onMounted(GetData);
watch(user, GetData);
return { GetData, keyword, resList };
}
// -- demo.vue --
import Combined from "./combined.js";
import { toRefs } from "vue";
export default {
props: {
user: { type: String },
},
setup(props) {
const { user } = toRefs(props);
const { GetData, keyword, resList } = Combined(user);
return { GetData, keyword, resList };
},
methods: {
Reset() {
this.keyword = ""; // 自动触发搜索
},
},
};
- Teleport
<template>
<button @click="modalOpen = true">点我弹框</button>
<teleport to="body">
<div v-if="modalOpen" class="modal">我在body下</div>
</teleport>
</template>
<script>
export default {
data() {
return {
modalOpen: false,
};
},
};
</script>
teleport 下内容将会挂载到 body 下
- 组件内根元素可以有多个,即片段
<template>
<header>...</header>
<!-- 调用处传入的属性绑定在这个标签上 -->
<main v-bind="$attrs">...</main>
<footer>...</footer>
</template>
- 组件事件定义和类型验证。emits 里定义的原生事件(比如 click)将替代原生事件的监听器
export default {
props: {
list: {
type: Array,
},
},
emits: {
// 没有验证
click: null,
// 验证submit 事件
submit: ({ email, password }) => {
if (email && password) {
return true;
} else {
console.warn("Invalid submit event payload!");
return false;
}
},
},
methods: {
submitForm() {
this.$emit("submit", { email, password });
},
},
};
- 多个 v-model 绑定
app.component("user-name", {
props: {
firstName: String,
lastName: String,
},
template: `
<input
type="text"
:value="firstName"
@input="$emit('update:firstName', $event.target.value)">
<input
type="text"
:value="lastName"
@input="$emit('update:lastName', $event.target.value)">
`,
});
<user-name
v-model:first-name="firstName"
v-model:last-name="lastName"
></user-name>
使用v-model:xxx
传值,使用$emit('update:xxx')
同步变更
- 自定义 v-model 修饰符
<my-component v-model:first-name.lowcase="bar"></my-component>
app.component("my-component", {
props: {
firstName: String,
firstNameModifiers: {
// 所有修饰符都在它里:{ lowcase: true }
default: () => ({}),
},
},
template: `
<input type="text"
:value="firstName"
@input="$emit('update:firstName', $event.target.value)">
`,
created() {
console.log(this.firstNameModifiers.lowcase); // true,可以根据此值做一些逻辑处理
},
});
原来的
value
->modelValue
;input
->update:modelValue
;组件内使用
xxxModifiers
或modelModifiers
记录 v-model 的修饰符。Modifiers 为一个对象,比如 { lowcase: true }
2.x 中的
.sync
被弃用
css <style scoped>
/* 深度选择器 类似/deep/,vue3不再支持/deep/ */
::v-deep(.foo) {
}
:deep(.foo) {
}
/* 插槽 */
::v-slotted(.foo) {
}
:slotted(.foo) {
}
/* 一次性全局规则 */
::v-global(.foo) {
}
:global(.foo) {
}
/* 尽量不使用 :deep .foo 这样的组合选择器*/
不兼容的变更
创建应用不再使用 new Vue
import { createApp } from "vue";
import MyApp from "./MyApp.vue";
const app = createApp(MyApp);
// 或链式调用 createApp(comp|page).mount('#app')
app.component("button-counter", {
data: () => ({
count: 0,
}),
template: '<button @click="count++">Clicked {{ count }} times.</button>',
});
app.directive("focus", {
mounted: (el) => el.focus(),
});
app.use(VueRouter);
app.mount("#app");
2.x 全局 API | 3.x 实例 API (app) |
---|---|
Vue.config | app.config |
Vue.config.productionTip | removed |
Vue.config.ignoredElements | app.config.isCustomElement |
Vue.use | app.use |
Vue.mixin | app.mixin |
Vue.component | app.component |
Vue.directive | app.directive |
.
-
nextTick 必须显式导入
import { nextTick } from "vue"; nextTick(() => {}); // webpack.config.js // 在插件开发中,需要将vue源码从代码里剔除 module.exports = { /*...*/ externals: { vue: "Vue", }, };
<template v-for key>
key 应该设置在 template 标签上与 2 不同,在 3 中同一元素上 v-if 比 v-for 优先级更高(最好不要一起用)
-
v-for 中的 Ref:不会再自动创建数组,需要为它指定一个方法
<div v-for="item in list" :ref="RecardEl"></div> methods: { RecardEl(el) { this.itemRefs.push(el) // 需要自已记录el } },
-
函数式组件:仅是个简单函数(不再是对象);不再有 functional 属性;只接受 props 和 context 两个参数;不再隐式提供 h;
import { h } from "vue"; const myComp = (props, context) => { return h(`h${props.level}`, context.attrs, context.slots); }; myComp.props = ["level"]; export default myComp;
-
异步组件:
// in vue2.x const asyncPage = () => import("./NextPage.vue"); // in vue3 import { defineAsyncComponent } from "vue"; const asyncPage = defineAsyncComponent(() => import("./NextPage.vue"));
带选项的异步组件 都不知道还有这种用法
// in vue2.x const asyncPage = { component: () => import("./NextPage.vue"), delay: 200, timeout: 3000, error: ErrorComponent, loading: LoadingComponent, }; // in vue3 import { defineAsyncComponent } from "vue"; import ErrorComponent from "./components/ErrorComponent.vue"; import LoadingComponent from "./components/LoadingComponent.vue"; const asyncPageWithOptions = defineAsyncComponent({ loader: () => import("./NextPage.vue"), // 不使用component,使用loader delay: 200, timeout: 3000, errorComponent: ErrorComponent, loadingComponent: LoadingComponent, });
-
自定义指令 directive:
- bind → beforeMount
- inserted → mounted
- beforeUpdate:新的!这是在元素本身更新之前调用的,很像组件生命周期钩子。
- update → 移除!有太多的相似之处要更新,所以这是多余的,请改用 updated。
- componentUpdated → updated
- beforeUnmount:新的!与组件生命周期钩子类似,它将在卸载元素之前调用。
- unbind -> unmounted
app.directive("highlight", { // in 2.x,bind(el, binding, vnode) { beforeMount(el, binding, vnode) { el.style.background = binding.value; // in 2.x,const vm = vnode.context const vm = binding.instance; }, });
由于现在组件支持多根节点,所以绑定在组件上的自定义指定:
<template> <header>...</header> <!-- 调用处传入的属性绑定在这个标签上 --> <main v-bind="$attrs">...</main> <footer>...</footer> </template> // 没明白 <div @vnodeMounted="myHook" />
watch 不再支持点分隔字符串路径,请改用计算函数作为参数
this.$watch(
() => this.c.d,
(newVal, oldVal) => {
// 做点什么
}
)
// or
computed: {
cd() { return this.c.d; }
},
watch{
cd(newVal) {
// do something
}
}
- destroyed => unmounted,beforeDestroy => beforeUnmount
- 来自 mixin 的 data 现在将会替换属性的值,而不再合并
- <template> 没有特殊指令的标记 (v-if/else-if/else、v-for 或 v-slot) 现在被视为普通元素,并将生成原生的 <template> 元素,而不是渲染其内部内容。
- off 和 $once 实例方法已被移除,eventBus 退出历史。
- 过滤器已删除,官方建议使用计算属性。
- 删除了$destroy 方法,官方认为用户不应再手动管理单个 Vue 组件的生命周期
- 移除了 click.native 中 native 修饰符