初学vue(2)-实现一个Vuex

437 阅读6分钟

概述

Vuex 集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以可预测的方式发生变化。

首先这张图大家都看了好多遍,但你要明白,它想要表达打核心思想是什么?

  • 集中式管理: store就类似于银行或者钱包,把我们要应用的状态集中的管理在store这个类里;并且store的这个状态一定要是响应式的,因为如果不是响应式的,它将来发生改变怎么去通知组件更新;
  • 可预测:怎么可预测呢?我们在使用组件时可以直接修改state吗?最好不要,是不是。因为你直接改state不可以预测。最可预测的方式就是:我让你用我提供的方式,去改就行了。所以才会有Dispatch、Commit这些方法,因为如果有这种方法的话,内部是怎么实现我们并不会去关心;其实它的核心思想就是在改状态之前去做一些其他的事情:比如说我们在commit一个mutaition时,我是不是可以先去给Devtools先去发送一些信息,让Devtools那些变更可以记录下来。当然我mutaition阶段也可以写一些插件,那可以做一些什么事呢,做log日志,可以去做数据的持久化,去做缓存....我们可以做好多好多事情,这就是可以测给我们带来的好处。

最后要搞清楚的一点,Mutation和Action的区别是什么?复杂的业务组合甚至是异步,我们都要在actions中去做;能够改状态的只有mutation

所以我们在确认一下,这张图想要表达的是什么?集中式管理、单向数据流、可预测的变更、改状态的只有Mutation。

安装

vue add vuex

核心概念

  • state 状态、数据
  • mutations 更改状态的函数
  • actions 异步操作
  • store 包含以上概念的容器

思路

我们平时在使用Vuex的时候也会

  1. Vue.use(Vuex)安装插件
  2. new Vuex.Store 使用实例
  3. 配置状态
  4. 在mutations里面改变状态

实现步骤

  • 实现一个Store的类

    • 维持一个响应式状态state
    • 实现commit、dispatch方法
  • 实现一个install方法

    • 挂载$store

问题

  1. mutations方法里面的state从哪里取来的?

实现

挂载$store

初学vue(1)-实现一个Vue Router 这一章里已有讲解,在此就不过多赘述了,那我们应该怎么办?大声的说出来?command+c、command+v。嗯,很好!

在这里需要着重强调一点 我们这个里的导出和router要有些不同,看一下在实例Store的时候向下取了一级

所以我们要

好的,这样就一一对应上了,我们接下来去store类里,去维护一个响应式的state

响应式的状态state

这个options是不是会接收一些选项啊,我们来思考一下会接收哪些选项? new Vuex.store()这个实例接收的对象对不。这个选项里面对我们最重要的是不是就是state啊!所以到这大家应该怎么做?是不是要把options这个选项里的state拿出来做响应式处理啊

好的,接下来两步走

1.保存配置

2.对state做响应式处理

敲黑板了,这里我们应该怎么做?于是我们就很自然而然的就想到了,初学vue(1)-实现一个Vue Router 这一章里的Vue.util.defineReactive()这个方法;不过今天我要展示我的方法二“借鸡生蛋”。

这里面的data = options.state 是不是相当于对new Vuex.store({state: {})中的state做处理;如果我把这个new Vue()赋值给this.state,我在App.vue上这样使用,页面会不会成功展示

好的,成功展示了,对不对?

那么我问一下大家?new Vue() 和 Vue.util.defineReactive()有什么区别?哪个性能好?答案是没有区别两个共用的是一种方法,唯一的区别就是new Vue() 的方法可能会占用一丢丢内存

为什么我写的这个东西可行啊?大家有没有想过?首先Vue对data做了哪些事情?

Vue初始化的时候会对dada做响应式处理,同时他还会做代理,data中响应式属性会被代理到Vue实例上

那我们想一想,我们定义的this.state 就是指向的这个vue实例啊,那么我是不是就可以直接访问到state里的变量;不过这种方法并不好,接下来我们来对它做一些改造。为什么会不好?这样以来用户就可以直接访问到Vue实例了对不对?用户甚至可以直接改,这是我们不希望的,所以我们要对它做一些封装。

  1. 我们把这个Vue实例保存到_vm的变量里,
  2. 然后对data也做一些改造,把options.state存放到$$state里。划重点了!!!$$state这样做有一个好处,这样就不会被代理了。后面及时用户取到了_vm,他也不会看到$$state
  3. 定义个get 方法,这样以后用户通过store在访问state属性,我们应该把_vm里的$$state返回去;this._vm._data;这里一定要记住,由于没有做代理,我们必须通过_data,才能访问到这个响应式数据

有的人可能就会问了:

直接$data不就好了?答案是$data不可以,因为$data不是响应式的;

我们打个断点来看一下

$data不是响应式的,只是一个普通的对象,即使你调用了也没有什么用,就算值变了界面也不会重新渲染;_data:这里面有个__ob__这个我们经常看到,是什么意思呢?其实就是Observe-观察者,我们只需要记住以后再碰到__ob__这样的数据就是响应式数据,由此可见应该用谁就显而易见了吧。

$$没有用过呀,它是响应式数据且隐藏吗?其实是这样子的,他一定会做响应,我加上两个$是为了避免刚才的代理过程,也就是用户在外面没有办法通过state直接访问到我们的这个实例,这是我所期望的

  1. 用户有可能会去改,我们定义一个set方法,来告诉用户,这里不可以去改

好了做完了,我们刷新一下页面,如果没有报错那就是成功的

很好,展示完美!接下来我们点击一下,报错了,因为我们还没有实现这个commit方法

接下来我们一起来实现一下这个commit这个方法

实现一个commit

老样子,我们先思考一下,在平时使用的过程中我们应该怎么使用?

commit(type, payload) 一个type(提交的类型),一个payload(可能的参数)

怎么做呢?大家想一下,用户传的参数,究竟是为了干啥?它将来提交的类型必须要和函数名称相同啊,这是不是就已经商量好了,我从mutation里取出了这个函数,就得调用它啊;

大家觉得这样可以改变吗?

改变了,是不是?

为什么可以这样做呢?我们一起来捋一下啊,我们提交了commit之后会通过提交类型找到这个函数,找到这个函数之后并执行这个函数,然后会把当前store实例下的state传给它。大家想一下这个this.state返回的是谁?其实就是$$state === options.state=== 用户的new Vuex.store()里定义的state,然后mutations里面调用了这个方法,接下来state一变,使用到这个state的组件就会重新render了(等后面有时间的时候,我会把这个流程画成一个图)

好了我们还有一个方法要实现就是dispatch

实现一个dispatch

我们先定义一下add方法,这个add方法的参数是一个上下文context,这里面可以解构出很多东西( { commit, state}, payload)

然后界面也做一下修改

我们来看一下界面,成功了没

结果就是上面的可以,下面的不可以,而且还报错了,连$options都找不到了,很显然this指向出问题,我们看一下我们actions里定义的add方法,在定时器里再调用commit,这个时候上下文早就发生改变了;然后我们是不是可以效仿react那边的做法啊,react是怎么做的呢?在constructot里直接bind(this),直接写死this;

界面跑通了,整体很丝滑,一个简单的vuex就实现了,接下来我们看一下整体的代码

源代码

app.vue

store下的index.js

yqvuex.js