大家都知道 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 组件。
案例也很简单,就是想通过一个按钮把一个数字进行增加。
<template><div><counter-result :result="result" /><div><counter-btn innerText="+" action="PLUS" @compute="onCompute" /><counter-btn innerText="-" action="MINUS" @compute="onCompute" /></div></div></template><script>import CounterResult from "../result.vue";import CounterBtn from "../button.vue";export default {name: "Counter",components: {CounterResult,CounterBtn},data() {return {result: 0};},methods: {onCompute(action) {// 判断事件类型switch (action) {case "PLUS":this.result += 1;break;case "MINUS":this.result -= 1;break;}}}};</script>
<template><h1>{{ result }}</h1></template><script>export default {name: "CounterResult",props: {result: Number}};</script><style></style>
<template><button :action="action" @click="compute">{{ innerText }}</button></template><script>export default {props: {action: String, // 按钮的动作innerText: String // 按钮的内容},methods: {compute() {// emit 一个 compute 事件this.$emit("compute", this.action);}}};</script><style></style>
这样我们就简单的实现了一个计数器:
下面我们将通过「派发器模式」对上面的案例进行改造!
改造后
目录结构:
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 去管理对应的事件类型和事件处理。
<template><div><counter-result :result="result" /><div><counter-btn innerText="+" action="PLUS" @diapatch="onDiapatch" /><counter-btn innerText="-" action="MINUS" @diapatch="onDiapatch" /></div></div></template><script>import CounterResult from "./result.vue";import CounterBtn from "./button.vue";import dispatch from "@/dispatchs/counter";export default {name: "Counter",components: {CounterResult,CounterBtn},data() {return {result: 0};},methods: {// 通过 dispatch 去触发对应的事件// 把 this 当前实例传递进去,这样就会产生闭包onDiapatch(...args) {dispatch(this)(...args);}/*onCompute(action) {switch (action) {case "PLUS":this.result += 1;break;case "MINUS":this.result -= 1;break;}}*/}};</script>
<template><button :action="action" @click="compute">{{ innerText }}</button></template><script>export default {props: {action: String,innerText: String},methods: {compute() {// 这里 emit 出去的是一个 diapatch 事件this.$emit("diapatch", this.action);// this.$emit("compute", this.action);}}};</script><style></style>
接下来我们就看看 dispatch 是怎么做的吧:
// 导入事件管理import reducer from "../reducers/counter";// 导入事件类型import { PLUS, MINUS } from "../actions/counter";export default (ctx) => {// ctx 代表的就是组件的实例对象// 然后把 ctx 传递给 reducer 函数const { plus, minus } = reducer(ctx);return function (type, ...args) {// 通过判断事件类型去改变实例对象中的 result 书写switch (type) {case PLUS:ctx.result = plus(...args);break;case MINUS:ctx.result = minus(...args);break;}};};
// 因为 src/dispatchs/counter.js 执行的时候把 ctx 传递了过来// 所以这里的 data 就是 ctx 就是组件实例对象function counterReducer(data) {function plus() {return data.result + 1;}function minus() {return data.result - 1;}// 把方法返回出去return { plus, minus };}export default counterReducer;
const PLUS = "PLUS";const MINUS = "MINUS";// 把事件类型导出export { PLUS, MINUS };
这样,我们组件内部的逻辑就可以一部分抽离出来啦。
