本文主要是介绍Web开发-Vue3.0上手 如果你忘了 看这一pian就够了!,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
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: {// 父组件如果不指定字段名 默认展开的字段名为 modelValuecounter: {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.$emitconst modelValue = this.modelValueconst 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-activev-enter-tov-leave-from
v-leave-activev-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><inputtype="text"v-model="newTodo"@keyup.enter="addTodo"autofocusplaceholder="新增今日待办"autocomplete="off"/><ul><liv-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可以自动获取焦点 这样体验更好 这里应用到自定义指令 --><inputtype="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: '', // 缓存编辑前的titleeditedTodo: 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.titlestate.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.beforeEditCachestate.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><EditTodov-model:todo-title="newTodo"@keyup.enter="addTodo"autofocusplaceholder="新增今日待办"autocomplete="off"></EditTodo><ul><TodoItemv-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><inputtype="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可以自动获取焦点 这样体验更好 这里应用到自定义指令 --><EditTodoclass="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.titleemit('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.beforeEditCacheemit('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"><spanv-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();// 监听routewatch(()=>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} newif (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')}
}
这篇关于Web开发-Vue3.0上手 如果你忘了 看这一pian就够了!的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!