2

一、Vuex面试核心知识点

1、你了解Vuex吗,说说它主要为了解决什么出现的呢?

Vuex是vue的状态管理器,它采用集中式的存储管理方式管理组件的所有状态。并以一定的规则来保证状态以一种可预测的方向发生变化~~

Vuex产生的缘由

  • 单向数据流
new Vue({
  // state
  data () {
    return {
      count: 0
    }
  },
  // view
  template: `
    <div>{{ count }}</div>
  `,
  // actions
  methods: {
    increment () {
      this.count++
    }
  }
})

上面这个简单的实例:包含了state、view视图、actions操作。

clipboard.png

三者以一种单向数据流的形式在运行着。但是如果我们碰到了多组件需要共享状态的时候,单向数据流就不大行了~~

  • 多组件依赖同一个数据
  • 不同组件可以修改同一个状态

遇到了种种问题,我们会有疑问,为什么不使用一种全局单例模式对我们的状态进行一个全局的统一管理呢,这样我们的组件树就成为了一个大的视图,不管我们在树的任何位置,我们都可以对状态进行获取和action操作。这样将使得我们的项目能够更好的维护于发展。

这是vuex的内部核心思想,它还借鉴了redux、flux等。vue高度契合vue的细粒度的数据响应机制,来实现高效的状态管理。

clipboard.png

2、一个简单Vuex实例:

import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    count: 1 // 计数
  },
    getters: {
      getCount(state) {
        return state.count > 120 ? 120 : state.count;
      }
    },
  mutations: {
    add(state, num) {
      return state.count = state.count + num;
    },
      de(state, payload) {
          state.count-=payload.n;
      }
  },
  actions: {
    add(context) {
        setTimeout( ()=>{
            context.commit('add', 100 )
        }, 1000)
    }
  }
});
<template>
  <div id="app">
      <span>{{$store.state.count}}</span>
      <button @click="add">添加</button>
      <button @click="de">减少</button>
  </div>
</template>

<script>
    export default {
        name: "APP",
        methods: {
            add() {
                // this.$store.commit("add", 10)
                this.$store.dispatch('add');
            },
            de() {
                this.$store.commit("de", {
                    n: 4
                })
            }
        }
    };
</script>

上面实现了一个简单的例子;

3、vuex有哪几种属性?

答:有五种,分别是 StateGetterMutationActionModule;

4、vuex的state的特性是什么?

state是整个Vuex的核心,这里我们使用的是一个单一状态树,也就是唯一数据源,这里跟module并不冲突。一个组件树共享一个store。

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

computed: {
  count() {
      return this.$store.state.count
  }
},

计算属性是依赖响应,基于依赖进行计算,当它的依赖发生了变化的时候,才会重新计算我们的返回值。

因为我们的Vuex会被所有组件去使用,如果我们频繁的导入显得不是很合理,Vue为我们提供了机制可以从根组件注入到每一个自组件中。

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount("#app");

同时vuex为我们提供了语法糖,方便我们去批量获取状态。

import { mapState } from 'vuex'

export default {
  // ...
  computed: mapState({
    // 箭头函数可使代码更简练
    count: state => state.count,

    // 传字符串参数 'count' 等同于 `state => state.count`
    countAlias: 'count',

    // 为了能够使用 `this` 获取局部状态,必须使用常规函数
    countPlusLocalState (state) {
      return state.count + this.localCount
    }
  })
}

我们还可以更简洁,但是要求我们的属性和store中的一致

computed: mapState([
  // 映射 this.count 为 store.state.count
  'count'
])

但是如果我们还有其他的计算属性呢,我们可以使用...扩展运算符混入。

computed: {
  localComputed () { /* ... */ },
  // 使用对象展开运算符将此对象混入到外部对象中
  ...mapState({
    // ...
  })
}
state:唯一性、响应式、获取用计算属性computed。 语法糖:mapState。

5、使用Getter?

getter跟计算属性有点类似,它主要解决什么问题呢?

当我们想要对某一个状态列表筛选的时候,我们可以在computed中进行筛选,但是如果我们多个组件都使用呢,我们是不是得每个组件都复制一份呢,显然不合理了,因此vuex允许我们在vuex内去抽离一个筛选或者处理的公共方法。这样我们就不必要每次都重写一遍了。

getters: {
    doneTodos: state => {
      return state.todos.filter(todo => todo.done)
    }
}

getters: {
  getCount(state) {
    return state.count > 120 ? 120 : state.count;
  }
},

Getter 也可以接受其他 getter 作为第二个参数:

getters: {
  // ...
  doneTodosCount: (state, getters) => {
    return getters.doneTodos.length
  }
}

使用:

computed: {
  doneTodosCount () {
    return this.$store.getters.doneTodosCount
  }
}

同样vuex也提供了语法糖:

import { mapGetters } from 'vuex'

export default {
  // ...
  computed: {
  // 使用对象展开运算符将 getter 混入 computed 对象中
    ...mapGetters([
      'doneTodosCount',
      'anotherGetter',
      // ...
    ])
  }
}

同时我们还可以给getter去传递参数

getters: {
  // ...
  getTodoById: (state) => (id) => {
    return state.todos.find(todo => todo.id === id)
  }
}

注意,getter 在通过方法访问时,每次都会去进行调用,而不会缓存结果。

本人理解getter就是对状态的一个预定义处理和筛选,复用性,可预测性好。

6、你理解mutation吗?

vuex规定了我们状态的唯一改变方式就是通过提交mutation来更改。每一个mutation都有一个type类型表示我们操作的类型,还有一个handler用来标明我们的具体操作过程。

最简单的形式就是:

  mutations: {
    add(state, num) {
      return state.count = state.count + num;
    },
      de(state, payload) {
          state.count-=payload.n;
      }
  },

// 调用执行
this.$store.commit("add", 10)
this.$store.commit("de", {
                    n: 4
                })

我们还可以使用对象风格的方式提交:

this.$store.commit({
    type: 'add',
    n: 4
})
// 这种风格会把整个对象作为载荷传递过去。
注意点:
当需要在对象上添加新属性时,你应该

1、使用 Vue.set(obj, 'newProp', 123), 或者

2、以新对象替换老对象。state.obj = { ...state.obj, newProp: 123 }

但是通常我们会使用一个字符串常量来代替我们的mutation事件类型。

const store = new Vuex.Store({
  state: { ... },
  mutations: {
    // 我们可以使用 ES2015 风格的计算属性命名功能来使用一个常量作为函数名
    [SOME_MUTATION] (state) {
      // mutate state
    }
  }
})
还需要注意的是,mutation的操作必须是同步的,异步执行会单独放在action中执行。原因其实是异步操作我们不知道如何执行完,如果我们执行了两个mutation,我们不知道哪个先执行完,所以对于我们自己也不友好,对于开发工具也不好跟踪。
  • 语法糖
import { mapMutations } from 'vuex'

export default {
  // ...
  methods: {
    ...mapMutations([
      'increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')`

      // `mapMutations` 也支持载荷:
      'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.commit('incrementBy', amount)`
    ]),
    ...mapMutations({
      add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment')`
    })
  }
}

有了语法糖就不要我们每次都显示性的写commit了。

总之:mutation就是对应我们用户的一些行为,mutation是唯一修改store中状态的办法。我们需要commit来同步提交。mutation 都是同步事务~~

7、对于异步获取的状态值,我们应该怎么处理呢?

Action跟我们的mutation类似,但是有两个特点:

1、内部可以书写异步代码
2、内部操作的不是state,而是通过一个与 store 实例具有相同方法和属性的 context 对象。通常是commit 提交 mutation。
注意这里的context不是store,而是一个类似的对象。
actions: {
  incrementAsync ({ commit }) {
    setTimeout(() => {
      commit('increment')
    }, 1000)
  }
}

// 通过dispathc来触发

store.dispatch('increment')

当然它也有语法糖:

import { mapActions } from 'vuex'

export default {
  // ...
  methods: {
    ...mapActions([
      'increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`

      // `mapActions` 也支持载荷:
      'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
    ]),
    ...mapActions({
      add: 'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
    })
  }
}
const actions = {
    asyncInCrement({ commit }, n){
        return new Promise(resolve => {
            setTimeout(() => {
                commit(types.TEST_INCREMENT, n);
                resolve();
            },3000)
        })
    }
}

这种形式我们可以在mutation提交之后通过.then拿到通知,去做一些我们要做的事情。

8、module

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:
const moduleA = {
  state: { ... },
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}

const moduleB = {
  state: { ... },
  mutations: { ... },
  actions: { ... }
}

const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
  }
})

store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态

二、Vuex面试题目分析

1、vuex的state的特性

1、state是vuex的核心,我们常称为是仓库store,或者是组件状态树。
2、它是唯一数据源。我们的全局公共状态都防止在这个state对象中了。
3、state是响应式的。通常我们会在组件的computed中引入我们想要使用的state或者getters

2、vuex的getters特性?

1、getters特性我们可以理解为它就是state中的计算属性,通常用于对我们的state的筛选和处理
2、我们通常习惯在组件中使用state的时候就使用我们自定义的getters
3、getters不是一定要使用,它为了防止我们在组件中写冗余的state处理计算属性。

3、vuex的mutation属性的要点有哪些?

1、mutation是唯一修改state的方法,且必须是同步事物

4、Action的注意点?

1、Action跟mutation类似,但是Action中允许你写异步代码
2、Action中处理的不是state,而是提交mutation

5、Vue.js中ajax请求代码应该写在组件的methods中还是vuex的actions中?

1、如果请求来的数据是不是要被其他组件公用,仅仅在请求的组件内使用,就不需要放入vuex 的state里。
2、如果被其他地方复用,这个很大几率上是需要的,如果需要,请将请求放入action里,方便复用,并包装成promise返回,


Meils
1.6k 声望157 粉丝

前端开发实践者