手写基础 vuex
闲来无事 手搓一版基础的vuex 主旨在于理解vuex最核心的功能逻辑 跟实际源码出入比较大 了解一下即可 让新手同学对vuex有个初步了解
先从引入vuex开始:
个人习惯会 在入口文件 通过引入 './store/base.js' 文件 获取 store 实例 并注入vue根实例中
在 './store/base.js' 中代码如图所示:其中 Vuex 就是这次手写的部分
根据上图分析如下:
-
需要定义一个返回 install 方法的对象
(需要了解 Vue.use 的用法)
-
我们需要定义一个Store类 并通过 new 来创建 store 实例同时在创建时传入 store 的配置 包括 state, getters, mutations, actions 等
开始行动:
第一步 定义 index.js 入口文件实现 install 与 Store 导出
// index.js
import install from './install'
import Store from './store'
export default {
install,
Store
}
第二步 实现 install 方法
// install.js
export let Vue // 在vuex代码中使用的Vue 与外层项目一致 此处导出可以在其他文件使用
function install(vue){
Vue = vue // install方法接收vue 并赋值给Vue
// 通过mixin混入在每一vue实例中添加 beforCreate方法
// 有兴趣的同学可以了解一下vue 的 mergeOptions 操作,深入了解一下 混入以及合并策略
Vue.mixin({
beforeCreate(){
const options = this.$options
if(options.store){ //只有根实例上我们注入了store,options.store存在证明这是根实例
this.$store = options.store //在vue根实例添加$store
}else{
//已知根实例上已经有了$store 其他的vue一般都在根实例内部 所以我们通过$parent向上查找
//联想一下 出列在根实例外创建的vue 是不是都能链式的拿到 根上定义的那个$store
//由于是对象引用 所以所有的vue实例中定义的$store都指向根的$store store实例只需一个即可
if(this.$parent && this.$parent.$store){
this.$store = this.$parent.$store
}
}
}
})
}
export default install
通过 vue.mixin 我们在每一个vue实例中都能获取到$store对象 我们再来看看Store类做了什么
第三步 实现 Store 类
// store.js
import { Vue } from '../install' //从install中获取导出的Vue
class store{
constructor(options) {
// new Vue.Store({....}) 时传入 options 解构 options
let { state, getters = {}, mutations, actions } = options
//当我们修改store.state 属性值时需要触发页面更新操作 所以这里需要对state做响应式处理
//state响应式原理很巧妙的 使用new Vue实例来实现
const vueOptions = {
data: {
//命名为$$state 第一:防止重复 第二 以$开头的属性vue在做proxy时会忽略
//所以 不能通过 this.$$state直接访问到 需要通过this._data.$$state访问
$$state:state
}
}
// 初始化 getters getter实际就是vue computed的getter
this.initGetters(vueOptions, getters)
this.initMutations(mutations)
this.initActions(actions)
this._vm = new Vue(vueOptions)
/* 在 dispatch 调用 actions 时 内部使用commit或者dispatch时会出现this绑定问题 在这里手动绑定一下 或者可以使用箭头函数*/
this.commit = this.commit.bind(this)
this.dispatch = this.dispatch.bind(this)
}
/* (代理模式) 可以通过 this.state 访问this._vm._data.$$state */
get state(){
return this._vm._data.$$state
}
//store commit 方法 初始化未手动绑定this时此处可以使用箭头函数确保 this指向;
// 如:commit = (type,payload)=> ...
commit(type, payload){
return this.mutations[type](payload)
}
/* store dispatch 方法 this 绑定问题同上 */
dispatch(type, payload){
return this.actions[type](payload)
}
/* getters 使用computed来实现 并注入 state 参数 */
initGetters(vueOptions, getters){
const computed = {}
this.getters = {}
// 遍历getters生成compute 并且 传入this.state 使用getter时可以拿到state
Object.keys(getters).forEach(key =>{
computed[key] = ()=>{
return getters[key].call(this,this.state)
}
// 对getters做一层代理 在 vue中使用时 通过访问getter的key 实际获取的是 store实例中_vm的computed
Object.defineProperty(this.getters, key, {
get:()=>{
return this._vm[key]
}
})
})
vueOptions.computed = computed
}
/* 初始化 mutations 并注入 state */
initMutations(mutations){
this.mutations = {}
Object.keys(mutations).forEach(key =>{
this.mutations[key] = (payload)=>{
return mutations[key].call(this,this.state, payload)
}
})
}
/* 初始化 actions 并注入 store (包括: state, commit dispatch...) */
initActions(actions){
this.actions = {}
Object.keys(actions).forEach(key =>{
this.actions[key] = (payload)=>{
return actions[key].call(this,this,payload)
}
})
}
}
export default store
描述一下上面的流程:
- 解构 options 获取 用户定义的 state,getters,mutations,actions
- 初始化 state 将 state 做成响应式数据 在state发生变化时触发组件更新
- 初始化 getters 每一个getter就是一个computed 只是多了一个可以接收state值 同时需要对getters做代理 当通过this.$store.getters.xxx时能代理到 store._vm的computed属性上去
- 初始化 mutations 遍历mutations 重新定义方法 (payload)=>{xxx.call(this,state,payload)} 方式实现 commit 中 state参数以及payload参数
- 初始化 actions 同mutations一样传入 stroe 实例 在dispatch中都能获取到 state属性 以及 commit dispatch 方法
- 定义 commit 方法 通过 $store.commit(type,payload) 找到对应的mutation并执行 在mutation中 修改state 会触发响应式更新
- 定义 dispatch 方法 通过 $store.dispatch(type,payload) 找到对应的actions并执行
到此为止 简单版本的 vuex 就实现了 可以在项目中尝试一下 这里没有考虑 modules 严格模式 插件等功能 后续会出一版 包含这些功能的版本 这里写的比较粗糙 主旨还是为了扫盲 了解一下 vuex 即可
转载自:https://juejin.cn/post/7360879591236419618