Vue3
创建项目
vue CLI
@vue.cli 4.5
提供了内置选项创建Vue3项目
vue create temp-vue3
// 选择 vue3 预设
vite项目
npm create vue@latest
全局API
createApp
createApp创建应用而非new实例,不再支持vue2中对Vue类的全局配置方式,仅对应用实例配置,大部分配置项保留:
import { createApp } from 'vue';
// 构造vue应用实例
const app = createApp({ ... });
app.config.globalProperties.$http = () => {}
app实例
Vue/app对应表
Vue | app |
---|---|
Vue.config.productionTip | 移除 |
Vue.config.ignoredElements | app.config.compilerOptions.isCustomElement |
Vue.component | app.component |
Vue.directive | app.directive |
Vue.mixin | app.mixin |
Vue.use | app.use |
Vue.prototype | app.config.globalProperties |
全局API 具名导出
为了全局API支持被模块打包工具的TreeShaking,全局API采用具名导出访问
import { nextTick, inject } from 'vue'
nextTick(() => {...});
await nextTick();
组合式(composable)
组合式API
将数据通过 ref
响应化,声明数据逻辑(watch、生命周期、计算属性,计算值)
// 全局导入选项
import { ref, watch, computed, onMounted } from 'vue';
export default {
... 兼容options
setup (props, { slots, expose }) {
const list = ref([]);
const getListData = async () => {
list.value = await fetchUserRepositories(props.user)
};
watch(list, (newValue, oldValue) => {
console.log('The new value is: ' + list.value)
});
computed(userName, () => props.user.name);
onMounted(getListData);
return {
list, // 被视为 options.data/computed/props
getListData // 被视为 options.methods
}
}
}
组合式:将各部分数据的逻辑以函数的形式独立为块,最终在setup中混合输出;
export default {
components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
props: {
user: { type: String, required: true }
},
setup(props) {
const { user } = toRefs(props);
// list列表功能
const { repositories, getUserRepositories } = useUserRepositories(user);
// 筛选/搜索功能
const {
searchQuery,
repositoriesMatchingSearchQuery
} = useRepositoryNameSearch(repositories);
const {
filters,
updateFilters,
filteredRepositories
} = useRepositoryFilters(repositoriesMatchingSearchQuery);
return {
repositories: filteredRepositories,
getUserRepositories,
searchQuery,
filters,
updateFilters
}
}
}
setup
单文件组件(SFC
)下组合式API语法糖
支持将普通的的setup Function以 **
<template>
<div></div>
<Comp v-directive-bind />
</template>
<script setup>
// 顶层变量暴露于模板
import { ref } from 'vue';
import Comp from './Comp.vue';
let msg = ref('tip message');
let list = ref([]);
// 允许函数作为自定义指令
const vDirectiveBind = {
beforeMount(){ ... },
...
};
// vue选项声明
const props = defineProps({
foo: String
});
const emit = defineEmits(['change', 'delete']);
// <script setup> 内访问 $slot $attrs
const slots = useSlots();
const attrs = useAttrs();
// 仅通过defineExpose暴露的属性,可被外部调用$parent/children访问
defineExpose({ list, foo });
// <script setup> 内支持顶层async/await
list.value = await this.$http.getList(params);
</script>
hook
组合式函数,利用 Vue 的组合式 API 来封装和复用 有状态逻辑 的函数;
生命周期
destroyed
更名为onUnmounted
beforeDestroy
更名为 onBeforeUnmount
生命周期事件
// @hook更改为@vnode
<template>
<child-component @vnode-updated="onUpdated">
</template>
v-model
移除了 v-bind/:attr.sync
写法,将其集成至 v-model
;
v-model
的默认 prop 由 value 变为 modelValue
,且支持添加自定义修饰符;
<user-form v-model:title="formTitle" v-model="formData" />
// 等价于
<user-form
:title="formTitle"
@update:title="v => formTitle = v"
:modelValue="formData"
@update:modelValue="v => formData= v"
/>
注:
emit("update:modelValue", [...list, item])
更新引用类型数组项,需 reactive(item)
使其立即作为可响应值
emit
发射后,监听处理将在下次 nextTick
时机才会统一执行
自定义事件
- 任何情况必须声明事件选项emit
- 任何情况必须声明props,即需要访问的传递值
指令
生命周期
// 指令生命周期与组件生命周期一致
const vMyDirective = {
created(el, binding, vnode, prevVnode) {
// binding.instance访问组件实例,vnode访问组件虚拟节点
const vm = binding.instance;
}, // 新增
beforeMount() {},
mounted() {},
beforeUpdate() {}, // 新增
updated() {},
beforeUnmount() {}, // 新增
unmounted() {}
}
// 模板语法直接使用 v-my-directive
template
多根节点支持
片段即支持多根节点的组件,但需要求开发者显式定义 attribute 分布节点;
<template>
<header>...</header>
<main v-bind="$attrs">...</main>
<footer>...</footer>
</template>
teleport
vue3新增内置组件,teleport实现了UI结构不再作为Vue逻辑组件结构体现
允许指定Vue组件的UI部分/全部内容在页面的其它位置展现;
<teleport :to="'#el'/'body'">
<child-component name="John" />
</teleport>
template支持绑定key标识
且会为v-if
v-else-if
v-else
自动生成key,不再需要手动添加 key
以不被重用;
v-if/v-for
v-if
优先级高于 v-for
组件
引用
<script setup>
const comp1 = ref(null) // 对comp引用
</script>
<template>
<comp ref="comp1"/>
</template>
异步组件
// 不再支持
const comp = () => import('./comp.vue');
const asyncModal = {
component: () => import('./Modal.vue'),
delay: 200,
timeout: 3000,
error: ErrorComponent,
loading: LoadingComponent
}
// 改为显示定义
import { defineAsyncComponent } from 'vue'
const comp = defineAsyncComponent(() => import('./comp.vue'));
const asyncModal = defineAsyncComponent({
loader: () => import('./Modal.vue'),
delay: 200,
timeout: 3000,
error: ErrorComponent,
loading: LoadingComponent
})
Suspense
<Suspense />
组件实际上是一个提升用户友好度的组件,当如果你在渲染时遇到异步依赖项 (异步组件或者具有 async setup组件),它将等到所有异步依赖项解析完成时再显示默认插槽。
$attr
class
、style
属性也会存在于 $attr
上,所以也会受到 inheritAttr
控制;$listeners
成为了 $attrs
对象一部分
<comp @success="handle"></comp>
// 如comp内 $emits 选项未接收handle,则作为原生事件绑定给内层元素上
// $attrs.onSuccess
defineOptions
可用于声明组件选项
<script setup>
defineOptions({ inheritAttrs: false, customOptions: { /* ... */ } })
</script>
拓展
响应式
markRow(value)
:标记一个值作为原数据存在,使响应式不作反应
shadowRow(value)
:创建一个浅层响应式值,值内部修改不作反应
渲染函数(render)
vue提供了一个 h()
函数用于创建 vnodes,默认接受3个参数:
// 完整参数签名
function h(
type: string | Component,
props?: object | null,
children?: Children | Slot | Slots
): VNode
配合 setup
返回渲染函数使用
import { useViewable } from "./useViewable.js";
import { ref, reactive, h, defineComponent } from "vue";
export const UseViewable = defineComponent({
name: "UseViewable",
props: {
containerElement: {
type: Object,
default: null,
validator(value) {
return value instanceof Element;
},
},
initialSizePercentage: { type: Number, default: 1 },
scaleStep: { type: Number, default: 0.02 },
onDrag: { type: Function, default: () => {} },
onScale: { type: Function, default: () => {} },
},
setup(props, { slots, expose }) {
const target = ref(null);
const data = reactive(useViewable(target, { ...props }));
expose(data);
return () =>
h(
"div",
{ ref: target, style: data.style },
slots.default ? slots.default(data) : []
);
},
});
单文件样式
作用域样式 scoped
// 深度选择器 :deep()
// 代替 ::v-deep
.comp :deep(.el-form){
margin-bottom: 24px;
}
// 插槽选择器 :slotted
:slotted(.item) {
width: 240px;
}
// 全局选择器 :global
:global(.piker-popover) {
padding: 24px;
}
<style module>
标签会被作为 CSS Modules,暴露的 $style
对象包含module声明的CSS类;
<template>
<p :class="$style.collapse">
There is some content...
</p>
<div class="box">
<div class="$box.title">Title 1</div>
</div>
</template>
<!-- setup 中使用 -->
<script>
import { useCssModule } from 'vue'
export default {
setup {
const $style = useCssModule();
const getTitleClassName = () => $style[`title${i}`];
return { getTitleClassName };
}
}
</script>
<script setup>
import { useCssModule } from 'vue'
const $boxStyle = useCssModule('box');
const getTitleClassName = () => $style[`title${i}`];
</script>
<style module>
.collapse {
height: 120px;
overflow: hidden;
}
</style>
<style module="box">
.title {
font-size: 24px;
font-weight: bold;
}
</style>
动态CSS
<script>
const colors = ['red', 'green', 'blue'];
export default {
...
computed: {
color(){
return colors[this.index];
}
}
}
</script>
<script setup>
const colors = ['red', 'green', 'blue'];
const color = () => colors[this.index];
</script>
<style scoped>
.text {
color: v-bind(color);
}
.test {
color: v-bind(theme.color);
}
</style>
移除项
extend
// vue2
const Comp = Vue.extend(comp);
new Comp().$mount('#el');
// vue3
Vue.createApp(comp).mount('#el');
v-on.native
新增 emits
选项定义子组件真正会被触发的事件
否则作为 native 事件监听( inheirtAttr: false
情况外)
事件API
vm.$on|$off|$once
事件API移除,事件总线$eventBus
使用第三方库 mitt代替
// eventBus.js
import emitter from 'tiny-emitter/instance'
export default {
$on: (...args) => emitter.on(...args),
$once: (...args) => emitter.once(...args),
$off: (...args) => emitter.off(...args),
$emit: (...args) => emitter.emit(...args),
}
生态
Vue Router
createRouter
// import Router from 'vue-router'
import { createRouter } from 'vue-router'
const router = createRouter({
// ...
})
history
import { createRouter, createWebHistory } from 'vue-router'
// 还有 createWebHashHistory 和 createMemoryHistory
createRouter({
history: createWebHistory('/base-directory/'), // 替代mode,旧的base选项改为传入参数
routes: [],
})
通配符路由
{ path: '/:pathMatch(.*)*', name: 'not-found', component: NotFound }
router.match
改为 router.resolve
带有空 path
的命名子路由不再添加斜线
Vuex
createStore
// store/index.js
import { createStore } from 'vuex'
export const store = createStore({
state () {
return { ... }
}
})
// main.js
import { createApp } from 'vue'
import { store } from './store'
import App from './App.vue'
const app = createApp(App)
app.use(store)
app.mount('#app')
useStore
vuex 4 为组件的 setup
钩子函数提供 useStore
import { useStore } from 'vuex'
export default {
setup () {
const store = useStore();
const componentData = computed(() => store.getters['component/componentData']);
const ACTIVE_COMPONENT = (payload) =>
store.commit('component/ACTIVE_COMPONENT', payload);
}
}