Web开发-Vue3.0上手 如果你忘了 看这一pian就够了!
之前用Vue3.0开发过一个项目雏形,后续需求尚未跟进,有些忘了,so,在继续开发之前,没事看看Vue3的知识,简单回顾,强化技能;
Study from 2021 Vue3.0极速上手
vue3基础知识参考Web开发-Vue3.x
主要内容
相比Vue2的主要变化:
- Fragments和Emits
- 自定义渲染器
- 全局API调整
- 摇树优化
- v-model调整
- 更好用的渲染函数
- 失宠的函数式组件
- 异步组件的使用
- data选项、组件白名单
- transition类名调整
- watch API重要调整
$on $off $once
被删除了- filter为何也被移除
- 。。。
TodoList实践;
vue-router 4.x
- 新用法 新特性
- history选项
- 通配处理
- isReady方法
- 滚动行为变化
- keep-alive和transition与router-view使用的重要变化
- router-link使用调整
- 动态路由解析变化
- composition API
vuex 4.x
- 新用法
- composition API
如何快速开始Vue3.0
- cdn方式
- vue-cli(> v4.5):
npm i -g @vue/cli@next
- 创建项目时,会有相应的vue3选项;
- vite:更快捷
npm init vite-app <project-name>
cd <project-name>
npm install
npm run dev
使用vite开发时,模块间引入文件的后缀名不可省略;
相比Vue2的主要变化:
composition API
更好的逻辑复用和代码组织
<p> {{ counter.counter }} </p>
<p> {{ counter.doubleCounter }} </p>
import {reactive, computed, onMountd, onUnmounted} from 'vue'
exprot default {
name:'',
props:{
msg:String
},
setup() {
// counter 相关操作逻辑
// 响应式对象
const data = useCounter()
// or 不提取
// const data = reactive({
// counter: 1,
// // 计算属性
// doubleCounter: computed(()=> data.counter * 2)
// })
// let timer
// // 声明周期监听回调
// onMountd(()=>{
// // 定义定时器
// timer = setInterval(()=>{
// data.counter++
// }, 1000)
// })
// onUnmounted(()=>{
// // 取消定时器
// clearInterval(timer)
// })
// 其他逻辑
const msg2 = ref('something')
return {data, msg2}
}
}
// 提取:counter 相关操作逻辑
function useCounter() {
const data = reactive({
counter: 1,
// 计算属性
doubleCounter: computed(()=> data.counter * 2)
})
let timer
// 声明周期监听回调
onMountd(()=>{
// 定义定时器
timer = setInterval(()=>{
data.counter++
}, 1000)
})
onUnmounted(()=>{
// 取消定时器
clearInterval(timer)
})
return data
}
// 对象解包
const {counter, doubleCounter} = useCounter()
function useCounter() {
...
// 可以把响应式对象中的各个key 转换为单值响应式的ref数据
return toRefs(data)
}
// ref引用 DOM元素
<p ref='desc'></p>
...
setup() {
// ref响应式数据
const {counter, doubleCounter} = useCounter()
// reactive响应式数据
const data = useCounter()
...
// 使用元素引用
const desc = ref(null)
// ref监听器
watch(counter, (val, oldVal) => {
// 访问ref单值响应式数据 需要使用.value属性
const pElement = desc.value;
pElement.textContent = `value change ${val} ${oldValue}`
})
// reactive监听器
watch(() => data.counter, (val, oldVal) => {
// 访问ref单值响应式数据 需要使用.value属性
const pElement = desc.value;
pElement.textContent = `value change ${val} ${oldValue}`
})
return {data, counter, doubleCounter, desc}
}
Teleport
传送门组件:提供了一种简洁的方式可以指定它里面内容的父元素
<template>
<button @click="modalOpen = true">弹出一个全屏模态窗口</button>
<!-- 传送组件是一个全局组件 包含的就是要传送的内容 to指定的是一个选择器-->
<teleport to="body">
<div v-if="modalOpen" class="modal">
<div>
这是一个模态窗口
我的父元素是body
<button @click="modalOpen=false">Close</button>
</div>
</div>
</teleport>
</template>
<script>
export default {
setup(){
let modalOpen = ref(true)
}
}
</script>
<style scoped>
.modal {
position: absolute;
top:0; right:0; bottom:0; left:0;
background-color:rgba(0,0,0,0.5);
display:flex;
flex-direction: column;
align-items: center;
justify-content:center;
}
.modal div {
display: flex;
flex-direction: column;
align-items: center;
justify-content:center;
background-color:white;
width: 300px;
height: 300px;
padding: 5px;
}
</style>
Fragments
Vue3中组件可以拥有多个根;
<template>
<header>..
<main>..
<footer>..
</template>
Emits Component Option
vue3中组件发送的自定义事件需要定义在emits选项中:
// 定义组件 Emit
<Emit>
<template>
<div>
// 如果与原生事件名重名 那么该事件会被触发两次 通过在emits选项中进行配置 可以使触发只有一次
<p @click="$emit('click')">Click</p>
// 自定义事件会被触发 这可以更好的指示组件工作
<p @click="$emit('myClick')">MyClick</p>
</div>
</template>
export default {
// emits选项
emits: ['click','myClick']
}
</Emit>
// 使用组件 Emit
<Emit @click='onClick'></Emit>
<Emit @myClick='onClick'></Emit>
自定义渲染器 custom renderer
这个API可以用来自定义渲染逻辑;如自定义一个画布的渲染过程;
全局API调整
Global API改为应用程序实例调用
- Vue2没有app概念,
new Vue()
得到的根实例被作为app;这样的根实例是共享相同的全局配置,这在测试时会污染其他测试用例;Vue.xxx
- 全局配置也导致没有办法在单页面创建不同全局配置的多个app实例;
- vue3中使用
createApp
返回app实例,由它暴露一系列全局api;app.xxx
主要受影响的API:
- Vue.config <=> app.config
- Vue.component <=> app.component
- Vue.directive <=> app.directive
- Vue.mixin <=> app.mixin
- Vue.use <=> app.use
摇树优化
vue2中不少global-api是作为静态函数直接挂在在构造函数上,如Vue.nextTick()
,如果代码中未用到,会形成dead code
,这类代码无法使用webpack的tree-shaking
排除掉;
vue3中则将他们抽取为独立函数,这样打包工具的摇树优化可以将这些dead code排除掉;
import {nextTick} from 'vue'
nextTick(() => {
//
})
v-model调整
model选项 和 v-bind的sync修饰符被移除,统一为v-model参数形式;
// 示例:在子组件中修改传入的父组件值
// 父组件部分
<SubComponent v-model:counter='counter'></SubComponent>
// 等价于下面这个,这也是我们在vue2中使用的方式 显然现在更简洁了
// <SubComponent :counter='counter' @update:counter="counter=$event"></SubComponent>
// 子组件部分
<template>
<div @click="$emit('update:counter', counter + 1)"> Counter: {{counter}}
</div>
</template>
export default {
props: {
// 父组件如果不指定字段名 默认展开的字段名为 modelValue
counter: {
type: Number,
default:0
},
},
}
更好用的渲染函数API
- 不再传入h函数,需要手动导入;
- 拍平 props结构;
- 作用域插槽scopedSlots删掉了,统一到slots;
<RenderTest>
<template>默认插槽</template>
<template v-slot:content>具名插槽</template>
</RenderTest>
import {h} from 'vue'
componets:{
RenderTest: {
render(){
...
}
}
}
// ...的具体实现
render() {
const emit = this.$emit
const modelValue = this.modelValue
const onclick = this.onclick
// 获取插槽内容
this.$slots.default()
this.$slots.content()
retrun h('div', [
h('div', {
onClick() {
emit('update:modelValue', modelValue + 1)
},
`I am comp, ${modelValue}`
}),
h('button', {
onClick() {
onclick()
},
'buty it!'
})
])
}
失宠的函数式组件
函数式组件仅能通过简单函数方式创建,functional选项废弃;
异步组件的使用
异步组件要求使用defineAsyncComponent
方法创建;
- 必须使用
defineAsyncComponent
包裹 component
选项重命名为loader
- Loader函数不在接收
resolve
andreject
且必须返回一个Promise;
import {defineAsyncComponent} from 'vue'
// 不带配置的异步组件
const asyncPage = defineAsyncComponent(()=> import('./NextPage.vue'))
// 带配置的异步组件(这里loader选项就是以前的component选项)
import ErrorComponent from './components/ErrorComponent.vue'
import LoadingComponent from './components/LoadingComponent.vue'
const asyncPageWithOptions = defineAsyncComponent({
loader: () => import('./NextPage.vue'),
delay: 200,
timeout: 3000,
errorComponent: ErrorComponent,
loadingComponent: LoadingComponent
})
data选项
组件data选项应该总是声明为函数,返回响应式数据;
组件白名单
vue3中 自定义元素检测发生在模板编译时,如果要添加一些vue之外的自定义元素,避免编译时的检测,需要在编译器选项中设置isCustomElement
选项;
使用构建工具时,模板都会用vue-loader预编译,设置它提供的compilerOptions即可:
// vue.config.js
rules: [
{
test: /\.vue$/,
use: 'vue-loader',
options: {
compilerOptions: {
// 标签为plastic-button时 放过,可以设置多个
isCustomElement: tag => tag === 'plastic-button'
}
}
}
// ...
]
使用vite演示的话,在vite.config.js中配置 vueCompilerOptions即可:
module.exports = {
vueCompilerOptions: {
isCunstomElement: tag => tag === 'piechart'
}
}
如果采用的是运行时编译的vue,可以通过全局配置isCunstomElement:
const app = Vue.createApp({})
app.config.sCunstomElement = tag => tag === 'piechart'
is属性仅限于用在component标签上
vue3设置动态组件,使用is可以确定实际渲染的组件是哪一个;但在vue3中,is属性仅限于用在component标签上;
dom内模板解析可以使用v-is
指令替代;
<table>
// 实际渲染的是blog-post-row组件
<tr v-is="'blog-post-row'" :data='xxx'></tr>
</table>
component('blog-post-row', {
props: ['data'],
template: "<tr><td>{{this.data}}</td></tr>"
})
自定义指令API 和 组件保持一致
bind -> beforeMount
inserted -> mounted
beforeUpdate
update -> [removed]
componnetUpdated -> updated
beforeUnmount
unbind -> unmounted
createApp(App).directive('highlight', {
beforeMount(el, binding, vnode) {
el.style.background = binding.value
}
})
// 使用
<p v-highlight></p>
transition类名调整
- v-enter -> v-enter-from
- v-leave -> v-leave-from
v-enter-from
v-enter-active
v-enter-to
v-leave-from
v-leave-active
v-leave-to
<transition name='fade'>
<p v-if="show"> hellow </p>
</transition>
<style scoped>
// 淡入淡出
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s ease;
}
.fage-enter-from,
.fade-leave-to {
opacity: 0;
}
<style>
watch API重要调整
组件watch选项和实例方法$watch不再支持点分隔符字符串路径
可以使用计算函数作为$watch的参数实现对带路径的支持;
this.$watch(()=> this.foo.bar, (v1,v2) => {
})
keyCode 作为v-on
修饰符被移除
keyCode 作为v-on
修饰符被移除,只支持别名方式
// keycode不支持
<input v-on:keyup.13="submit" />
// alias支持
<input v-on:keyup.enter="submit" />
$on $off $once被删除了
这三个被认为是不应该由vue提供的,可以使用其他第三方库实现;
<script src="https://unpkg.com/mitt/dist/mitt.umd.js"></script>
// 创建emitter(放在公共的地方 并且导出)
export const emitter = mitt()
// import {emitter} from ...
// 发送事件
emitter.emit('foo', 'foooooo')
// 监听事件
emitter.on('foo', msg => console.log(msg))
filter也被移除了;
TodoList实践
MVC实现
功能预览:
代码实现(Vue3):
<!-- Todos.vue -->
<template>
<div>
<input
type="text"
v-model="newTodo"
@keyup.enter="addTodo"
autofocus
placeholder="新增今日待办"
autocomplete="off"
/>
<ul>
<li
v-for="todo in filterdTodos"
:key="todo.id"
:class="{completed: todo.completed, editing: todo === editedTodo}">
<div class="view">
<input type="checkbox" v-model="todo.completed">
<label @dblclick="editTodo(todo)">{{todo.title}}</label>
<button @click="removeTodo(todo)">X</button>
</div>
<!-- 双击:编辑待办 -->
<!-- 我们希望双击时 input可以自动获取焦点 这样体验更好 这里应用到自定义指令 -->
<input
type="text"
class="edit"
v-model="todo.title"
v-todo-focus="todo=== editedTodo"
@blur="doneEdit(todo)"
@keyup.enter="doneEdit(todo)"
@keyup.escape="cancelEdit(todo)"
/>
</li>
</ul>
<!-- 过滤功能:计算属性 + watch的应用 -->
<p class="filters">
<span @click="visibility = 'all'" :class="{selected: visibility === 'all'}">All</span>
<span @click="visibility = 'active'" :class="{selected: visibility === 'active'}">Active</span>
<span @click="visibility = 'completed'" :class="{selected: visibility === 'completed'}">Completed</span>
</p>
</div>
</template>
<script>
import { reactive, toRefs, computed, watchEffect } from 'vue'
const filters = {
all (todos) {
console.log('tag', 'all')
return todos
},
active (todos) {
console.log('tag', 'active')
return todos.filter(todo => !todo.completed)
},
completed (todos) {
console.log('tag', 'completed')
return todos.filter(todo => todo.completed)
}
}
// 缓存操作:刷新数据不丢失
const todoStorage = {
fetch () {
const todos = JSON.parse(localStorage.getItem('vue3-todos') || '[]')
todos.forEach((todo, index) => {
todo.id = index + 1
})
return todos
},
save (todos) {
localStorage.setItem('vue3-todos', JSON.stringify(todos))
}
}
export default {
setup () {
const state = reactive({
newTodo: '',
beforeEditCache: '', // 缓存编辑前的title
editedTodo: null,
todos: todoStorage.fetch(),
visibility: 'all',
filterdTodos: computed(() => {
// 计算属性
return filters[state.visibility](state.todos)
})
})
function addTodo () {
state.todos.push({
id: state.todos.length + 1,
title: state.newTodo,
completed: false
})
state.newTodo = ''
}
function editTodo (todo) {
state.beforeEditCache = todo.title
state.editedTodo = todo
}
function removeTodo (todo) {
state.todos.splice(state.todos.indexOf(todo), 1)
}
function doneEdit (todo) {
// 数据的变更 已经 由双向绑定完成 这里只需要把editTodo置空即可
state.editedTodo = null
}
function cancelEdit (todo) {
todo.title = state.beforeEditCache
state.editedTodo = null
}
// 监听副作用
watchEffect(() => {
// 只要todos改变 就缓存
todoStorage.save(state.todos)
})
return {
...toRefs(state),
addTodo,
editTodo,
removeTodo,
doneEdit,
cancelEdit
}
},
directives: {
'todo-focus': (el, { value }) => {
if (value) {
el.focus()
}
}
}
}
</script>
<style scoped>
.completed label{
/* 删除装饰线 */
text-decoration: line-through;
}
.edit,
.editing .view {
display: none;
}
.view,
.editing .edit {
display: block;
}
.filters > span {
cursor: pointer;
padding: 5px 10px;
border: 1px solid transparent;
}
.filters > span.selected {
border: 1px solid black;
border-radius: 5px;
}
</style>
组件化实践
针对上边的Todos进行组件化优化重构;
先来看看利用组件化和 Composition API 优化后的Todos.vue
:
<template>
<div>
<EditTodo
v-model:todo-title="newTodo"
@keyup.enter="addTodo"
autofocus
placeholder="新增今日待办"
autocomplete="off"
></EditTodo>
<ul>
<TodoItem
v-for="todo in filterdTodos"
:key="todo.id"
:todoC="todo"
v-model:edited-todo="editedTodo"
@remove-todo="removeTodo"
></TodoItem>
</ul>
<!-- 过滤功能:计算属性 + watch的应用 -->
<Filter
:items="filterItems"
v-model="visibility"
></Filter>
</div>
</template>
<script>
import { reactive, toRefs } from 'vue'
import TodoItem from './TodoItem.vue'
import Filter from './Filter.vue'
import { useTodos } from './useTodos.js'
import { useFilter } from './useFilter.js'
export default {
components: {
TodoItem,
Filter
},
setup () {
const todoState = reactive({
newTodo: '',
editedTodo: null
})
const { todos, addTodo, removeTodo } = useTodos(todoState)
const filterState = useFilter(todos)
return {
...toRefs(todoState),
...toRefs(filterState),
addTodo,
removeTodo
}
}
}
</script>
<style scoped>
</style>
具体的变化:
- 组件部分:
- input标签进行了组件封装,参考
EditTodo.vue
; - 值得注意的是
EditTodo
组件是在main.js
中被注册为全局组件,参考main.js
; - li标签及其内容进行了组件提取,参考
TodoItem.vue
; - 过滤的三个功能按钮进行了组件化封装,参考
Filter.vue
;
- input标签进行了组件封装,参考
- 组合API调整:
- Todos相关操作(添加、移除、缓存)的逻辑
状态
提取到了useTodos.js
中; - Fliter相关
状态
(响应式) 提取到了useFilter.js
中; - 之后将组合之后的状态、操作 return出去;
- Todos相关操作(添加、移除、缓存)的逻辑
EditTodo.vue
$attrs
<template>
<input
type="text"
:value="todoTitle"
@input="onInputChange"
v-bind="$attrs"
/>
</template>
<script>
export default {
props: {
todoTitle: {
type: String,
default: ''
}
},
methods: {
onInputChange (e) {
this.$emit('update:todoTitle', e.target.value)
}
}
}
</script>
<style>
</style>
main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import EditTodo from './components/todos/EditTodo.vue'
createApp(App)
.component('EditTodo', EditTodo) // 全局注册组件(局部注册与之前一致)
.directive('hightlight', { // 注册全局指令
beforeMount (el, binding, vnode) {
el.style.background = binding.value
}
})
.use(store)
.use(router)
.mount('#app')
TodoItem.vue
emit配置项、自定义指令
<template>
<li
:class="{completed: todo.completed, editing: todo === editedTodo}">
<div class="view">
<input type="checkbox" v-model="todo.completed">
<label @dblclick="editTodo(todo)">{{todo.title}}</label>
<button @click="removeTodo(todo)">X</button>
</div>
<!-- 双击:编辑待办 -->
<!-- 我们希望双击时 input可以自动获取焦点 这样体验更好 这里应用到自定义指令 -->
<EditTodo
class="edit"
v-model:todo-title="todo.title"
v-todo-focus="todo=== editedTodo"
@blur="doneEdit(todo)"
@keyup.enter="doneEdit(todo)"
@keyup.escape="cancelEdit(todo)"
></EditTodo>
</li>
</template>
<script>
import { reactive, toRefs } from 'vue'
export default {
props: {
todoC: {
type: Object,
required: true
},
editedTodo: Object
},
// emit配置项
emits: ['remove-todo', 'update:edited-todo'],
setup (props, { emit }) {
const state = reactive({
todo: props.todoC, // 避免v-model绑定修改单向数据流造成的异常
beforeEditCache: '' // 缓存编辑前的title
})
function editTodo (todo) {
state.beforeEditCache = todo.title
emit('update:edited-todo', todo)
}
function removeTodo (todo) {
emit('remove-todo', todo)
}
function doneEdit (todo) {
// 数据的变更 已经 由双向绑定完成 这里只需要把editTodo置空即可
emit('update:edited-todo', null)
}
function cancelEdit (todo) {
todo.title = state.beforeEditCache
emit('update:edited-todo', null)
}
return {
...toRefs(state),
editTodo,
removeTodo,
doneEdit,
cancelEdit
}
},
directives: {
'todo-focus': (el, { value }) => {
if (value) {
el.focus()
}
}
}
}
</script>
<style scoped>
.completed label{
/* 删除装饰线 */
text-decoration: line-through;
}
.edit,
.editing .view {
display: none;
}
.view,
.editing .edit {
display: block;
}
</style>
Filter.vue
<template>
<p class="filters">
<span
v-for="item in items"
:key='item.value'
@click="$emit('update:modelValue', item.value)"
:class="{selected: modelValue === item.value}">{{item.label}}</span>
</p>
</template>
<script>
export default {
props: ['items', 'modelValue'],
emits: ['update:modelValue']
}
</script>
<style scoped>
.filters > span {
cursor: pointer;
padding: 5px 10px;
border: 1px solid transparent;
}
.filters > span.selected {
border: 1px solid black;
border-radius: 5px;
}
</style>
useTodos.js
watchEffect
import { ref, watchEffect } from 'vue'
// 缓存操作:刷新数据不丢失
const todoStorage = {
fetch () {
const todos = JSON.parse(localStorage.getItem('vue3-todos') || '[]')
todos.forEach((todo, index) => {
todo.id = index + 1
})
return todos
},
save (todos) {
localStorage.setItem('vue3-todos', JSON.stringify(todos))
}
}
export function useTodos (state) {
const todos = ref(todoStorage.fetch())
function addTodo () {
todos.value.push({
id: todos.value.length + 1,
title: state.newTodo,
completed: false
})
state.newTodo = ''
}
function removeTodo (todo) {
todos.value.splice(todos.value.indexOf(todo), 1)
}
// 监听副作用
watchEffect(() => {
// 只要todos改变 就缓存
todoStorage.save(todos.value)
})
return {
todos,
addTodo,
removeTodo
}
}
useFilter.js
import { reactive, computed } from 'vue'
const filters = {
all (todos) {
console.log('tag', 'all')
return todos
},
active (todos) {
console.log('tag', 'active')
return todos.filter(todo => !todo.completed)
},
completed (todos) {
console.log('tag', 'completed')
return todos.filter(todo => todo.completed)
}
}
export function useFilter (todos) {
const filterState = reactive({
filterItems: [
{ label: 'All', value: 'all' },
{ label: 'Active', value: 'active' },
{ label: 'Completed', value: 'completed' }
],
visibility: 'all',
filterdTodos: computed(() => {
// 计算属性
return filters[filterState.visibility](todos.value)
})
})
return filterState
}
vue-router 4.x
<router-view>
<router-link to="/">
// 路由实例注册:
createApp(App)
.use(router)
.mount('#app')
// 路由实例创建
import {createRouter, createWebHistory} from 'vue-router'
const routes = [{
path: '/user',
name: 'user-layout',
component: () => import('@/layout/user-layout/user-layout'),
redirect: '/user/login',//如果将第一个child的path设置为空串 这里可以不使用redirect 重定向
children: [{
path: '/user/login',
name: 'login',
component: () => import('@/views/user/login')
}]
},
{
path: 'path: "/:pathMatch(.*)*"',
name: '404',
// 每个路由中都可以携带一个元数据对象 用于携带信息
meta: {
auth: false,
title: 'notFound'
},
component: () =>
import( /* webpackChunkName: "fail" */ '@/views/error/notFound')
}
];
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
})
// 路由动态添加
router.addRoute({
path:'/about',
name:'about',
component: () => import('./components/About.vue')
})
router.addRoute('about',{
path: './about/info',
component: {
render() {
return h('div', 'info page')
}
}
})
// 结合组合API的使用
import {useRouter, useRoute} from "vue-router"
import {watch} from "vue"
setup(){
const router = useRouter();
const route = useRoute();
// 监听route
watch(()=>route.params,(newparams)=>{
})
watch(()=>route.query,(newquery)=>{
})
// 守卫
onBeforeRouteLeave((to,from) => {
const answer = window.confirm("Sure Leave?")
if (!answer){
return false
}
})
onBeforeRouteUpdate((to,from) => {
})
return {}
}
<!-- // 新接口 useLink的使用(如包装 router-link标签称为更高级的组件)
// 创建 NavLink.vue -->
<template>
<div :class="{active: isActive}" @click="navigate">
{{route.name}}
</div>
</template>
<script>
import {RouterLink, useLink} from 'vue-router'
export default {
props: {
...RouterLink.props,
inactiveClass: String
},
setup(props) {
const {route, href, isActive, isExactActive, navigate} = useLink(props)
return {route, isActive, navigate}
}
}
</script>
<style scoped>
.active {
background-color: #3fb983;
}
</style>
history选项 代替了mode选项
- history =>
createWebHistory('/base-prefix')
可以指定公共的路由前缀 - hash =>
createWebHashHistory()
- abstract =>
createMemoryHistory()
用于服务端渲染
base选项移至 createWebHistory方法中;
通配符(*
)被移除了,如而代之的是:
path: '*'
=>path: "/:pathMatch(.*)*"
// 使用命名导航至404页面
router.resolve({
name:'404',
params: {
pathMatch: ['not', 'found']
}
})
- isReady() 替代了 onReady()
router.push()
// before
router.onReady(onSuccess, onError)
// new
router.isReady().then(onSuccess).catch(onError)
scrollBehavior
行为发生了变化
const router = createRouter({
history:...,
routes:[],
scrollBehavior(to, from, savedPosition){
// {x:10, y:10} before
// {left:10, top:10} new
if (savedPosition) {
// 记录并设置 页面的滚动位置
return savedPosition
}ekse {
return {top: 10}
}
}
})
keep-alive
和transition
必须用在router-view
内部- 以前是在外边的
<router-view v-slot="{Component}">
<keep-alive>
<component :is="Component" />
</keep-alive>
</router-view>
router-link
移除了一些属性- append属性
- tag/event
- exact:完全比配逻辑简化了 仅支持完全相同
// before append
<router-link to="child-route" append>
// byself
<router-link :to="append($route.path, 'child-route')">
app.config.globalPropertier.append = (path, pathToAppend) => {
return path + pathToAppend
}
// before tag
<router-link to="/xx" tag="span" event="dblclick"></router-link>
// new
<router-link to="/xx" custom v-slot="{navigate}">
<span @dblclick="navigate">xxx</span>
</router-link>
- mixins中的路由守卫将被忽略;
- match方法被移除了,使用resolve替代;
- resolve返回的是一个location,多数路由跳转还是用push,push返回的是一个promise;
- 移除
router.getMatchedComponents()
,用于服务端渲染的;- 可以用
router.currentRoute.value.matched
这种方式访问匹配的路由;
- 可以用
- 包括首屏导航在内所有导航均为异步;
- 如果首屏存在路由守卫,则可以不等待就绪就直接挂载
// 如果需要首屏前动画
app.use(router)
router.isReady().then(()=> app.mount('#app'))
- route的parent属性被移除
- 替换:
const parent = this.$route.matched[this.$route.matched.length - 2]
- 替换:
createRouter({
strict: boolean,
sensitive: boolean
})
- router创建时 routes选项是必填项;
- 跳转不存在的命名路由 会报错;
- 必填参数缺失会抛异常;
- 命名子路由 如果path为空 将不再追加
/
;- 这可能会给设置了重定向
redirect
选项的子路由带来副作用;
- 这可能会给设置了重定向
vuex 4.x
vuex4是vuex3的兼容版本,提供和vuex3相同的API;
// 创建store实例
import {createStore} from 'vuex'
const store = createStore({
state() {
return {
count: 1
}
},
mutations: {
add(state){
state.count++;
}
}
})
app.use(store)
// composition API
import {useStore} from 'vuex'
const store = useStore()
return {
...toRefs(store.state),
add() {
store.commit('add')
}
}