Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vuex那些事儿 #106

Open
FrankKai opened this issue Oct 5, 2018 · 6 comments
Open

Vuex那些事儿 #106

FrankKai opened this issue Oct 5, 2018 · 6 comments

Comments

@FrankKai
Copy link
Owner

FrankKai commented Oct 5, 2018

用vuex也有一段时间了,但仅仅是很基础的用,没有学习高级特性,更没有深入过源码
由于手上负责的聊天项目较为复杂,深入学习vuex更显得很有必要,从而优化聊天部分的前端实现

基于以上目的,我将在这里记录自己在实践过程中的一些总结。

  • mapState与mapGetter的区别是什么?
  • Vuex的plugins有什么用?
  • this.$store.commit()和mapMutation()哪种更好?
  • mutation与action区别是什么?
  • Modules的namespacing是什么操作?
  • 一次vuex的状态变更经历哪些步骤?
  • Vuex是如何与Vue框架做双向数据绑定的?
@FrankKai FrankKai changed the title vuex vuex那些事儿 Oct 5, 2018
@FrankKai
Copy link
Owner Author

FrankKai commented Oct 5, 2018

mapState与mapGetter的区别是什么?

我们都知道,这是我们从vuex查询数据的两种方式,那这两者到底有什么区别呢?
直接看二者的定义:

// https://vuex.vuejs.org/api/#mapstate
mapState(namespace?: string, map: Array<string> | Object<string | function>): Object
// https://vuex.vuejs.org/api/#mapgetters
mapGetters(namespace?: string, map: Array<string> | Object<string>): Object

如果看定义看不懂的话,可以移步TypeScript入门,或者到TypeScript官网自己学习,了解TypeScript中的Optional Property和strictNullChecks。
二者的第一个参数都是可选的命名空间,第二个是数组或者对象的map。

  • mapState一方面获取全局状态,一方面辅助我们生成计算属性。
  • mapGetters仅仅是获取全局状态,通过将store中的getter映射过来。

mapState:

mapState(['bar','baz']) // 相当于state.bar
mapState({
    foo: 'foo',
    bar: state => state.bar,
    baz(state) {
        return state.baz + 'hhh'; // 用做计算
    }
})
mapState('foo',['bar','baz']) // 这种形式不常用

mapGetters:

mapGetters(['bar', 'baz']) // 直接映射
mapGetters({
    barBuddy: 'bar',  // 重命名映射
    bazBuddy: 'baz',
})

其实mapState和mapGetters的真正区别在于:

  • mapState在组件层,整合组件资源,进行个性化操作,也就是某个.vue。适用于获取纯状态树上的原始数据,可能在每个页面都要再进行计算。
  • mapGetters在store层,整合store层资源,进行个性化操作,也就是getters.js。适于获取状态树上的处理后的数据,不需要在每个页面进行计算,store层直接算好了。

@FrankKai
Copy link
Owner Author

FrankKai commented Oct 5, 2018

Vuex的plugins有什么用?

暴露每个mutation的hooks,只接收一个store(初始化好的store)为参数。

  • Plugin内部提交mutation
  • 获取状态快照
  • Built-in Logger插件

最简单的开发和注入:

const myPlugin = store => {
  store.subscribe((mutation, state) => { })
}
const store = new Vuex.Store({
  plugins: [myPlugin]
})

又见subscribe,这仍然是基于事件的"发布订阅者"编程模型,关于发布订阅者模型,可以参考webhook到底是个啥?

Plugin内部提交mutation

Plugins不能直接修改状态--这和我们的组件一样,可以通过commit mutation触发更改。
通过commit mutation,插件可以同步数据到store。例如sync一个websocket数据源到store,这个contrived example里, 函数里可以执行很多更加复杂的任务:

export default function createWebSocketPlugin (socket) {
  return store => {
    socket.on('data', data => {
      store.commit('receiveData', data)
    })
    store.subscribe(mutation => {
      if (mutation.type === 'UPDATE_DATA') {
        socket.emit('update', mutation.payload)
      }
    })
  }
}
const plugin = createWebSocketPlugin(socket)

const store = new Vuex.Store({
  state,
  mutations,
  plugins: [plugin]
})

获取状态快照

state的"snapshots"指的是,对mutation的状态做保留,例如当前的post-mutation state和pre-mutation state做对比。可以通过深拷贝实现:

const myPluginWithSnapshot = store => {
  let prevState = _.cloneDeep(store.state)
  store.subscribe((mutation, state) => {
    let nextState = _.cloneDeep(state)

    // compare `prevState` and `nextState`...

    // save state for next mutation
    prevState = nextState
  })
}

snapshot适合在development阶段使用。当结合webpack或者Browserify,我们可以让构建工具处理:

const store = new Vuex.Store({
    plugins: process.env.NODE_ENV !=='production' ? [myPluginWithSnapshot] : []
})

此处的环境变量的获取,需要用到webpack的DefinePlugin 这个webpack内置插件。关于DefinePlugin,可以参考Webpack Plugin那些事儿

Built-in Logger插件

用于在控制台输出store的mutation信息,其实也是一种state snapshot。
信息内容包括:

  • mutation信息
  • mutation 前的state
  • mutation 后的state

相比vue-devtools,有以下优势:

  • 可以直接在console查看
  • 可以查看特定的mutation,在filter中
  • 可以输出特定的state,在transformer函数中
  • 可以选择性输出matation的type,payload,在mutationTransformer中

/store/logger.js

import createLogger from 'vuex/dist/logger';

const subscribedMutations = ['USER_INIT', 'DEVICE_EXPEND_STATUS'];

const logger = createLogger({
  collapsed: false,
  filter: (mutation) => subscribedMutations.includes(mutation.type),
  transformer: (state) => state,
  mutationTransformer: (mutation) => mutation, 
  logger: console,
});
export default logger;

/store/index.js

import logger from './logger';
export default new Vuex.Store({
    plugins: [logger],
})

日志输出就像下面这样:
image

注意:logger plugin仅仅用于development环境,因为它也本身属于state snapshot。

@FrankKai FrankKai changed the title vuex那些事儿 Vuex那些事儿 Oct 5, 2018
@FrankKai
Copy link
Owner Author

FrankKai commented Oct 6, 2018

this.$store.commit()和mapMutation()哪种更好?

当然是mapMutation,可以统一查看当前组件有多少mutation类型,但是需要注意命名不能和组件方法冲突。

import { mapMutations } from 'vuex'

export default {
  // ...
  methods: {
    ...mapMutations([
      'increment', // 不带payload映射,map `this.increment()` to `this.$store.commit('increment')`
      'incrementBy' // 带payload映射,map `this.incrementBy(amount)` to `this.$store.commit('incrementBy', amount)`
    ]),
    ...mapMutations({
      add: 'increment' // 重命名: map `this.add()` to `this.$store.commit('increment')`
    })
  }
}

直接使用this.$store.commit():

methods: {
   foo() {
       this.$store.commit('USER_INIT'); 
   }
   ...
   ...
   bar() {
         this.$store.commit('USER_UPDATE'); 
    }
}

当业务逐渐变复杂时,foo和bar之间会相隔很远,很难一目了然看清当前组件用到了哪些mutation。
使用mapMutatinos:

methods: {
   ...mapMutations([
       'USER_INIT',
       'USER_UPDATE',
   ]),
   foo() {
        this['USER_INIT']();
   }
   ...
   bar() {
        this['USER_UPDATE']();
   }
}

很明显,这样很清晰。

@FrankKai
Copy link
Owner Author

FrankKai commented Dec 26, 2018

mutation与action区别是什么?

  • mutation必须同步进行,action可以异步进行。
  • action可以调用异步api,可以分发多重mutation。
  • action可以返回promise,保证异步请求的顺序,一个action执行完再触发第二个action。
    2019.10.4更新
  • action是为了异步请求后在action内部调用其他mutation。

mutation可以结构多个入参数吗?

当然,加个判断就好了。

@FrankKai
Copy link
Owner Author

FrankKai commented Oct 4, 2019

Modules的namespacing是什么操作?

  • 默认情况下,actions,mutations和getters是注册在global namespace的,这样设计的目的是多个module可以对同一mutation或者action做出响应。(据我使用两年vuex的经验,基本不会有这种操作,都是各模块管各模块的。)
  • namespaced: true可以使得模块更加自容器化和可重用,开启namespaced后,所有的getters,actions,mutations都会自动添加路径。
  • namespaced开启后,可以使用本地化的getters, dispatch和commit,单个module中的资源不需要增加前缀就可以访问。
  modules: {
    account: {
      namespaced: true,
      // module assets
      state: { ... }, // module state is already nested and not affected by namespace option
      getters: {
        isAdmin () { ... } // -> getters['account/isAdmin']
      },
      actions: {
        login () { ... } // -> dispatch('account/login')
      },
      mutations: {
        login () { ... } // -> commit('account/login')
      },
    },
  }
  • 获取全局的state,getter,mutation,action。getters可以通过rootState, rootGetters获得;mutation和action可以通过dispatch,commit的{root: true}去获取全局的mutation和action。
modules: {
  foo: {
    namespaced: true,

    getters: {
      // `getters` is localized to this module's getters
      // you can use rootGetters via 4th argument of getters
      someGetter (state, getters, rootState, rootGetters) {
        getters.someOtherGetter // -> 'foo/someOtherGetter'
        rootGetters.someOtherGetter // -> 'someOtherGetter'
      },
      someOtherGetter: state => { ... }
    },

    actions: {
      // dispatch and commit are also localized for this module
      // they will accept `root` option for the root dispatch/commit
      someAction ({ dispatch, commit, getters, rootGetters }) {
        getters.someGetter // -> 'foo/someGetter'
        rootGetters.someGetter // -> 'someGetter'

        dispatch('someOtherAction') // -> 'foo/someOtherAction'
        dispatch('someOtherAction', null, { root: true }) // -> 'someOtherAction'

        commit('someMutation') // -> 'foo/someMutation'
        commit('someMutation', null, { root: true }) // -> 'someMutation'
      },
      someOtherAction (ctx, payload) { ... }
    }
  }
}
  • 可以在module内部,通过{root: true}注册global action。(这种骚操作应用场景少吧。)
  • 使用namespace绑定helpers(mapState, mapGetters, mapActions和mapMutations),更高级的是用createNamespacedHelpers。
import { createNamespacedHelpers } from 'vuex'
const { mapState, mapActions } = createNamespacedHelpers('some/nested/module')
export default {
  computed: {
    // look up in `some/nested/module`
    ...mapState({
      a: state => state.a,
      b: state => state.b
    })
  },
  methods: {
    // look up in `some/nested/module`
    ...mapActions([
      'foo',
      'bar'
    ])
  }
}

一个组件可能需要多个module的state,mutation和action,可以使用多个mapState么?亲测可以。

...mapState({
      user: 'user', 
}),
...mapState({
      Foo: 'device',
}),

上述内容完全可以满足需求,其他内容暂时不用涉猎。

@FrankKai
Copy link
Owner Author

一次vuex的状态变更经历哪些步骤?

组件内的方法
commit (1.判断是否当前类型的mutation2.若有触发mutation)

Vuex是如何与Vue框架做双向数据绑定的?

使用了Vue实例作为全局状态树_vm($$state) deep watch所有属性

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant