大家都知道 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 };
这样,我们组件内部的逻辑就可以一部分抽离出来啦。