# 手写 vuex
## 第1步, 先安装vuex, 并介绍vuex的基本使用
### 1.安装:
yarn add vuex
### 2.新建 store/index.js
```js
import Vue from 'vue';
import vuex from 'vuex';
// 1.Vue.use(Vuex); Vuex是一个对象 install方法
// 2.Vuex中有一个Store类
// 3.混入到组件中 增添store属性
Vue.use(vuex);
const store = new vuex.Store({
state: { // -> data
age: 10,
},
getters: { // 计算属性
mumAge(state) {
return state.age + 30
}
},
mutations: { // method=> 同步的更改state mutation的参数是状态
changeAge(state, payload) {
state.age += payload; // 更新age属性
}
},
actions: { // 异步操作做完后将结果提交给mutations
changeAge({ commit }, payload) {
setTimeout(() => {
commit('changeAge', payload)
}, 1000);
}
},
});
export default store;
```
### 3.在入口文件挂载
```js
import store from './store/index.js';
new Vue({
router,
store, // 每个子组件,都会拥有一个属性$store
render: h => h(App)
}).$mount('#app')
```
### 4.在根组件app.vue里演示
```html
<template>
<div id="app">
Alice今年多少岁:{{$store.state.age}}
<br />
妈妈的年龄是:{{$store.getters.mumAge}}
<br />
<p>
<button @click="$store.commit('changeAge',5)">同步更新age</button>
<button @click="$store.dispatch('changeAge',8)">异步更新age</button>
</p>
</div>
</template>
```
## 第2步- 新建自己的插件包 vuex, 并添加相关配置
新建vuex/index.js
```js
let Vue;
// Vue.use 方法会调用插件的install方法,此方法中的参数就是Vue的构造函数
// Vue.use = function (plugin) {
// plugin.install(this);
// }
const install = (_vue)=>{
// _Vue 是Vue的构造函数
Vue = _vue;
// 需要将根组件中注入的store 分派给每一个组件 (子组件) 通过Vue.mixin
Vue.mixin({ // 内部会把生命周期函数 拍平成一个数组
beforeCreate:function(){
// 给所有的组件增加$store 属性 指向我们创建的store实例
console.log(this.$options.name);
const options = this.$options; // 获取用户所有的选项
if(options.store){ // 根实例
this.$store = options.store;
}else if(options.parent && options.parent.$store){ // 儿子 或者孙子....
this.$store = options.parent.$store;
}
},
});
}
const Store = ()=>{
}
// Vuex.Store Vuex.install
export default {
install,
Store
}
// 这个文件是入口文件,核心就是导出所有写好的方法
```
在store/index.js修改
```js
// import vuex from 'vuex'; //插件里的
import vuex from '../../vuex/index.js'; //自己插件里的
```
## 第3步 开始写install方法
vuex/store.js
```js
let Vue;
export const install = (_vue)=>{
// _Vue 是Vue的构造函数
Vue = _vue;
// 需要将根组件中注入的store 分派给每一个组件 (子组件) Vue.mixin
Vue.mixin({ // 内部会把生命周期函数 拍平成一个数组
beforeCreate:function(){
// 给所有的组件增加$store 属性 指向我们创建的store实例
console.log(this.$options.name);
const options = this.$options; // 获取用户所有的选项
if(options.store){ // 根实例
this.$store = options.store;
}else if(options.parent && options.parent.$store){ // 儿子 或者孙子....
this.$store = options.parent.$store;
}
},
});
}
```
## 第4步 处理store实例属性 state
```js
export class Store{ //容器的初始化
constructor(options){ //options 就是你new vuex.Store(state,getters,mutations,actions)
//方式一
const state = options.state; //数据变化要更新视图(核心逻辑)
// 通过new Vue({data}) - >响应式数据
// 1. 添加状态state逻辑, 数据在哪使用, 就会收集对应的依赖
this._vm = new Vue({
data:{ //属性如果是$开头的, 默认不会将这个属性挂载在vm上
$$state:state //会将$$state对应的对象 都通过defineProperty来进行属性劫持
},
computed:computedData
});
}
get state(){ //属性访问器,通过 new Store().state 访问 Object.defineProperty(Store,state:{})
return this._vm._data.$$state;
}
}
```
```js
//方式二:通过Vue.util.defineReactive方法, 将state 挂载在 Store的实例化对象this上, 初始值为options.state
Vue.util.defineReactive(this, 'state', options.state) ;
```
## 第5步 处理store实例属性 getters
```
// 2. 处理getters属性, 具有缓存的 computed带有缓存(如果值不变,多次取值不会重新赋值)
this.getters = {};
Object.keys(options.getters).forEach((key)=>{
Object.defineProperty(this.getters,key,{
get:()=>options.getters[key](this.state,options.getters)
})
});
```
优化:封装成一个函数
```js
//遍历对象
export const forEachValue = (obj,callback) =>{
Object.keys(obj).forEach(key=>{
callback(obj[key],key)
});
}
```
使用:
```js
forEachValue({a:1},function(value,key){
// key - > a
// value - >1
})
```
把处理getters的代码使用封装的函数后 就是这样
```js
forEachValue(options.getters,(fn,key)=>{
Object.defineProperty(this.getters,key,{
get:()=>fn(this.state)
})
})
```
但是这样有一个问题, getter不具有缓存的作用
添加测试
```
getters: { // 计算属性
mumAge:function(state) {
console.log(1111);
return state.age + 30;
}
},
```
每次回重复执行
**处理缓存问题**
```js
const computedData = {};
forEachValue(options.getters,(fn,key)=>{
//添加计算属性
computedData[key] = ()=>{ //将用户的getters定义在实例上
return fn(this.state);
}
Object.defineProperty(this.getters,key,{
// get:()=>{ return fn(this.state);} // 1. 这种不好,每次都要调用
get:()=>{ return this._vm[key]; }//2. 从实例属性上取,读缓存
})
});
// 1. 添加状态state逻辑, 数据在哪使用, 就会收集对应的依赖
this._vm = new Vue({
data:{ //属性如果是$开头的, 默认不会将这个属性挂载在vm上
$$state:state //会将$$state对应的对象 都通过defineProperty来进行属性劫持
},
computed:computedData
});
```
## 第6步 处理store实例属性 mutations
```js
// 3. 实现mutations
this.mutations = {};
// mutations: {
// changeAge:fn1,
// getAge:fn2
// }
forEachValue(options.mutations,(fn,key)=>{
this.mutations[key] = (payload)=>{
fn(this.state,payload); //第一个参数是state
}
})
```
通过提交一个 commitation函数修改state
需要给构造函数store添加一个原型方法
```js
// commit = function(){}
commit=(type,playload)=>{ //使用箭头函数的目的:保证当前的this 指向当前的store实例
// 调用commit就是去调用配置好的mutations函数
this.mutations[type](playload);
}
```
## 第7步 处理store实例属性 actions
```js
// 4.实现actions
this.actions = {};
forEachValue(options.actions,(fn,key)=>{
this.actions[key] = (payload)=>{
fn(this,payload); //第一个参数是store的实例
}
})
```
有异步数据更新的时候, 通过dispath一个action
需要给构造函数store添加一个原型方法
```
dispatch=(type,playload)=>{
this.actions[type](playload);
}
```
## 扩展 Vue.util.defineReactive方法
```js
// 第二种写法, 主要是体现在state的监测性 和 getters的 缓存处理方式上
export class Store{
constructor(options){
//方式二:通过Vue.util.defineReactive方法, 将state 挂载在 Store的实例化对象this上, 初始值为options.state
//第一步:state
Vue.util.defineReactive(this, 'state', options.state) ;
// state,getters,mutations,actions
// this.state = {};
this.getters = {};
this.mutations = {};
this.actions = {};
//第二步:getters
forEachValue(options.getters,(fn,key)=>{
// var newArr = {}; //缓存用
// debugger
// if(newArr[key] && newArr[key]==fn(this.state)){
// return fn(this.state);
// } else {
// newArr[key] = fn(this.state)
// }
Object.defineProperty(this.getters,key,{
//触发get, 读取属性的值, 这里没有实现缓存的功能
get:()=>{
return fn(this.state)
// return newArr[key]
},
// set:()=>{
// newArr[key] = fn(this.state)
// }
})
});
// 第三步:mutations
// mutations: {
// changeAge:fn1,
// getAge:fn2
// }
forEachValue(options.mutations,(fn,key)=>{
this.mutations[key] = (payload)=>{
fn(this.state,payload); //第一个参数是state
}
})
// 4.实现actions
forEachValue(options.actions,(fn,key)=>{
this.actions[key] = (payload)=>{
fn(this,payload); //第一个参数是store的实例
}
})
}
// commit = function(){}
//type:某个mutation函数
commit=(type,playload)=>{ //使用箭头函数的目的:保证当前的this 指向当前的store实例
// 调用commit就是去调用配置好的mutations函数
this.mutations[type](playload);
}
//type:某个action函数
dispatch=(type,playload)=>{
this.actions[type](playload);
}
}
```