大家都知道 Vue 是一个组件化的框架,通过一个一个的组件编写项目,通过 ESModule 模块化的特性来支持这样的编程模式。
Vue 的好处是上手快,因为 Vue 的框架已经把整体的结构固定,架子已经成形,你需要做的就是在相应的地方书写你的逻辑,例如在data中定义数据,在methods编写事件处理。
而坏处就是你无法用自己的思想、编程模式对其进行更大的优化,这个时候就需要用到 JS 编程本身的一些特殊或者模式进行改造。

我们本篇就使用「派发器模式」来改造组件的逻辑部分。
一般情况下,我们一个.vue文件内会把所有的逻辑都写在methods对象下,当这个文件内的逻辑非常多的情况下,methods就会特别的长查找一个东西就需要上下滚动代码,非常的麻烦。我们不希望把所有的逻辑都写在methods里面,而是要抽离一部分。
我们的目标是:通过 type 类型 ==> 找到对应的事件 ==> 找到对应的逻辑 ==> 通过 type 触发 ==> 派发器 ==> 对数据进行操作
整个过程有点类似 vuex:通过commit/dispatch去找对应的事件 ==> 然后在对应的 type 方法内更改 state 的数据。

改造前

下面就开始对传统的模式进行一个派发器的改造吧:

Demo ├─ src │ ├─ App.vue │ ├─ components │ │ └─ Counter │ │ ├─ index.vue │ │ ├─ button.vue │ │ └─ result.vue

以上就是我们本篇案例的大致目录结构,通过 App.vue 导入 components/Counter/index.vue 组件。
案例也很简单,就是想通过一个按钮把一个数字进行增加。

  1. <template>
  2. <div>
  3. <counter-result :result="result" />
  4. <div>
  5. <counter-btn innerText="+" action="PLUS" @compute="onCompute" />
  6. <counter-btn innerText="-" action="MINUS" @compute="onCompute" />
  7. </div>
  8. </div>
  9. </template>
  10. <script>
  11. import CounterResult from "../result.vue";
  12. import CounterBtn from "../button.vue";
  13. export default {
  14. name: "Counter",
  15. components: {
  16. CounterResult,
  17. CounterBtn
  18. },
  19. data() {
  20. return {
  21. result: 0
  22. };
  23. },
  24. methods: {
  25. onCompute(action) {
  26. // 判断事件类型
  27. switch (action) {
  28. case "PLUS":
  29. this.result += 1;
  30. break;
  31. case "MINUS":
  32. this.result -= 1;
  33. break;
  34. }
  35. }
  36. }
  37. };
  38. </script>
  1. <template>
  2. <h1>{{ result }}</h1>
  3. </template>
  4. <script>
  5. export default {
  6. name: "CounterResult",
  7. props: {
  8. result: Number
  9. }
  10. };
  11. </script>
  12. <style></style>
  1. <template>
  2. <button :action="action" @click="compute">
  3. {{ innerText }}
  4. </button>
  5. </template>
  6. <script>
  7. export default {
  8. props: {
  9. action: String, // 按钮的动作
  10. innerText: String // 按钮的内容
  11. },
  12. methods: {
  13. compute() {
  14. // emit 一个 compute 事件
  15. this.$emit("compute", this.action);
  16. }
  17. }
  18. };
  19. </script>
  20. <style></style>

这样我们就简单的实现了一个计数器:
屏幕录制2023-06-01 14.26.00.gif
下面我们将通过「派发器模式」对上面的案例进行改造!

改造后

目录结构:

Demo ├─ src │ ├─ App.vue │ ├─ actions # 定义事件的类型 │ │ └─ counter.js │ ├─ dispatchs # 根据事件类型去执行对应的逻辑 │ │ └─ counter.js │ ├─ reducers # 具体的事件处理 │ │ └─ counter.js │ ├─ components │ │ └─ Counter │ │ ├─ index.vue │ │ ├─ button.vue │ │ └─ result.vue

根据上面的目录结构,我们新增了 actions、dispatchs、reducers 这三个文件夹,其中核心就是通过 dispatchs 去管理对应的事件类型和事件处理。

  1. <template>
  2. <div>
  3. <counter-result :result="result" />
  4. <div>
  5. <counter-btn innerText="+" action="PLUS" @diapatch="onDiapatch" />
  6. <counter-btn innerText="-" action="MINUS" @diapatch="onDiapatch" />
  7. </div>
  8. </div>
  9. </template>
  10. <script>
  11. import CounterResult from "./result.vue";
  12. import CounterBtn from "./button.vue";
  13. import dispatch from "@/dispatchs/counter";
  14. export default {
  15. name: "Counter",
  16. components: {
  17. CounterResult,
  18. CounterBtn
  19. },
  20. data() {
  21. return {
  22. result: 0
  23. };
  24. },
  25. methods: {
  26. // 通过 dispatch 去触发对应的事件
  27. // 把 this 当前实例传递进去,这样就会产生闭包
  28. onDiapatch(...args) {
  29. dispatch(this)(...args);
  30. }
  31. /*
  32. onCompute(action) {
  33. switch (action) {
  34. case "PLUS":
  35. this.result += 1;
  36. break;
  37. case "MINUS":
  38. this.result -= 1;
  39. break;
  40. }
  41. }
  42. */
  43. }
  44. };
  45. </script>
  1. <template>
  2. <button :action="action" @click="compute">
  3. {{ innerText }}
  4. </button>
  5. </template>
  6. <script>
  7. export default {
  8. props: {
  9. action: String,
  10. innerText: String
  11. },
  12. methods: {
  13. compute() {
  14. // 这里 emit 出去的是一个 diapatch 事件
  15. this.$emit("diapatch", this.action);
  16. // this.$emit("compute", this.action);
  17. }
  18. }
  19. };
  20. </script>
  21. <style></style>

接下来我们就看看 dispatch 是怎么做的吧:

  1. // 导入事件管理
  2. import reducer from "../reducers/counter";
  3. // 导入事件类型
  4. import { PLUS, MINUS } from "../actions/counter";
  5. export default (ctx) => {
  6. // ctx 代表的就是组件的实例对象
  7. // 然后把 ctx 传递给 reducer 函数
  8. const { plus, minus } = reducer(ctx);
  9. return function (type, ...args) {
  10. // 通过判断事件类型去改变实例对象中的 result 书写
  11. switch (type) {
  12. case PLUS:
  13. ctx.result = plus(...args);
  14. break;
  15. case MINUS:
  16. ctx.result = minus(...args);
  17. break;
  18. }
  19. };
  20. };
  1. // 因为 src/dispatchs/counter.js 执行的时候把 ctx 传递了过来
  2. // 所以这里的 data 就是 ctx 就是组件实例对象
  3. function counterReducer(data) {
  4. function plus() {
  5. return data.result + 1;
  6. }
  7. function minus() {
  8. return data.result - 1;
  9. }
  10. // 把方法返回出去
  11. return { plus, minus };
  12. }
  13. export default counterReducer;
  1. const PLUS = "PLUS";
  2. const MINUS = "MINUS";
  3. // 把事件类型导出
  4. export { PLUS, MINUS };

这样,我们组件内部的逻辑就可以一部分抽离出来啦。