…
VUE
Vue 官网
Vue API
安装
Vue 不支持 IE8 及以下版本,因为 Vue 使用了 IE8 无法模拟的 ECMAScript 5 特性。
使用npm安装:
创建项目
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| vue create app_name
|
创建好的APP目录如下:
- app_name
- node_modules/
- public/
- src/
- assets/ 资源文件
- components/ 公共组件
- router/ 前端路由
- store/ 应用数据 state
- views/ 页面目录
- App.vue 根 APP
- main.js 项目入口
- tests/
- .browserslistrc
- .gitignore
- babel.config.js
- jest.config.js
- package-lock.json
- package.json
- README.md
Vue 实例 生命周期
- new Vue()
- 初始化:事件,声明周期
- beforeCreate()
- 初始化,注入,校验
- created
- 是否指定 el
- 是否指定 template
- 将template/el翻译渲染
- beforeMount
- 创建vm.$el,并替换el
- mounted
- 挂载完毕
- data被修改
- beforeUpdate
- 重新渲染
- updated
- beforeDestroy
- 解除绑定,销毁子组件
- destroyed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <template> <div class="hello"> <h1>{{ msg }}</h1> </div> </template>
<script> export default { name: 'HelloWorld', props: { msg: String } } </script>
<style scoped lang="scss"> h1 { margin: 40px 0 0; } </style>
|
模板
插值:
1 2
| {{ msg }} {{ message.split('').reverse().join('') }}
|
指令:
1 2 3 4 5 6 7 8 9 10 11 12 13
| <span v-html="rawHtml"></span> <span v-once>这个将不会改变: {{ msg }}</span>
<div v-bind:id="dynamicId"></div> <button :disabled="isButtonDisabled">Button</button>
<a v-on:click="doSomething">...</a> <a @click="doSomething">...</a> <form @submit.prevent="onSubmit">...</form>
<a v-bind:[attributeName]="url"> ... </a>
|
Class与Style:
1 2 3 4 5 6 7 8
| <div :class="{ active: isActive }"></div> <div class="static" v-bind:class="{ active: isActive, 'text-danger': hasError }" ></div>
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div> <div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>
|
条件渲染:
1 2 3 4 5 6 7
| <p v-if="seen">现在你看到我了</p> <h1 v-else-if="ok">Oh no</h1> <h1 v-else>Oh no</h1>
<h1 v-show="ok">Hello!</h1>
|
列表渲染:
1 2 3 4 5 6 7
| <li v-for="item in items" :key="item.id"> {{ item.message }} </li>
<li v-for="(item, index) in items" :key="item.id"> {{ item.message }} </li>
|
监听事件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <button @click="counter += 1">Add 1</button> <button @click="greet">Greet</button> <button @click="say('hello', $event)">Greet</button>
<input @click.left="submit"> <input @click.right="submit"> <input @click.middle="submit">
<a @click.stop="doThis"></a>
<form @submit.prevent="onSubmit"></form>
<a @click.stop.prevent="doThat"></a>
<form @submit.prevent></form>
<div @click.capture="doThis">...</div>
<div @click.self="doThat">...</div>
<input @keyup.enter="submit">
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| export default { name: 'HelloWorld', methods: { greet: function (event) { alert('Hello ' + this.name + '!') if (event) { alert(event.target.tagName) } }, say: function (message, event) { alert(message) } } }
|
表单输入绑定:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| <input v-model="message" placeholder="edit me"> <p>Message is: {{ message }}</p>
<input type="checkbox" id="checkbox" v-model="checked"> <label for="checkbox">{{ checked }}</label>
<div id="example-4"> <input type="radio" id="one" value="One" v-model="picked"> <label for="one">One</label> <br> <input type="radio" id="two" value="Two" v-model="picked"> <label for="two">Two</label> <br> <span>Picked: {{ picked }}</span> </div>
<div id="example-5"> <select v-model="selected"> <option disabled value="">请选择</option> <option>A</option> <option>B</option> <option>C</option> </select> <span>Selected: {{ selected }}</span> </div>
<input v-model.lazy="msg" > <input v-model.number="age" type="number"> <input v-model.trim="msg">
|
组件
1 2 3 4 5 6 7 8
| data: function () { return { count: 0 } },
props: ['title'],
|
子组件可以通过emit触发事件:
1 2 3 4 5 6 7 8 9
| <button @click="$emit('enlarge-text')"> Enlarge text </button>
<blog-post ... @enlarge-text="onEnlargeText" ></blog-post>
|
也可以使用v-model:
1 2 3 4 5 6 7
| <custom-input :value="searchText" @input="searchText = $event" ></custom-input>
<custom-input v-model="searchText"></custom-input>
|
1 2 3 4 5 6 7 8 9 10
| Vue.component('custom-input', { props: ['value'], template: ` <input v-bind:value="value" v-on:input="$emit('input', $event.target.value)" > ` })
|
组件可以使用插槽:
组件注册
1 2 3 4 5 6 7 8 9 10 11 12 13
| Vue.component('my-component-name', { })
var ComponentA = { } new Vue({ el: '#app', components: { 'component-a': ComponentA, 'component-b': ComponentB } })
|
1 2 3 4 5 6 7 8 9 10
| import ComponentA from './ComponentA' import ComponentC from './ComponentC'
export default { components: { ComponentA, ComponentC }, }
|
Prop
驼峰命名法在使用时应写为短横线分割命名。
类型:
1 2 3 4 5 6 7 8 9
| props: { title: String, likes: Number, isPublished: Boolean, commentIds: Array, author: Object, callback: Function, contactsPromise: Promise }
|
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。
验证:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| Vue.component('my-component', { props: { propA: Number, propB: [String, Number], propC: { type: String, required: true }, propD: { type: Number, default: 100 }, propE: { type: Object, default: function () { return { message: 'hello' } } }, propF: { validator: function (value) { return ['success', 'warning', 'danger'].indexOf(value) !== -1 } } } })
|
Model
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| Vue.component('base-checkbox', { model: { prop: 'checked', event: 'change' }, props: { checked: Boolean }, template: ` <input type="checkbox" v-bind:checked="checked" v-on:change="$emit('change', $event.target.checked)" > ` })
|
绑定原生事件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| Vue.component('base-input', { inheritAttrs: false, props: ['label', 'value'], computed: { inputListeners: function () { var vm = this return Object.assign({}, this.$listeners, { input: function (event) { vm.$emit('input', event.target.value) } } ) } }, template: ` <label> {{ label }} <input v-bind="$attrs" v-bind:value="value" v-on="inputListeners" > </label> ` })
|
Slot
1 2 3 4 5 6 7 8 9 10
| <a v-bind:href="url" class="nav-link" > <slot>Default</slot> <slot name="sname">Default</slot> </a>
<template v-slot:sname> <template #sname>
|
动态组件
keep-alive 可以保存状态,防止重新渲染。
1 2 3
| <keep-alive> <a v-bind:is="currentTabComponent"></a> </keep-alive>
|
异步组件
1 2 3 4 5 6
| new Vue({ components: { 'my-component': () => import('./my-async-component') } })
|
访问元素 & 组件
1 2 3 4 5 6
| this.$root
this.$parent.map || this.$parent.$parent.map
this.$refs.usernameInput
|
计算属性与侦听器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| export default { name: 'HelloWorld', data: { message: 'Hello', firstName: 'Foo', }, computed: { reversedMessage: function () { return this.message.split('').reverse().join('') } }, computed: { reversedMessage: { get: function() { return "" }, set: function(newValue) { } } }, watch: { firstName: function (newValue, oldValue) { } } }
|
过渡动画
当插入或删除包含在 transition 组件中的元素时,Vue 将会做以下处理:
- 自动嗅探目标元素是否应用了 CSS 过渡或动画,如果是,在恰当的时机添加/删除 CSS 类名。
- 如果过渡组件提供了 JavaScript 钩子函数,这些钩子函数将在恰当的时机被调用。
- 如果没有找到 JavaScript 钩子并且也没有检测到 CSS 过渡/动画,DOM 操作 (插入/删除) 在下一帧中立即执行。(注意:此指浏览器逐帧动画机制,和 Vue 的 nextTick 概念不同)
1 2 3 4 5 6 7 8
| <div id="demo"> <button v-on:click="show = !show"> Toggle </button> <transition name="fade" :duration="{ enter: 500, leave: 800 }"> <p v-if="show">hello</p> </transition> </div>
|
1 2 3 4 5 6
| new Vue({ el: '#demo', data: { show: true } })
|
1 2 3 4 5 6
| .fade-enter-active, .fade-leave-active { transition: opacity .5s; } .fade-enter, .fade-leave-to { opacity: 0; }
|
过渡类名有:
1 2 3 4 5 6
| .v-enter .v-enter-active .v-enter-to .v-leave .v-leave-active .v-leave-to
|
也可以使用关键帧动画:
1 2 3 4 5 6
| <div id="example-2"> <button @click="show = !show">Toggle show</button> <transition name="bounce"> <p v-if="show">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris facilisis enim libero, at lacinia diam fermentum id. Pellentesque habitant morbi tristique senectus et netus.</p> </transition> </div>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| .bounce-enter-active { animation: bounce-in .5s; } .bounce-leave-active { animation: bounce-in .5s reverse; } @keyframes bounce-in { 0% { transform: scale(0); } 50% { transform: scale(1.5); } 100% { transform: scale(1); } }
|
也可以自定义过渡类名:
1 2 3 4 5 6
| .enter-class .enter-active-class .enter-to-class .leave-class .leave-active-class .leave-to-class
|
例如
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <link href="https://cdn.jsdelivr.net/npm/animate.css@3.5.1" rel="stylesheet" type="text/css">
<div id="example-3"> <button @click="show = !show"> Toggle render </button> <transition name="custom-classes-transition" enter-active-class="animated tada" leave-active-class="animated bounceOutRight" > <p v-if="show">hello</p> </transition> </div>
|
也可以使用钩子:
1 2 3 4 5 6 7 8 9 10 11 12 13
| <transition v-on:before-enter="beforeEnter" v-on:enter="enter" v-on:after-enter="afterEnter" v-on:enter-cancelled="enterCancelled"
v-on:before-leave="beforeLeave" v-on:leave="leave" v-on:after-leave="afterLeave" v-on:leave-cancelled="leaveCancelled" > </transition>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| new Vue({ el: '#example-4', data: { show: false }, methods: { beforeEnter: function (el) { el.style.opacity = 0 el.style.transformOrigin = 'left' }, enter: function (el, done) { Velocity(el, { opacity: 1, fontSize: '1.4em' }, { duration: 300 }) Velocity(el, { fontSize: '1em' }, { complete: done }) }, leave: function (el, done) { Velocity(el, { translateX: '15px', rotateZ: '50deg' }, { duration: 600 }) Velocity(el, { rotateZ: '100deg' }, { loop: 2 }) Velocity(el, { rotateZ: '45deg', translateY: '30px', translateX: '30px', opacity: 0 }, { complete: done }) } } })
|
设置初始渲染的过渡
1 2 3 4 5 6 7 8
| <transition appear appear-class="custom-appear-class" appear-to-class="custom-appear-to-class" (2.1.8+) appear-active-class="custom-appear-active-class" > </transition>
|
过渡模式:
1 2 3
| <transition name="fade" mode="out-in"> </transition>
|
mode:
in-out
out-in
列表过渡:
1 2 3 4 5 6 7 8 9 10
| <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.14.1/lodash.min.js"></script>
<div id="flip-list-demo" class="demo"> <button v-on:click="shuffle">Shuffle</button> <transition-group name="flip-list" tag="ul"> <li v-for="item in items" v-bind:key="item"> {{ item }} </li> </transition-group> </div>
|
1 2 3 4 5 6 7 8 9 10 11
| new Vue({ el: '#flip-list-demo', data: { items: [1,2,3,4,5,6,7,8,9] }, methods: { shuffle: function () { this.items = _.shuffle(this.items) } } })
|
1 2 3
| .flip-list-move { transition: transform 1s; }
|
状态过渡:
1 2 3 4 5 6
| <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.20.3/TweenMax.min.js"></script>
<div id="animated-number-demo"> <input v-model.number="number" type="number" step="20"> <p>{{ animatedNumber }}</p> </div>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| new Vue({ el: '#animated-number-demo', data: { number: 0, tweenedNumber: 0 }, computed: { animatedNumber: function() { return this.tweenedNumber.toFixed(0); } }, watch: { number: function(newValue) { TweenLite.to(this.$data, 0.5, { tweenedNumber: newValue }); } } })
|
Router
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <script src="https://unpkg.com/vue/dist/vue.js"></script> <script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<div id="app"> <h1>Hello App!</h1> <p> <router-link to="/foo">Go to Foo</router-link> <router-link to="/bar">Go to Bar</router-link> </p> <router-view></router-view> </div>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
const Foo = { template: '<div>foo</div>' } const Bar = { template: '<div>bar</div>' }
const routes = [ { path: '/foo', component: Foo }, { path: '/bar', component: Bar } ]
const router = new VueRouter({ routes })
const app = new Vue({ router }).$mount('#app')
|
组件内访问路由器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| export default { computed: { username() { return this.$route.params.username } }, methods: { goBack() { window.history.length > 1 ? this.$router.go(-1) : this.$router.push('/') } } }
|
路由的动态匹配
一个“路径参数”使用冒号 :
标记。当匹配到一个路由时,参数值会被设置到 this.$route.params,可以在每个组件内使用。
1 2 3 4 5 6
| const router = new VueRouter({ routes: [ { path: '/user/:id', component: User } ] })
|
响应路由参数的变化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const User = { template: '...', watch: { '$route' (to, from) { } } }
const User = { template: '...', beforeRouteUpdate (to, from, next) { } }
|
捕获 404
1 2 3 4 5 6 7 8
| { path: '*' } { path: '/user-*' }
|
当使用一个通配符时,$route.params 内会自动添加一个名为 pathMatch 参数。它包含了 URL 通过通配符被匹配的部分。
正则匹配
使用 path-to-regexp 作为引擎。
嵌套路由
使用children参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| const router = new VueRouter({ routes: [ { path: '/user/:id', component: User, children: [ { path: 'profile', component: UserProfile }, { path: 'posts', component: UserPosts } ] } ] })
|
此时,基于上面的配置,当你访问 /user/foo 时,User 的出口是不会渲染任何东西,这是因为没有匹配到合适的子路由。如果你想要渲染点什么,可以提供一个 空的 子路由:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const router = new VueRouter({ routes: [ { path: '/user/:id', component: User, children: [ { path: '', component: UserHome },
] } ] })
|
编程式导航
除了使用 <router-link>
创建 a 标签来定义导航链接,我们还可以借助 router 的实例方法,通过编写代码this.$router.push
来实现。点击 <router-link :to="...">
等同于调用 router.push(...)
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| router.push('home')
router.push({ path: 'home' })
router.push({ name: 'user', params: { userId: '123' }})
router.push({ path: 'register', query: { plan: 'private' }})
const userId = '123' router.push({ name: 'user', params: { userId }}) router.push({ path: `/user/${userId}` })
router.push({ path: '/user', params: { userId }})
|
router.replace(location, onComplete?, onAbort?)
跟 router.push 很像,唯一的不同就是,它不会向 history 添加新记录,而是跟它的方法名一样 —— 替换掉当前的 history 记录。
router.go(n)
这个方法的参数是一个整数,意思是在 history 记录中向前或者后退多少步,类似 window.history.go(n)。
命名路由
1 2 3 4 5 6 7 8 9
| const router = new VueRouter({ routes: [ { path: '/user/:userId', name: 'user', component: User } ] })
|
1
| <router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>
|
命名视图
有时候想同时 (同级) 展示多个视图,而不是嵌套展示,例如创建一个布局,有 sidebar (侧导航) 和 main (主内容) 两个视图,这个时候命名视图就派上用场了。你可以在界面中拥有多个单独命名的视图,而不是只有一个单独的出口。如果 router-view 没有设置名字,那么默认为 default。
1 2 3
| <router-view class="view one"></router-view> <router-view class="view two" name="a"></router-view> <router-view class="view three" name="b"></router-view>
|
1 2 3 4 5 6 7 8 9 10 11 12
| const router = new VueRouter({ routes: [ { path: '/', components: { default: Foo, a: Bar, b: Baz } } ] })
|
嵌套命名视图
1 2 3 4 5 6 7
| <div> <h1>User Settings</h1> <NavBar/> <router-view/> <router-view name="helper"/> </div>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| { path: '/settings', component: UserSettings, children: [{ path: 'emails', component: UserEmailsSubscriptions }, { path: 'profile', components: { default: UserProfile, helper: UserProfilePreview } }] }
|
重定向和别名
重定向:
1 2 3 4 5 6 7 8 9 10
| const router = new VueRouter({ routes: [ { path: '/a', redirect: '/b' }, { path: '/a', redirect: { name: 'foo' }}, { path: '/a', redirect: to => { }} ] })
|
别名:
1 2 3 4 5
| const router = new VueRouter({ routes: [ { path: '/a', component: A, alias: '/b' } ] })
|
路由组件传参
在组件中使用 $route 会使之与其对应路由形成高度耦合,从而使组件只能在某些特定的 URL 上使用,限制了其灵活性。
使用 props 将组件和路由解耦:
1 2 3 4 5 6 7 8
| const User = { template: '<div>User {{ $route.params.id }}</div>' } const router = new VueRouter({ routes: [ { path: '/user/:id', component: User } ] })
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const User = { props: ['id'], template: '<div>User {{ id }}</div>' } const router = new VueRouter({ routes: [ { path: '/user/:id', component: User, props: true }, { path: '/user/:id', components: { default: User, sidebar: Sidebar }, props: { default: true, sidebar: false } } ] })
|
History 模式
vue-router 默认 hash 模式 —— 使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载。
如果不想要很丑的 hash,我们可以用路由的 history 模式,这种模式充分利用 history.pushState API 来完成 URL 跳转而无须重新加载页面。
1 2 3 4
| const router = new VueRouter({ mode: 'history', routes: [...] })
|
不过这种模式要玩好,还需要后台配置支持。因为我们的应用是个单页客户端应用,如果后台没有正确的配置,当用户在浏览器直接访问 http://oursite.com/user/id 就会返回 404,这就不好看了。
后台nginx配置:
1 2 3
| location / { try_files $uri $uri/ /index.html; }
|
懒加载
首先,可以将异步组件定义为返回一个 Promise 的工厂函数 (该函数返回的 Promise 应该 resolve 组件本身):
1
| const Foo = () => Promise.resolve({ })
|
第二,在 Webpack 2 中,我们可以使用动态 import语法来定义代码分块点 (split point):
结合这两者,这就是如何定义一个能够被 Webpack 自动代码分割的异步组件。
1
| const Foo = () => import('./Foo.vue')
|
在路由配置中什么都不需要改变,只需要像往常一样使用 Foo:
1 2 3 4 5
| const router = new VueRouter({ routes: [ { path: '/foo', component: Foo } ] })
|
导航守卫
导航守卫主要用来通过跳转或取消的方式守卫导航。有多种机会植入路由导航过程中:全局的, 单个路由独享的, 或者组件级的。参数或查询的改变并不会触发进入/离开的导航守卫。
全局前置守卫:
1 2 3 4 5
| const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => { })
|
当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于 等待中。
每个守卫方法接收三个参数:
- to: Route: 即将要进入的目标 路由对象
- from: Route: 当前导航正要离开的路由
- next: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。
- next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。
- next(false): 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。
- next(‘/‘) 或者 next({ path: ‘/‘ }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向 next 传递任意位置对象,且允许设置诸如 replace: true、name: ‘home’ 之类的选项以及任何用在 router-link 的 to prop 或 router.push 中的选项。
- next(error): (2.4.0+) 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调。
全局解析守卫
在 2.5.0+ 你可以用 router.beforeResolve 注册一个全局守卫。这和 router.beforeEach 类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。
全局后置钩子
和守卫不同的是,这些钩子不会接受 next 函数也不会改变导航本身:
1 2 3
| router.afterEach((to, from) => { })
|
路由独享守卫
1 2 3 4 5 6 7 8 9 10 11
| const router = new VueRouter({ routes: [ { path: '/foo', component: Foo, beforeEnter: (to, from, next) => { } } ] })
|
组件内守卫
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const Foo = { template: `...`, beforeRouteEnter (to, from, next) { }, beforeRouteUpdate (to, from, next) { }, beforeRouteLeave (to, from, next) { } }
|
完整的导航解析流程:
导航被触发。
在失活的组件里调用离开守卫。
调用全局的 beforeEach 守卫。
在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
在路由配置里调用 beforeEnter。
解析异步路由组件。
在被激活的组件里调用 beforeRouteEnter。
调用全局的 beforeResolve 守卫 (2.5+)。
导航被确认。
调用全局的 afterEach 钩子。
触发 DOM 更新。
用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数。
路由元信息
定义路由的时候可以配置 meta 字段:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const router = new VueRouter({ routes: [ { path: '/foo', component: Foo, children: [ { path: 'bar', component: Bar, meta: { requiresAuth: true } } ] } ] })
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| router.beforeEach((to, from, next) => { if (to.matched.some(record => record.meta.requiresAuth)) { if (!auth.loggedIn()) { next({ path: '/login', query: { redirect: to.fullPath } }) } else { next() } } else { next() } })
|
过渡效果:
1 2 3
| <transition :name="transitionName"> <router-view></router-view> </transition>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const Foo = { template: ` <transition name="slide"> <div class="foo">...</div> </transition> ` }
const Bar = { template: ` <transition name="fade"> <div class="bar">...</div> </transition> ` }
|
1 2 3 4 5 6 7
| watch: { '$route' (to, from) { const toDepth = to.path.split('/').length const fromDepth = from.path.split('/').length this.transitionName = toDepth < fromDepth ? 'slide-right' : 'slide-left' } }
|
获取数据
导航后获取数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <template> <div class="post"> <div v-if="loading" class="loading"> Loading... </div>
<div v-if="error" class="error"> {{ error }} </div>
<div v-if="post" class="content"> <h2>{{ post.title }}</h2> <p>{{ post.body }}</p> </div> </div> </template>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| export default { data () { return { loading: false, post: null, error: null } }, created () { this.fetchData() }, watch: { '$route': 'fetchData' }, methods: { fetchData () { this.error = this.post = null this.loading = true getPost(this.$route.params.id, (err, post) => { this.loading = false if (err) { this.error = err.toString() } else { this.post = post } }) } } }
|
导航前获取数据
通过这种方式,我们在导航转入新的路由前获取数据。我们可以在接下来的组件的 beforeRouteEnter 守卫中获取数据,当数据获取成功后只调用 next 方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| export default { data () { return { post: null, error: null } }, beforeRouteEnter (to, from, next) { getPost(to.params.id, (err, post) => { next(vm => vm.setData(err, post)) }) }, beforeRouteUpdate (to, from, next) { this.post = null getPost(to.params.id, (err, post) => { this.setData(err, post) next() }) }, methods: { setData (err, post) { if (err) { this.error = err.toString() } else { this.post = post } } } }
|
Vuex
State
从 store 实例中读取状态最简单的方法就是在计算属性中返回某个状态:
1 2 3 4 5 6 7 8
| const Counter = { template: `<div>{{ count }}</div>`, computed: { count () { return store.state.count } } }
|
Vuex 通过 store 选项,提供了一种机制将状态从根组件“注入”到每一个子组件中(需调用 Vue.use(Vuex)):
1 2 3 4 5 6 7 8 9 10 11
| const app = new Vue({ el: '#app', store, components: { Counter }, template: ` <div class="app"> <counter></counter> </div> ` })
|
1 2 3 4 5 6 7 8
| const Counter = { template: `<div>{{ count }}</div>`, computed: { count () { return this.$store.state.count } } }
|
Getter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| computed: { doneTodosCount () { return this.$store.state.todos.filter(todo => todo.done).length } }
const store = new Vuex.Store({ state: { todos: [ { id: 1, text: '...', done: true }, { id: 2, text: '...', done: false } ] }, getters: { doneTodos: state => { return state.todos.filter(todo => todo.done) } } })
|
Mutation
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const store = new Vuex.Store({ state: { count: 1 }, mutations: { increment (state) { state.count++ } } })
store.commit('increment') store.commit('increment', 10)
|
Action
可以异步
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const store = new Vuex.Store({ state: { count: 0 }, mutations: { increment (state) { state.count++ } }, actions: { increment (context) { context.commit('increment') } } })
store.dispatch('increment')
|
Module
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| const moduleA = { state: { ... }, mutations: { ... }, actions: { ... }, getters: { ... } }
const moduleB = { state: { ... }, mutations: { ... }, actions: { ... } }
const store = new Vuex.Store({ modules: { a: moduleA, b: moduleB } })
store.state.a store.state.b
|
SSR
服务端渲染
安装
1
| npm install vue vue-server-renderer --save
|
渲染示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| const Vue = require('vue') const app = new Vue({ template: `<div>Hello World</div>` })
const renderer = require('vue-server-renderer').createRenderer()
renderer.renderToString(app, (err, html) => { if (err) throw err console.log(html) })
renderer.renderToString(app).then(html => { console.log(html) }).catch(err => { console.error(err) })
|
Vue Cli
文档
配置
配置文件
vue.config.js
文件:
1 2 3 4 5 6
| module.exports = { publicPath: process.env.NODE_ENV === 'production' ? '/production-sub-path/' : '/' }
|