木灵鱼儿
阅读:2355
vuex核心概念和底层原理
vuex作为一个共享的数据对象,用于不同组件的共用一个数据,并且动态响应数据变化,配合computed属性,可以对数据进行缓存。
在vuex里面,在异步处理actions上,最终也还是要用到commit同步操作方法操作数据,并不是actions方法本身处理了数据,原因是因为需要有操作记录,方便在插件上查看。
废话不多说,直接上底层原理。
原理
vuex其实就是一个重新封装的new Vue 对象,他的动态响应数据就是data属性,而commit这些方法,都只是回调而已。
import Vue from "vue";
const Store = function(options = {}) {
const {
state = {}, mutations = {}
} = options;
this._vm = new Vue({
data: {
$$state: state
},
});
this._mutations = mutations;
};
Store.prototype.commit = function(type, value) {
if (this._mutations[type]) {
this._mutations[type](this.state, value);
}
};
Object.defineProperties(Store.prototype, {
state: {
get: function() {
return this._vm._data.$$state
}
}
});
export default {
Store
};
引入一个vue对象,然后创建一个Store的构造函数,并接收一个参数options,options设置一个默认空对象。
Store构造函数内部我们解构这个options,获取到state和mutations对象,如果没有,我们也默认是一个空对象。
给Store构造函数的this,也就是prototype增加一个_vm
属性,该属性是一个new Vue对象,并且在这个vue的data属性上,我们使用$$state的key,其属性值指向了之前解构的state对象。
这里我们就将数据给监听了,利用vue的特性。
现在我们需要注册mutations对象,mutations本身就是同步操作state属性的方法的一个对象集合。
直接this._mutations = mutations
,将解构的mutations挂载到Store的prototype上。
创建commit方法
commit方法用于统一调用mutations中的对应的方法,我们看个vuex的例子:
this.$store.commit("方法名",value);
所以,我们也要给Store构造函数添加一个commit方法,这个方法接收两个参数,一个是要调用的方法名,一个是值。
Store.prototype.commit = function(type, value) {
if (this._mutations[type]) {
this._mutations[type](this.state, value);
}
};
然后我们if判断,如果this._mutations中存在这个方法,我们就调用和这个方法,并且把state和value值传过去。
注意
此时我们的this.state是没有的,你可以仔细往上看,在添加commit方法的时候,我们还没有对this这个对象,也就是Store本身添加一个state属性。
defineProperties配置一个state属性
defineProperties是defineProperty的加强版,defineProperty一次只能修改或者创建一个属性,defineProperties可以多个。
Object.defineProperties(Store.prototype, {
state: {
get: function() {
return this._vm._data.$$state
}
}
});
defineProperties接收两个参数,第一个要定义的对象,第二个是定义的参数。
定义一个state属性,他的get方法返回this._vm._data
下的$$state属性。
在vue对象里面,data对象是挂载的_data下的。
使用defineProperties定义的属性,是无法被for in
遍历出key值得,并且我们配置set方法,所以这个state也无法被改写。
你无法Store.state = {}
去改变state的指向,或者设置他的值,都不行。
然后我们导出这个State对象。
因为vuex的使用一般是这样:
new Vuex.Store()
所以我们也导出一个对象,对象里面包含Stote
这样我们在使用时:
import miniVuex from "./mini-vuex";
const miniStore = new miniVuex.Store({});
Vue.prototype.$miniStore = miniStore;
基本保持一致。
例子
import miniVuex from "./mini-vuex";
const miniStore = new miniVuex.Store({
state: {
test: "我是一个测试的值"
},
mutations: {
setTest(state, value) {
state.test = value;
}
}
});
Vue.prototype.$miniStore = miniStore;
这样我们可以通过this.$miniStore来使用。
配置getters
首先我们要知道,getters一般是怎么用的。
const miniStore = new miniVuex.Store({
state: {
test: "我是一个测试的值"
},
getters:{
test(state,getters){
return state.test;
}
},
mutations: {
setTest(state, value) {
state.test = value;
}
}
});
getters是一个对象,对象里面的方法,接收两个参数,一个是state,一个是getters,state就不用多说了,而getters获取的是getters对象本身,用户获取其他值。
而我们之前说过,getters实际上会使用conputed缓存,所以我们需要在miniVuex中,解构到这个getters,然后for循环遍历出里面的内容,并绑定到computed上。
const Store = function Store(options = {}) {
const {state = {}, mutations={}, getters={}} = options;
const computed = {}
const store = this;
store.getters = {};
for (let [key, fn] of Object.entries(getters)) {
computed[key] = function () {
return fn(store.state, store.getters);
};
};
this._vm = new Vue({
data: {
$$state: state
},
computed,
})
this._mutations = mutations
};
这里有几个关键的点:
Object.entries是一个对象的方法,他会将对象里面的键值对转换为数组,例:
const obj = {a:1,b:2};
console.log(Object.entries(obj));
//输出:[[a,1],[b,2]]
然后我们for of循环这个数组,该遍历方法是es6的方法,他会创建一个用于每次遍历的对象,如果是数组的话,第一个对象就是对应数组下标0.
然后使用解构,我们将key和fn解析出来,fn实际上就是创建vuex是,getters中我们预先写好的方法。
然后我们将它挂载到computed空对象上面,这里使用了一个工厂函数抛出,如果不使用工厂函数,这里我们首先要传入两个参数(state,getters),并且return出一个值,这就导致运行完毕后直接return,后面的函数就不执行了。所以我们需要一个函数包裹,这样return停止的是这个工厂函数,外面的代码依旧可以运行。
传入的两个参数
我们需要传入state和getters。
store的话可以通过this.store获取,但是在函数里面this的指向会发生变化,所以我们创建一个变量store来指向这个this
于是const store = this;
,第一个参数为store.state
而getters暂时还没有,所以我们需要手动创建,于是:store.getters = {};
,第二个参数store.getters
这个时候,我们已经将getters挂载到Store
这个构造函数对象上面,而computed挂载到this._vm.computed
上,而vue会将computed中的属性全都挂载到根对象上,所以我们可以直接通过this._vm.test
获取到数据。
但是这个时候并没有达到我们的要求,我们的getter的调用方法是这样的:
this.$store.getters.test
目前的调用方式是:miniVuex._vm.test
所以我们还需要改写store.getters的get方法。
使用Object.defineProperty
我们将对象的get方式改变一下
for (let [key, fn] of Object.entries(getters)) {
computed[key] = function () {
return fn(store.state, store.getters);
};
//改写get
Object.defineProperty(store.getters, key, {
get: function () {
return store._vm[key];
},
});
};
在for循环里面,我们对store.getters对象,创建一个key,而这个key直接返回store._vm[key]
,store._vm=vue根对象,所以实际上这里我们return的是computed的值,缓存目的达到。
这个时候:
miniStore.getters.test
是有值得了。
这样我们的getters就写完了。
挂载到vue对象上
先上源码:
let Vue;
function install(_Vue) {
Vue = _Vue;
function vuexInit() {
var options = this.$options;
// store injection
if (options.store) {
this.$store = typeof options.store === 'function' ?
options.store() :
options.store;
} else if (options.parent && options.parent.$store) {
this.$store = options.parent.$store;
}
}
Vue.mixin({
beforeCreate: vuexInit
});
}
我只知道,Vue.use()会默认执行js里面导出的install方法,然后将Vue对象作为参数传入install中
在这里,我们给vue的mixin全局混入,在每个创建vue对象之前beforeCreate,触发vuexInit方法。
vuexInit中this就是这个组件实例,然后他的options就是配置,如:
new Vue({
store,
render: h => h(App),
}).$mount('#app')
{
store,
render: h => h(App),
}
就是options,然后判断options上有没有store对象,如果有的话,就给this
创建一个$store
属性,然后他的值做一个三元判断,有可能这个vuex并没有初始化,所以他是一个方法,我们就赋值这个运行后的方法,然后如果不是一个方法,说明store是初始化好的,我们直接赋值。
这样这个组件就能通过this.$store获取store。
这个情况是针对第一个vue组件的,而其他的子组件,我们就要通过options.parent
获取到父组件,然后判断父组件有没有store,有的话就this.$store = options.parent.$store;
然后我没有看过vue的源码,但是根据这个情况,我判断vue的组件渲染他是有顺序的,所以这里总能从父组件获取到store,然后这样不断的传递下去,父传子,子传孙,然后他们都是同一个vuex对象。
完成品
main.js调用
import Vue from 'vue'
import Vuex from './min-vuex'
import App from './App.vue'
Vue.use(Vuex)
Vue.config.productionTip = false
const store = new Vuex.Store({
state: {
count: 0,
},
mutations: {
increment(state) {
state.count++
}
},
getters: {
doubleCount(state) {
return state.count * 2
}
}
})
new Vue({
store,
render: h => h(App),
}).$mount('#app')
miniVuex
let Vue;
function install(_Vue) {
Vue = _Vue;
function vuexInit() {
var options = this.$options;
// store injection
if (options.store) {
this.$store = typeof options.store === 'function' ?
options.store() :
options.store;
} else if (options.parent && options.parent.$store) {
this.$store = options.parent.$store;
}
}
Vue.mixin({
beforeCreate: vuexInit
});
}
const Store = function Store(options = {}) {
const {
state = {}, mutations = {}, getters = {}
} = options
const computed = {}
const store = this
store.getters = {};
for (let [key, fn] of Object.entries(getters)) {
computed[key] = function() {
return fn(store.state, store.getters);
};
Object.defineProperty(store.getters, key, {
get: function() {
return store._vm[key];
},
});
}
this._vm = new Vue({
data: {
$$state: state
},
computed,
})
this._mutations = mutations
}
Store.prototype.commit = function(type, payload) {
if (this._mutations[type]) {
this._mutations[type](this.state, payload)
}
}
Object.defineProperties(Store.prototype, {
state: {
get: function() {
return this._vm._data.$$state
}
}
});
export default {
Store,
install
}
版权申明
本文系作者 @木灵鱼儿 原创发布在木灵鱼儿 - 有梦就能远航站点。未经许可,禁止转载。
相关推荐
实现一个点击空白区域关闭显示的自定义指令
typescriptimport type { DirectiveBinding } from "vue"; export default { bind(el: HTMLElement, binding: DirectiveBinding<Function>) { //声明一个给document绑定的事件 function documentClick(event: Event) { const target = event.target as unknown as Node; if (el.contains(t...
使用vue2.7的一些踩坑事项
eslint校验的一些问题(暂时无解)在初始化项目时勾选了eslint校验之后,升级vue 2.7版本后,eslint-plugin-vue这个插件需要升级到9+版本,我目前使用的版本是:"eslint-plugin-vue": "^9.4.0"具体的一些可以参考官方提供的2.7升级指南:2.7日志官方居然把这个写在了变更日志里面,按道理最好官方的文档上也有一份说明的,但是目前没有。虽然eslint的依赖更新到新版后确实解决了一些项目启动报错的问题,但是有时候我们的SFC单文件组件开发的时候,template中的一些变量绑定还是会出现波浪线警告,这...
正确使用vue3的ts类型声明
前言使用了ts最头疼的是什么,除了类型声明应该没有第二家了,那么在vue3中如何正确的声明ts类型,代表着我们踏出了认识vue3的第一步,这非常重要,所以为此水个文章,分享给有需要的人。Volar 插件一开始我对于Volar并没有太大的需要,因为一直使用的Vetur,而且这个插件刚出来时并不完善,各种视频up讲的那个一键分屏功能其实也并不好用,虽然是个很有意思的东西,但是没有那种非要使用它的点,所以当时的我怀着这么一个疑问?为什么要用Volar ?现在我就通过两张图告诉你,它有多香!我们在template里面写代码,绑定变量最烦的是什么,就是我们写了个对象,但是忘了它的属性有哪些啊,使用...

关于给css自动添加浏览器前缀
前言为了兼容之前的旧浏览器版本,特别是安卓4.4这种低版本,transform是一定得增加浏览器前缀的,但是我在项目中遇到了设置无效的问题,极度蛋疼,下面是我的解决教程,当然没这个问题,看这篇文章,相信你对如果给项目增加浏览器前缀,会有很充分的认识。教程给css增加浏览器前缀,业界的做法就是使用postcss,目前webpack与vue cli他们需要安装的依赖略有不同:webpack:pnpm i postcss postcss-loader autoprefixer -D添加对应的rules{ test: /.s?css$/, use: [ ...省略...
vuex 动态注册和卸载模块
概述一般情况下,我们的vuex数据都是静态的,store在首次初始化后数据的格式就定好了,在日常使用中也确实应该这么做。但是,随着业务的发展,vuex可能会变得非常的大,或者在多页面打包的时候,每个页面都需要vuex,但是如果把每个页面的vuex都写在一起,你会发现,原来我a页面可能只需要30个vuex的数据监听,但是会多出来其他页面的数据,这显然不应该的。所以,我们需要一个能够动态加载模块的方法,每个页面动态加载自己的vuex数据使用。api了解vuex官方提供了几个api:registerModule动态注册模块apiunregisterModule卸载一个动态模块hasModule...
vue-i18n 的使用方式
安装vue2版需要安装8.x版本的,9.x的是vue3版本使用上大同小异。vue2安装:yarn add vue-i18n@8vue3安装:yarn add vue-i18n封装官方虽然支持很不错的用法,但是自定义处理是难免的。vue3文件目录结构├─ src │ ├─ language │ │ ├─ lang │ │ │ ├─ en.json │ │ │ └─ zh.json │ │ ├─ core │ │ │ ├─ i18n.ts │ │ │ ├─ customization.ts │ │ │ └─ language.ts │ │ ├─ i...
vue router 一个重定向页面的思路
当项目需要和其他项目进行沟通的时候,往往常见的做法就是,我在a网站点击一个按钮,在链接中携带query参数啥的,然后去访问b网站页面。此时可能会有两种情况:跳转的链接就是b网站的具体链接地址。跳转的链接是b网站的一个跳板,跳板会对参数做一些操作,然后进行重定向。两种做法都各有优势:第一种省事简单,但是如果以后b网站链接层级发生变化,那么就会导致链接不可能,到时候还需要进行修改,很痛苦。第二种的话,我们需要写一个跳板页面,但是通过一些参数要求,甚至自身加上一些逻辑处理,那么他的功能性会更强一些。那么,在vue中,做一个重定向页面,怎么做?思路思路1利用路由守卫,当链接上存在某个参数的时候,...
vue 过滤器 驼峰与短横线相互转换的方法
自己去注册吧,提供的是es6导出的方法//短横线转驼峰 export const dashToHump = function (value) { const textArr = value.split("-"); return textArr.map((item, index) => { if (index === 0) return item.toLowerCase(); return item.slice(0, 1).toUpperCase() + item.slice(1); }) .join("")...
使用vue.draggable拖拽组件遇到的一些问题
资源github:vue.draggable中文文档:vue.draggable中文文档api参考文档:sortablejsvue.draggable是基于sortablejs的vue封装,所有有些api官方并不会有过多的解释,可以去sortablejs查看下拖拽无法触发页面滚动当拖拽的内容大于页面宽高时,页面滚动就是一个必然的需求,但是vue.draggable默认情况并不能触发滚动。官方设置里有一个属性:scroll,如果为true时就能触发滚动,但是默认属性就是true;所以这个配置可以说是无效的。解决方案:cannot set scrollSensitivity19年的时候就有提...

关于 element table多选里面实现单选的偷懒做法
在elment的table多选中增加单选逻辑,这个也不能说产品的问题,单选和多选应该算是比较基础的应用,但是,element并没有对单选做支持。于是我有了一个偷懒思路!前提由于table的一键全选按钮无法进行细致化操作,无法控制点击时选中的数量,只能在选中后的回调里面处理,所以,我的做法是单选时隐藏这个按钮。 selectable方法可以返回布尔值来表示当前格子是否允许勾选或者取消,其实就是禁用的意思。<el-table-column type="selection" :selectable="onSelectable"></el...
