事件驱动和状态驱动

793 阅读3分钟

1.背景

我们每天都在面对大量的UI界面,他们大多数都是bugs,越复杂的界面,就会有更多的bug。通过不断的迭代产品,会移除bug,提供更好的用户体验,但是随着新需求的增加,我们又回到了原点,出现了新的bug。

自从电脑出现后,我们就开始开发接口,为什么这么久了,仍然还是会出现这么多bug的状态?

2.不适合的开发方法:基于事件驱动

因为我们无论如何都无法完全预测用户如何使用我们的接口。 一个强大的软件会有很复杂界面,会有很多路径,这些路径有一些是无法预测的,尤其是边界case,这些都会导致软件出现期望外行为。 边界case的来源就是因为使用不当的开发方法:基于事件驱动开发 比如:实现一个button按钮,点击后进行请求图片操作,并显示成功或者失败的提示

import React, { useState } from "react"; import { fetchImage } from "./fetchImage"; const ImageFetcher = () => { const [isFetching, setFetching] = useState(false); const [isError, setError] = useState(false); const [isSuccess, setSuccess] = useState(false); const [image, setImage] = useState(null); const clickHandler = e => { setFetching(true); fetchImage() .then(response => { setSuccess(true); setImage(response); }) .catch(() => { setError(true); }) .finally(() => { setFetching(false); }); }; return (

{isFetching &&

loading...

} {isSuccess && } {isError &&

An error occured

} Get Image ); };

这样做有几个问题:

  1. 代码不能准确地表示组件的预期行为。本来是期望组件只有四种互斥状态:默认,加载中,成功和失败。但是这种代码显式表明或者保证是互斥的,还要处理222*2=16个组合条件(四个flag都有true/false两种情况)

  2. 耦合性强。状态逻辑耦合在ui实现中,是否渲染和如何渲染逻辑是耦合的。如果要避免多次点击按钮,重复发起请求的问题,还要增加代码,判断isFetching值。

3.更好的开发方法:基于状态驱动开发

与事件驱动的不同点,状态驱动是状态第一,事件第二,就是从防御转到进攻。 事件驱动,处理事件,然后判断当前状态,做不同的逻辑 状态驱动,已经知道当前状态,然后直接处理事件。 使用xstate来改造之前的例子

// 状态定义 import { Machine, assign } from "xstate"; import { fetchImage } from "./fetchImage"; export const machine = Machine({ id: "imageFetcher", initial: "ready", context: { image: null }, states: { ready: { on: { BUTTON_CLICKED: "fetching" } }, fetching: { invoke: { src: fetchImage, onDone: { target: "success", actions: assign({ image: (_, event) => event.data }) }, onError: "error" } }, success: {}, error: {} } }); //组件实现 import React from "react"; const ImageFetcher = () => { const [current, send] = useMachine(machine); const { image } = current.context; return (

{current.matches("ready") && ( <button onClick={() => send("BUTTON_CLICKED")}> Get Image )} {current.matches("fetching") &&

loading...

} {current.matches("success") && } {current.matches("error") &&

An error occured

} ); };

4.状态驱动的好处

有五点好处

  1. 定义状态名称。有更多的可读性
  2. 更好知悉组件的行为。
  3. 低耦合性。状态管理和ui渲染可以完全分离。切换不同框架(vue<-->react)状态逻辑可以复用。
  4. 更容易更准确的进行Test。只需要测试每种状态下可响应的事件即可
  5. 可视化。xstate自带的可视化工具,可以生成状态图