本文写于 2022 年 5 月 17 日。
如果我们需要一个 TODO list 应用,你会怎么样去设计它?
最简单直接的方法:
如果按照这种写法,一旦项目复杂了,我们将难以新增功能与维护。
例如,我希望让用户一秒内最多提交 1 次(节流);或者我们有多处可以修改储存 todoList 数组的手段,我希望集中这些操作,不要每修改一次数组就修改一次 DOM……
按照刚刚的思路,代码可能会变得比较“脏”。
其根本原因就是我们没有解耦 UI 和状态。
那么接下来,我们将尝试利用基于”事件“的思想来优化代码。
对于前端来说,事件化是解耦模块与模块之间业务逻辑的手段,也是大型前端项目构筑的一味良药。
只要你使用 JavaScript 操作过 DOM,就一定接触过事件。
window.addEventListener("resize", () => {}); button.addEventListener("click", (e) => {});
当我们的窗口被缩放的时候,触发函数;当一个按钮被点击的时候,触发函数。这就是事件。
并且事件往往是多播的,也就是说不论你往函数中添加多少个函数,在事件触发的时候,所有的函数都会被调用。
button.addEventListener("click", (e) => console.log(1)); button.addEventListener("click", (e) => console.log(2)); button.addEventListener("click", (e) => console.log(3)); button.addEventListener("click", (e) => console.log(4)); button.click(); // 1 // 2 // 3 // 4
回到我们文章开头的例子。
在基于事件的思想下,我们可以得到如下的分析结论:
那么我们的伪代码就可以这么写:
const todoListData = 创建一个可以被监听的数据([]); const todoList新增事件 = 创建一个事件源(); const todoList删除事件 = 创建一个事件源(); todoList新增事件.监听触发((新增的todo) => todoListData.新增一项(新增的todo)); todoList删除事件.监听触发((要删除的todo) => todoListData.删除一项(要删除的todo) ); todoListData.监听变化((此时的todoList) => { // 修改 HTML });
瞬间我们的代码就变得简洁了。如果要继续添加其他的代码逻辑也很简单,因为我们将修改数据和修改视图进行了分离。
这里你可以选择自己喜欢的库进行事件的编写,例如 RxJS、node 自带的 EventEmitter 等等……
如果你用的是 React,那么 Redux 也是这种思路。
接下来我将使用 RxJS 做一个简单的范例。
const todoListSource$ = BehaviorSubject([]); export const todoList$ = todoListSource$.pipe(debounceTime(0)); const createItemEventSource$ = Subject(); const createItemEvent$ = createItemEventSource$.pipe(throttleTime(200)); const deleteItemEventSource$ = Subject(); const deleteItemEvent$ = deleteItemEventSource$.asObservable(); export function createItem(newItem) { createItemEventSource$.next(newItem); } export function deleteItem(targetItem) { deleteItemEventSource$.next(targetItem); } function onTodoItemCreate(newItem) { todoListSource$.next([...todoListSource$.current, newItem]); } function onTodoItemDelete(targetItem) { todoListSource$.next( todoListSource$.current.filter((item) => item !== targetItem) ); } createItemEvent$.subscribe(onTodoItemCreated); deleteItemEvent$.subscribe(onTodoItemDelete);
简简单单几行代码,却实现了较为复杂的功能。我们来分析看看。
首先第二行。我们在监听数据源的同时做了一个防抖操作。
这里是为了防止当我们订阅了数据源,去做别的操作时,数据源被频繁的修改导致的多次触发订阅,就像 Vue 为什么会有 nextTick 一样。
后面第五行。我们为增加事件添加了节流,这样用户就没办法在短时间内多次新增新项,避免误触。
事件化的编程,让事件成为了模块之间的消息中介,我们不再简单粗暴的互相调用函数、引用变量,而是经由“事件系统”这样一个中介人。
中介人可以帮我们处理很多事情,例如防抖、节流。进一步优化我们的代码。
因此,对于前端来说,事件化是解耦业务逻辑与视图层的重要手段,也是大型前端项目构筑的一味良药。
(完)