[关闭]
@Bios 2018-12-10T08:38:06.000000Z 字数 20871 阅读 872

Vue 全家桶,深入Vue 的世界

Vue


Vue 实例上的属性

Vue实例

组件树

DOM访问

数据访问

  1. vm.$options.render = h => {
  2. return h('div',{},'new render function')
  3. }

DOM方法的使用

  1. // vue 的 渲染过程是异步的
  2. <template>
  3. <div id="app">
  4. <p>{{text}}</p>
  5. </div>
  6. </template>
  7. <script>
  8. export default {
  9. data() {
  10. return {
  11. text: 0
  12. };
  13. }
  14. mounted(){
  15. setInterval(()=> {
  16. this.text +=1;
  17. this.text +=1;
  18. this.text +=1;
  19. this.text +=1;
  20. this.text +=1;
  21. },1000)
  22. }
  23. }
  24. </script>

可以看到text值的变化是0 5 10 15 ... 而并没有出现 0 1 2 3 ... 这样连续的变化

event方法的使用

1.监听

  1. // 第一种写法
  2. watch: {
  3. text(new, old) {
  4. console.log(`${new}:${old}`);
  5. }
  6. }
  7. // 第二种写法
  8. const unWatch = this.$watch('text',(new,old)=>
  9. console.log(`${new}:${old}`);
  10. })
  11. // 2秒后销毁 unWatch
  12. setTimeout(()=> {
  13. unWatch();
  14. },2000)
  15. // 两种写法的结果一样,只是第二种需要在组件销毁手动销毁$watch

2.触发

3.删除

4.其他

  1. <template>
  2. <div id="app">
  3. <p>{{obj.a}}</p>
  4. </div>
  5. </template>
  6. <script>
  7. export default {
  8. data() {
  9. return {
  10. obj:{}
  11. };
  12. }
  13. mounted(){
  14. let i = 0;
  15. setInterval(()=> {
  16. i++;
  17. // 第一种
  18. this.obj.a = i ;
  19. // obj.a没有定义,vue是无法监听到这个属性的变化,所以页面的值也不会变化,这时可以用$forceUpdate进行强制渲染,当然不推荐这种用法
  20. this.$forceUpdate();
  21. // 第二种
  22. this.$set(this.obj,'a',i);
  23. },1000)
  24. }
  25. }
  26. </script>

Vue 生命周期

vue 官方生命周期
vue生命周期

  1. render (h) {
  2. throw new TypeError('render error')
  3. // console.log('render function invoked') // render 在beforeMount 和 mounted之间执行
  4. // return h('div', {}, this.text) // 虚拟DOM
  5. },
  6. renderError (h, err) {
  7. return h('div', {}, err.stack)
  8. },
  9. errorCaptured () {
  10. // 会向上冒泡,并且正式环境可以使用
  11. }

如果要修改data里面的值,最早只能放到create生命周期中

Vue 数据绑定

  1. <template>
  2. <div id="app">
  3. <p>{{isActive?'active':'notActive'}}</p>
  4. <p>{{arr.join(' ')}}</p>
  5. <p>{{Date.now()}}</p>
  6. <p v-html="html"></p>
  7. <div
  8. :class="{ active: isActive }"
  9. :style="[styles, styles2]"
  10. ></div>
  11. <div :class="[isActive? 'active':'']"></div>
  12. <ul>
  13. <li v-for="(item,index) in arr" :key="index">{{item}}</li>
  14. </ul>
  15. // 单个checkbox
  16. <input type="checkbox" v-model="a"> {{a}} <br/>
  17. // 多个checkbox
  18. 爱好:<input type="checkbox" v-model="b" value="游泳"> 游泳
  19. <input type="checkbox" v-model="b" value="游泳"> 爬山
  20. <input type="checkbox" v-model="b" value="游泳"> 睡觉
  21. 性别:<input type="radio" v-model="c" value="男">
  22. <input type="radio" v-model="c" value="女">
  23. // 只绑定一次
  24. <p v-once="a"></p>
  25. </div>
  26. </template>
  27. <script>
  28. export default {
  29. data() {
  30. return {
  31. isActive: false,
  32. arr: [1, 2, 3],
  33. html: '<span>123</span>',
  34. styles: {
  35. color: 'red',
  36. appearance: 'none'
  37. },
  38. styles2: {
  39. color: 'black'
  40. },
  41. a: false,
  42. b:[], // 可以拿到checkbox 的 value
  43. c:'' // 性别
  44. };
  45. }
  46. }
  47. </script>

v-model 的修饰符

来自官网的例子:

1..number
如果想自动将用户的输入值转为数值类型,可以给 v-model 添加 number 修饰符:

  1. <input v-model.number="age" type="number">

这通常很有用,因为即使在 type="number" 时,HTML 输入元素的值也总会返回字符串。

2..trim
如果要自动过滤用户输入的首尾空白字符,可以给 v-model 添加 trim 修饰符:

  1. <input v-model.trim="msg">

3..lazy
在默认情况下,v-model 在每次 input 事件触发后将输入框的值与数据进行同步 。你可以添加 lazy 修饰符,从而转变为使用 change 事件进行同步(当输入框失去焦点):

  1. <!-- 在“change”时而非“input”时更新 -->
  2. <input v-model.lazy="msg" >

数组和对象的注意事项

数组

由于 JavaScript 的限制,Vue 不能检测以下变动的数组:

  1. var vm = new Vue({
  2. data: {
  3. items: ['a', 'b', 'c']
  4. }
  5. })
  6. vm.items[1] = 'x' // 不是响应性的
  7. vm.items.length = 2 // 不是响应性的

为了解决第一类问题,以下两种方式都可以实现和 vm.items[indexOfItem] = newValue 相同的效果,同时也将触发状态更新:

  1. // Vue.set
  2. Vue.set(vm.items, indexOfItem, newValue)
  1. // Array.prototype.splice
  2. vm.items.splice(indexOfItem, 1, newValue)

你也可以使用 vm.$set 实例方法,该方法是全局方法 Vue.set 的一个别名:

  1. vm.$set(vm.items, indexOfItem, newValue)

为了解决第二类问题,你可以使用 splice:

  1. vm.items.splice(newLength)

对象

Vue 不能检测对象属性的添加或删除:

  1. var vm = new Vue({
  2. data: {
  3. a: 1
  4. }
  5. })
  6. // `vm.a` 现在是响应式的
  7. vm.b = 2
  8. // `vm.b` 不是响应式的

对于已经创建的实例,Vue 不能动态添加根级别的响应式属性。但是,可以使用 Vue.set(object, key, value) 方法向嵌套对象添加响应式属性。例如,对于:

  1. var vm = new Vue({
  2. data: {
  3. userProfile: {
  4. name: 'Anika'
  5. }
  6. }
  7. })

你可以添加一个新的 age 属性到嵌套的 userProfile 对象:

  1. Vue.set(vm.userProfile, 'age', 27)

你还可以使用 vm.$set 实例方法,它只是全局 Vue.set 的别名:

  1. vm.$set(vm.userProfile, 'age', 27)

有时你可能需要为已有对象赋予多个新属性,比如使用 Object.assign()_.extend()。在这种情况下,你应该用两个对象的属性创建一个新的对象。所以,如果你想添加新的响应式属性,不要像这样:

  1. Object.assign(vm.userProfile, {
  2. age: 27,
  3. favoriteColor: 'Vue Green'
  4. })

你应该这样做:

  1. vm.userProfile = Object.assign({}, vm.userProfile, {
  2. age: 27,
  3. favoriteColor: 'Vue Green'
  4. })

computed 计算属性

计算属性的使用

  1. <template>
  2. <div id="app">
  3. <p>{{name}}</p>
  4. </div>
  5. </template>
  6. <script>
  7. export default {
  8. data() {
  9. return {
  10. firstName: 'Fin',
  11. lastName: 'Get',
  12. };
  13. },
  14. computed: {
  15. name() {
  16. return `${this.firstName}${this.lastName}`
  17. }
  18. }
  19. }
  20. </script>

双向绑定的计算属性与Vuex

  1. // vuex state是无法直接修改的,官方给出了 v-model 的解决方案
  2. <input v-model="message">
  3. computed: {
  4. message: {
  5. get () {
  6. return this.$store.state.obj.message
  7. },
  8. set (value) {
  9. this.$store.commit('updateMessage', value)
  10. }
  11. }
  12. }

如果在方法或者生命周期中使用了计算属性,则必须设置一个set

watch 监听器

watch 简单使用

  1. <div id="demo">{{ fullName }}</div>
  2. var vm = new Vue({
  3. el: '#demo',
  4. data: {
  5. firstName: 'Foo',
  6. lastName: 'Bar',
  7. fullName: 'Foo Bar'
  8. },
  9. watch: { // watch 方法最初绑定的时候,它是不会执行的,只有变化了才会执行
  10. firstName: function (val) {
  11. this.fullName = val + ' ' + this.lastName
  12. },
  13. lastName: function (val) {
  14. this.fullName = this.firstName + ' ' + val
  15. }
  16. }
  17. })
  1. watch: {
  2. // 声明一个handler,这样在初始化时就会执行一次 handler
  3. firstName: {
  4. handler(val) {
  5. this.fullName = val + ' ' + this.lastName
  6. },
  7. immediate: true
  8. }
  9. }

监听对象属性的变化

  1. <div id="demo">{{ obj.a }}</div>
  2. <input v-model="obj.a" />
  3. var vm = new Vue({
  4. el: '#demo',
  5. data: {
  6. obj: {
  7. a: '123'
  8. }
  9. },
  10. watch: {
  11. obj: {
  12. handler() {
  13. console.log('obj.a changed');
  14. },
  15. immediate: true,
  16. deep: true // 如果不加这一句,在输入框中输入值,并不会打印 obj.a changed
  17. }
  18. }
  19. })
  1. // 这样写就能监听到属性值的变化
  2. watch: {
  3. 'obj.a': {
  4. handler() {
  5. console.log('obj.a changed');
  6. }
  7. }
  8. }

Vue 组件

Vue 组件中的data为什么必须是函数

官网解释

在Vue组件中data必须是函数,但是在 new Vue()中data可以是一个对象

  1. Vue.component('MyComponent', {
  2. template: '<div>this is a component</div>',
  3. data() {
  4. return {} // 返回一个唯一的对象,不要和其他组件共用一个对象进行返回
  5. },
  6. })

上面定义了一个MyComponent组件,在这里我们可以把这个组件看成一个构造函数。在其他页面引入,并注册组件时,实际上是对这个构造函数的一个引用。当在模板中正真使用组件时类似于实例化了一个组件对象。

  1. // 模拟一下
  2. let MyComponent = function() {
  3. // 定义一个构造函数
  4. }
  5. MyComponent.prototype.data = {
  6. name: 'component',
  7. age: 0
  8. }
  9. // 实例化组件对象
  10. let componentA = new MyComponent();
  11. let componentB = new MyComponent();
  12. componentA.data.name === componentB.data.name; // true
  13. componentA.data.age = 4;
  14. componentB.data.name;

可以看出,两个实例组件对象的data是一模一样的,一个改变也会导致另一个改变,这在实际开发中是不符合组件式思想的。

  1. // 模拟一下
  2. let MyComponent = function() {
  3. // 定义一个构造函数
  4. }
  5. // 这样就行了 写成函数,函数有自己的作用域,不会相互影响
  6. MyComponent.prototype.data = function() {
  7. return {
  8. name: 'component',
  9. age: 0
  10. }
  11. }

用 Vue.use() 定义全局组件

  1. // 定义一个 button 组件
  2. // button.vue
  3. <template>
  4. <div class="button">
  5. 按钮
  6. </div>
  7. </template>
  8. <script>
  9. </script>
  1. // button.js
  2. import ButtonComponent from './button.vue';
  3. const Button={
  4. install:function (Vue) {
  5. Vue.component('Button',ButtonComponent)
  6. }
  7. }
  8. export default Button;
  1. // main.js
  2. import Button from './component/button.js';
  3. Vue.use(Button);

完成上面的步骤就可以在全局使用button组件了,其实最重要的Vue.component('Button',ButtonComponent), Vue.use(Button)会执行install方法,也可以直接在main.js使用Vue.component()注册全局组件。

props

  1. <template>
  2. <div class="button">
  3. 按钮
  4. </div>
  5. </template>
  6. <script>
  7. export default {
  8. props: ['msg'], // 没有任何限制
  9. // 输入限制
  10. props: {
  11. // 基础的类型检查 (`null` 匹配任何类型)
  12. propA: Number,
  13. // 多个可能的类型
  14. propB: [String, Number],
  15. // 必填的字符串
  16. propC: {
  17. type: String,
  18. required: true
  19. },
  20. // 带有默认值的数字
  21. propD: {
  22. type: Number,
  23. default: 100
  24. },
  25. // 带有默认值的对象
  26. propE: {
  27. type: Object,
  28. // 对象或数组且一定会从一个工厂函数返回默认值
  29. default: function () {
  30. return { message: 'hello' }
  31. }
  32. },
  33. // 自定义验证函数
  34. propF: {
  35. validator: function (value) {
  36. // 这个值必须匹配下列字符串中的一个
  37. return ['success', 'warning', 'danger'].indexOf(value) !== -1
  38. }
  39. }
  40. }
  41. }
  42. </script>

子组件是不能直接修改props的。

Vue组件之间的通信问题可以看这里...

Vue 组件 extend

使用Vue.extend 就是构造了一个Vue构造函数的“子类”。它的参数是一个包含组件选项的对象,其中data选项必须是函数。

  1. import Vue from 'vue'
  2. // 一个包含组件选项的对象
  3. const compoent = {
  4. props: {
  5. active: Boolean,
  6. propOne: String
  7. },
  8. template: `
  9. <div>
  10. <input type="text" v-model="text">
  11. <span v-show="active">see me if active</span>
  12. </div>
  13. `,
  14. data () {
  15. return {
  16. text: 0
  17. }
  18. },
  19. mounted () { // 这个mounted先打印
  20. console.log('comp mounted');
  21. }
  22. }
  23. // 创建一个“子类”
  24. const CompVue = Vue.extend(compoent);
  25. // 实例化一个“子类”
  26. new CompVue({
  27. el: '#root',
  28. propsData: { // 这里如果用props,组件内是拿不到值的
  29. propOne: 'xxx'
  30. },
  31. data: {
  32. text: '123'
  33. },
  34. mounted () {
  35. console.log('instance mounted');
  36. }
  37. })
  1. const component2 = {
  2. extends: component, // 继承于 component
  3. data(){
  4. return {
  5. text: 1
  6. }
  7. },
  8. mounted () {
  9. this.$parent.text = '111111111'; // 可以改变父组件的值
  10. console.log('comp2 mounted')
  11. }
  12. }
  13. new Vue({
  14. name: 'Root',
  15. el: '#root',
  16. mounted () {
  17. console.log(this.$parent.$options.name)
  18. },
  19. components: {
  20. Comp: componet2
  21. },
  22. data: {
  23. text: 23333
  24. },
  25. template: `
  26. <div>
  27. <span>{{text}}</span>
  28. <comp></comp>
  29. </div>
  30. `
  31. })

Vue 组件高级属性

Vue 组件插槽

通常我们会向一个组件中传入一些自定义的内容,这个时候就可以用到插槽。插槽内可以包含任何模板代码,包括HTML或者是一个组件。

  1. // 定义一个带插槽的组件
  2. const component = {
  3. name: 'comp',
  4. template: `
  5. <div>
  6. <slot></slot>
  7. </div>
  8. `
  9. }
  10. new CompVue({
  11. el: '#root',
  12. components:{
  13. Comp
  14. },
  15. template: `
  16. <div>
  17. <comp>
  18. <p>这里的内容显示在插槽内</p>
  19. </comp>
  20. </div>
  21. `
  22. }

具名插槽

官网链接:https://cn.vuejs.org/v2/guide/components-slots.html

  1. <div class="container">
  2. <header>
  3. <!-- 我们希望把页头放这里 -->
  4. <slot name="header"></slot>
  5. </header>
  6. <main>
  7. <!-- 我们希望把主要内容放这里 -->
  8. <slot name="main"></slot>
  9. </main>
  10. <footer>
  11. <!-- 我们希望把页脚放这里 -->
  12. <slot name="footer"></slot>
  13. </footer>
  14. </div>

具名插槽的使用:

第一种:在一个父组件的 <template>元素上使用 slot 特性

  1. <base-layout>
  2. <template slot="header">
  3. <h1>Here might be a page title</h1>
  4. </template>
  5. <template slot="main">
  6. <p>A paragraph for the main content.</p>
  7. <p>And another one.</p>
  8. </template>
  9. <template slot="footer">
  10. <p>Here's some contact info</p>
  11. </template>
  12. </base-layout>

第二种:直接在普通元素上使用

  1. <base-layout>
  2. <h1 slot="header">Here might be a page title</h1>
  3. <div slot="main">
  4. <p>A paragraph for the main content.</p>
  5. <p>And another one.</p>
  6. </div>
  7. <p slot="footer">Here's some contact info</p>
  8. </base-layout>

插槽的默认内容

在插槽中可以设置一个默认内容,如果用户没有设置新的内容,则会显示默认内容

  1. <button>
  2. <slot>提交</slot>
  3. </button>

作用域插槽

2.1.0+ 新增 在 2.5.0+,slot-scope 不再限制在 <template> 元素上使用,而可以用在插槽内的任何元素或组件上。

  1. const component = {
  2. name: 'comp',
  3. template: `
  4. <div>
  5. <slot value="456" name="finget"></slot>
  6. </div>
  7. `
  8. }
  9. new CompVue({
  10. el: '#root',
  11. components:{
  12. Comp
  13. },
  14. template: `
  15. <div>
  16. <comp>
  17. <p slot-scope="props">{{props.value}} {{props.name}}</p> // 456 finget
  18. </comp>
  19. </div>
  20. `
  21. }

provide/inject 跨级组件交互

2.2.0 新增

这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。

  1. // 父级组件提供 'foo'
  2. var Provider = {
  3. provide: {
  4. foo: 'bar'
  5. },
  6. // ...
  7. }
  8. // 子组件注入 'foo'
  9. var Child = {
  10. inject: ['foo'],
  11. created () {
  12. console.log(this.foo) // => "bar"
  13. }
  14. // ...
  15. }

如果是注入一个父级组件内部的值,provide需要作为一个函数,类似于data

  1. const component = {
  2. name: 'comp',
  3. inject: ["value"]
  4. template: `
  5. <div>子组件 {{value}}</div>
  6. `
  7. }
  8. new CompVue({
  9. el: '#root',
  10. data() {
  11. return {
  12. value: '123'
  13. }
  14. }
  15. components:{
  16. Comp
  17. },
  18. provide() { // 这里如果只是一个对象的话是无法拿到this.value的
  19. return {
  20. value: this.value
  21. }
  22. },
  23. template: `
  24. <div>
  25. <comp></comp>
  26. <input type="text" v-model="value">
  27. </div>
  28. `
  29. }

如果要监听父级组件的属性值的变化,从而自动更新子组件的值,需要手动实现监听

  1. const component = {
  2. name: 'comp',
  3. inject: ["data"]
  4. template: `
  5. <div>子组件 {{data.value}}</div>
  6. `
  7. }
  8. ...
  9. provide() {
  10. const data = {}
  11. // 这是vue双向绑定的基础
  12. Object.defineProperty(data,"value",{
  13. get: () => this.value,
  14. enumerable: true
  15. })
  16. return {
  17. data
  18. }
  19. },
  20. ...

Vue 的render

Vue模板的解析:https://finget.github.io/2018/05/31/mvvm-vue/

Vue-router

router构建选项

重定向:

  1. {
  2. path: '/',
  3. redirect: '/app'
  4. }

History 模式:

  1. const router = new VueRouter({
  2. mode: 'history',
  3. routes: [...]
  4. })

vue-router 默认 hash 模式 —— 使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载。

不过这种模式要玩好,还需要后台配置支持。因为我们的应用是个单页客户端应用,如果后台没有正确的配置,当用户在浏览器直接访问 http://oursite.com/user/id 就会返回 404,这就不好看了。

给个警告页:

  1. const router = new VueRouter({
  2. mode: 'history',
  3. routes: [
  4. { path: '*', component: NotFoundComponent }
  5. ]
  6. })

base

  1. const router = new VueRouter({
  2. mode: 'history',
  3. base: '/base/',
  4. routes: [
  5. { path: '/hello', component: hello }
  6. ]
  7. })

当访问localhost:8080/hello会变成localhost:8080/base/hello,所有的路由路径都会加上/base,当然手动删除/base还是可以打开页面

linkActiveClass 和 linkExactActiveClass

  1. <router-link to="/app">app</router-link>
  2. <router-link to="/login">login</router-link>

router-link在页面中会渲染成a标签,点击之后会添加两个类名:router-link-exact-activerouter-link-active

  1. const router = new VueRouter({
  2. linkActiveClass: 'active-link',
  3. linkExactActiveClass: 'exact-active-link'
  4. })

这相当于是重新命名了两个类名。

两者的不同点:

  1. <router-link to="/login">login</router-link>
  2. <router-link to="/login/exact">login exact</router-link>

上面这两个路由有一部分/login是相同的,在点击了login exact路由调转到/login/exact后:

/login 上还保留了router-link-active类名

scrollBehavior

使用前端路由,当切换到新路由时,想要页面滚到顶部,或者是保持原先的滚动位置,就像重新加载页面那样。

注意: 这个功能只在支持 history.pushState 的浏览器中可用。

  1. const router = new VueRouter({
  2. scrollBehavior(to, form, savedPosition){
  3. if (savedPosition) {
  4. return savedPosition
  5. } else {
  6. return { x: 0, y: 0 }
  7. }
  8. },
  9. routes: [...]
  10. })

scrollBehavior 方法接收 tofrom 路由对象。第三个参数 savedPosition 当且仅当 popstate 导航 (通过浏览器的 前进/后退 按钮触发) 时才可用。

parseQuery 和 stringifyQuery

提供自定义查询字符串的解析/反解析函数。覆盖默认行为。

  1. const router = new VueRouter({
  2. parseQuery (query) {
  3. console.log(query)
  4. },
  5. stringifyQuery (obj) {
  6. console.log(obj)
  7. }
  8. })

fallback

当浏览器不支持 history.pushState 控制路由是否应该回退到 hash 模式。默认值为 true。

在 IE9 中,设置为 false 会使得每个 router-link 导航都触发整页刷新。它可用于工作在 IE9 下的服务端渲染应用,因为一个 hash 模式的 URL 并不支持服务端渲染。

  1. const router = new VueRouter({
  2. fallback: true
  3. })

路由元信息

官网例子:

  1. const router = new VueRouter({
  2. routes: [
  3. {
  4. path: '/foo',
  5. component: Foo,
  6. children: [
  7. {
  8. path: 'bar',
  9. component: Bar,
  10. // a meta field
  11. meta: { requiresAuth: true }
  12. }
  13. ]
  14. }
  15. ]
  16. })

那么如何访问这个 meta 字段呢?

首先,我们称呼 routes 配置中的每个路由对象为 路由记录。路由记录可以是嵌套的,因此,当一个路由匹配成功后,他可能匹配多个路由记录

例如,根据上面的路由配置,/foo/bar 这个 URL 将会匹配父路由记录以及子路由记录。

一个路由匹配到的所有路由记录会暴露为 $route 对象 (还有在导航守卫中的路由对象) 的 $route.matched 数组。因此,我们需要遍历 $route.matched 来检查路由记录中的 meta 字段。

下面例子展示在全局导航守卫中检查元字段:

  1. router.beforeEach((to, from, next) => {
  2. if (to.matched.some(record => record.meta.requiresAuth)) {
  3. // this route requires auth, check if logged in
  4. // if not, redirect to login page.
  5. if (!auth.loggedIn()) {
  6. next({
  7. path: '/login',
  8. query: { redirect: to.fullPath }
  9. })
  10. } else {
  11. next()
  12. }
  13. } else {
  14. next() // 确保一定要调用 next()
  15. }
  16. })

命名视图

在一个路由下展示多个视图组件,用的并不多

  1. // 在这个页面中要分别展示三个视图
  2. <router-view></router-view> // 默认的
  3. <router-view name="a"></router-view> // 视图a
  4. <router-view name="b"></router-view> // 视图b
  1. const router = new VueRouter({
  2. routes: [
  3. {
  4. path: '/',
  5. components: { // 加s
  6. default: Foo, // 对应默认router-view
  7. a: Bar, // name = "a"
  8. b: Baz // name = "b"
  9. }
  10. }
  11. ]
  12. })

导航守卫

路由改变时,按顺序触发的钩子函数

全局守卫

  1. const router = new VueRouter({ ... })
  2. router.beforeEach((to, from, next) => {
  3. console.log('before each invoked');
  4. next();
  5. })
  6. router.beforeResolve((to, from, next) => {
  7. console.log('before resolve invoked');
  8. next();
  9. })

每个守卫方法接收三个参数:

确保要调用 next 方法,否则钩子就不会被 resolved

路由对象

一个路由对象 (route object) 表示当前激活的路由的状态信息,包含了当前 URL 解析得到的信息,还有 URL 匹配到的路由记录 (route records)。

路由对象是不可变 (immutable) 的,每次成功的导航后都会产生一个新的对象。

路由对象属性:

  1. const router = new VueRouter({
  2. routes: [
  3. // 下面的对象就是路由记录
  4. { path: '/foo', component: Foo,
  5. children: [
  6. // 这也是个路由记录
  7. { path: 'bar', component: Bar }
  8. ]
  9. }
  10. ]
  11. })

当 URL 为 /foo/bar,$route.matched 将会是一个包含从上到下的所有对象 (副本)。

全局后置钩子

  1. router.afterEach((to, from) => {
  2. console.log('after each invoked');
  3. })

路由独享的守卫

  1. const router = new VueRouter({
  2. routes: [
  3. {
  4. path: '/foo',
  5. component: Foo,
  6. beforeEnter: (to, from, next) => {
  7. // ...
  8. }
  9. }
  10. ]
  11. })

组件内的守卫

  1. const Foo = {
  2. template: `...`,
  3. beforeRouteEnter (to, from, next) {
  4. // 在渲染该组件的对应路由被 confirm 前调用
  5. // 不!能!获取组件实例 `this`
  6. // 因为当守卫执行前,组件实例还没被创建
  7. },
  8. beforeRouteUpdate (to, from, next) {
  9. // 在当前路由改变,但是该组件被复用时调用
  10. // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
  11. // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
  12. // 可以访问组件实例 `this`
  13. },
  14. beforeRouteLeave (to, from, next) {
  15. // 导航离开该组件的对应路由时调用
  16. // 可以访问组件实例 `this`
  17. }
  18. }

beforeRouteEnter 守卫 不能 访问 this,因为守卫在导航确认前被调用,因此即将登场的新组件还没被创建。

不过,你可以通过传一个回调给 next来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数。

  1. beforeRouteEnter (to, from, next) {
  2. next(vm => {
  3. // 通过 `vm` 访问组件实例
  4. })
  5. }

完整的导航解析流程

  1. 导航被触发。
  2. 在失活的组件里调用离开守卫。
  3. 调用全局的 beforeEach 守卫。
  4. 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
  5. 在路由配置里调用 beforeEnter
  6. 解析异步路由组件。
  7. 在被激活的组件里调用 beforeRouteEnter
  8. 调用全局的 beforeResolve 守卫 (2.5+)。
  9. 导航被确认。
  10. 调用全局的 afterEach 钩子。
  11. 触发 DOM 更新。
  12. 用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数。

异步路由

在路由文件中,直接import所有组件势必造成页面首次渲染时间变长,异步路由,当进入对应的路由才加载对应的页面。

  1. const router = new VueRouter({
  2. routes: [
  3. { path: '/foo',
  4. component: () => import('../view/...'),
  5. }
  6. ]
  7. })

这种写法需要安装syntax-dynamic-import,并在.babelrc进行配置

  1. // .babelrc
  2. {
  3. "plugins": ["syntax-dynamic-import"]
  4. }

Vux

以下内容来自官网:https://vuex.vuejs.org/zh/

简单使用vuex

  1. // store.js
  2. import Vuex from 'vuex'
  3. import Vue from 'vue'
  4. Vue.use(Vuex)
  5. const store = new Vuex.Store({
  6. state: {
  7. count: 0
  8. },
  9. mutations: {
  10. updateCount(state, num) {
  11. state.count = num
  12. }
  13. }
  14. })
  15. export default store
  1. // main.js
  2. import Vue from 'vue'
  3. import App from './App'
  4. import store from './store/store.js'
  5. Vue.config.productionTip = false
  6. /* eslint-disable no-new */
  7. new Vue({
  8. el: '#app',
  9. store, // 挂载
  10. components: { App },
  11. template: '<App/>'
  12. })
  1. // 任意组件
  2. mounted(){
  3. console.log(this.$store)
  4. let i = 1
  5. setInterval(() => {
  6. this.$store.commit('updateCount', i++)
  7. })
  8. },
  9. computed: {
  10. count() {
  11. return this.$store.state.count
  12. }
  13. }

核心概念

State

Vuex 使用单一状态树——是的,用一个对象就包含了全部的应用层级状态。至此它便作为一个“唯一数据源 (SSOT)”而存在。这也意味着,每个应用将仅仅包含一个 store 实例。单一状态树让我们能够直接地定位任一特定的状态片段,在调试的过程中也能轻易地取得整个当前应用状态的快照。

大白话: state就相当于是个全局对象,通过Vue.use(Vuex)全局注册了vuex之后,在任意组件中可以用this.$store.state拿到该对象

Vuex的状态存储是响应式的,从store实例中读取状态最简单的方法就是在计算属性中返回某个状态。

  1. computed: {
  2. count() {
  3. return this.$store.state.count
  4. }
  5. }

state中的count变化时,自动会更新computed,从而改变相关DOM

mapState 辅助函数

当一个组件需要获取多个状态时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用mapState辅助函数帮助我们生成计算属性,让你少按几次键:

  1. // 在单独构建的版本中辅助函数为 Vuex.mapState
  2. import { mapState } from 'vuex'
  3. export default {
  4. // ...
  5. computed: mapState({
  6. // 箭头函数可使代码更简练
  7. count: state => state.count,
  8. // 传字符串参数 'count' 等同于 `state => state.count`
  9. countAlias: 'count',
  10. // 为了能够使用 `this` 获取局部状态,必须使用常规函数 不能用箭头函数
  11. countPlusLocalState (state) {
  12. return state.count + this.localCount
  13. }
  14. })
  15. }

当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 mapState 传一个字符串数组。

  1. computed: mapState([
  2. // 映射 this.count 为 store.state.count
  3. 'count'
  4. ])
  5. // 常用操作
  6. computed: {
  7. ...mapState(['count'])
  8. }
  9. // 换一个变量名
  10. computed: {
  11. ...mapState({
  12. count1 : 'count',
  13. count2 : state => state.count
  14. })
  15. }

Getter

Getter就是vuex种state的computed,通过state派生出新的state,而且它会被缓存起来,只有依赖的state发生变化才会重新计算

  1. export default {
  2. fullName(state) { // 默认接收state作为第一个参数
  3. return `${state.firstName}${state.lastName}`
  4. }
  5. }
mapGetters 辅助函数

getter的使用和state类似,可以把它看成state来用。

  1. import { mapGetters } from 'vuex'
  2. export default {
  3. // ...
  4. computed: {
  5. // 使用对象展开运算符将 getter 混入 computed 对象中
  6. ...mapGetters([
  7. 'doneTodosCount',
  8. 'anotherGetter',
  9. // ...
  10. ])
  11. }
  12. }

如果想给getter换个名字,方法和state一样,不重复

Mutation

Mutation必须是同步的

更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数:

  1. const store = new Vuex.Store({
  2. state: {
  3. count: 1
  4. },
  5. mutations: {
  6. increment (state) {
  7. // 变更状态
  8. state.count++
  9. }
  10. }
  11. })

你不能直接调用一个 mutation handler。这个选项更像是事件注册:“当触发一个类型为 increment 的 mutation 时,调用此函数。”要唤醒一个 mutation handler,你需要以相应的 type 调用 store.commit 方法:

  1. store.commit('increment')
提交载荷(传参)

你可以向 store.commit 传入额外的参数,即 mutation 的 载荷(payload):

  1. // ...
  2. mutations: {
  3. increment (state, n) {
  4. state.count += n
  5. }
  6. }
  7. store.commit('increment', 10)

在大多数情况下,载荷应该是一个对象,这样可以包含多个字段并且记录的 mutation 会更易读:

  1. // ...
  2. mutations: {
  3. increment (state, payload) {
  4. state.count += payload.amount
  5. }
  6. }
  7. store.commit('increment', {
  8. amount: 10
  9. })
对象风格的提交方式

提交 mutation 的另一种方式是直接使用包含 type 属性的对象:

  1. store.commit({
  2. type: 'increment',
  3. amount: 10
  4. })

当使用对象风格的提交方式,整个对象都作为载荷传给 mutation 函数,因此 handler 保持不变:

  1. mutations: {
  2. increment (state, payload) {
  3. state.count += payload.amount
  4. }
  5. }
使用常量替代 Mutation 事件类型

使用常量替代mutation 事件类型在各种 Flux 实现中是很常见的模式。这样可以使 linter 之类的工具发挥作用,同时把这些常量放在单独的文件中可以让你的代码合作者对整个 app 包含的 mutation 一目了然:

  1. // mutation-types.js
  2. export const SOME_MUTATION = 'SOME_MUTATION'
  3. // store.js
  4. import Vuex from 'vuex'
  5. import { SOME_MUTATION } from './mutation-types'
  6. const store = new Vuex.Store({
  7. state: { ... },
  8. mutations: {
  9. // 我们可以使用 ES2015 风格的计算属性命名功能来使用一个常量作为函数名
  10. [SOME_MUTATION] (state) {
  11. // mutate state
  12. }
  13. }
  14. })
在组件中提交 Mutation

你可以在组件中使用 this.$store.commit('xxx') 提交 mutation,或者使用 mapMutations 辅助函数将组件中的 methods 映射为 store.commit 调用(需要在根节点注入 store)。

  1. import { mapMutations } from 'vuex'
  2. export default {
  3. // ...
  4. methods: {
  5. ...mapMutations([
  6. 'increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')`
  7. // `mapMutations` 也支持载荷:
  8. 'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.commit('incrementBy', amount)`
  9. ]),
  10. ...mapMutations({
  11. add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment')`
  12. })
  13. }
  14. }

Action

Action 可以包含异步操作

Action跟Mutation类似,Action是调用commit方法,提交mutation的。

  1. const store = new Vuex.Store({
  2. state: {
  3. count: 0
  4. },
  5. mutations: {
  6. increment (state) {
  7. state.count++
  8. }
  9. },
  10. actions: {
  11. increment (context) {
  12. context.commit('increment')
  13. }
  14. }
  15. })

Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.statecontext.getters 来获取 stategetters

实践中,我们会经常用到 ES2015 的 参数解构 来简化代码(特别是我们需要调用 commit 很多次的时候):

  1. actions: {
  2. // {commit} = context 解构出来
  3. increment ({ commit }) {
  4. commit('increment')
  5. }
  6. }

实际代码:

在组件中分发 Action

你在组件中使用 this.$store.dispatch('xxx') 分发 action,或者使用 mapActions 辅助函数将组件的 methods 映射为 store.dispatch 调用(需要先在根节点注入 store):

  1. import { mapActions } from 'vuex'
  2. export default {
  3. // ...
  4. methods: {
  5. ...mapActions([
  6. 'increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`
  7. // `mapActions` 也支持载荷:
  8. 'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
  9. ]),
  10. ...mapActions({
  11. add: 'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
  12. })
  13. }
  14. }

严格模式

开启严格模式,仅需在创建 store 的时候传入 strict: true:

  1. const store = new Vuex.Store({
  2. // ...
  3. strict: true
  4. })

在严格模式下,无论何时发生了状态变更且不是由 mutation 函数引起的,将会抛出错误。这能保证所有的状态变更都能被调试工具跟踪到。

开发环境与发布环境

不要在发布环境下启用严格模式!严格模式会深度监测状态树来检测不合规的状态变更——请确保在发布环境下关闭严格模式,以避免性能损失。

类似于插件,我们可以让构建工具来处理这种情况:

  1. const store = new Vuex.Store({
  2. // ...
  3. strict: process.env.NODE_ENV !== 'production'
  4. })

实际问题

axios中拿不到this

  1. this.$axios.get('https://www.easy-mock.com/mock/5b616f2e0f34b755cbc58bb4/demo').then(function(res){
  2. console.log(this);
  3. })

闭包问题,要使用箭头函数

注册自定义指令

  1. // 全局注册自定义指令
  2. Vue.directive('focus', {
  3. // 当被绑定的元素插入到 DOM 中时……
  4. inserted: function (el) {
  5. // 聚焦元素
  6. console.log(el)
  7. el.focus()
  8. }
  9. })

全局注册(directive),局部注册(directives)

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注