JSX 语义化 状态机组件

关于有限状态机的定义 和 已有的实现就不再赘述了

阮一峰 : JavaScript与有限状态机

XState : js State Machine

状态机应用案例

我们从一个现实的应用案例开始吧

前端的 UI 组件, 尤其是 面向 C 端的 App 或 小程序, 经常需要细致的体验优化

在许多异步和异常的情况给与特殊的提示和效果

比如淘宝的商品列表, 需要有 :

  1. 正常展示列表数据 - Normal
  2. 加载中 - Loading
  3. 无相关结果 - Empty
  4. 网络请求错误或超时 - Error

这四种状态, 就可以用有限状态机来描述

enum ComponentStatus {
	normal,
	loading,
	error,
	empty
}

我们常用的写法和伪代码 :

export default () => {
  const [status, setStatus] = useState(ComponentStatus.loading)
  
  useEffect(() => {
    setStatus(loading)
    fetch(xxx).then(resp => {
      if(resp.data.length){
        setStatus(normal)
      }else {
        setStatus(empty)
      }
    }).catch(e => {
      setStatus(error)
    })
  }, [])
  
  return (
    <>
    	{status === ComponentStatus.normal && <NormalListUI />}
	    {status === ComponentStatus.loading && <LoadingUI />}
			{status === ComponentStatus.error && <ErrorUI />}
			{status === ComponentStatus.empty && <EmptyUI />}
    </>
  )
}

这种写法非常常见, 从枚举定义看来, 也比较清晰

不过我们还可以用更 JSX 的写法... 让他更加的语义化 ~

export default () => {
  const [status, setStatus] = useState(ComponentStatus.loading)
  
  useEffect(() => {
    setStatus(loading)
    fetch(xxx).then(resp => {
      if(resp.data.length){
        setStatus(normal)
      }else {
        setStatus(empty)
      }
    }).catch(e => {
      setStatus(error)
    })
  }, [])
  
  const Normal = StateMachine.create(ComponentStatus.normal)
  const Loading = StateMachine.create(ComponentStatus.loading)
  const Error = StateMachine.create(ComponentStatus.error)
  const Empty = StateMachine.create(ComponentStatus.empty)

  return (
    <StateMachine current={status}>
    	<Normal>
		    <NormalList>
	    </Normal>
      <Loading>
          <LoadingUI>
      </Loading>
      <Error>
          <ErrorUI />
      </Error>
      <Empty>
          <EmptyUI />
      </Empty>
    </StateMachine>
  )
}

emmmm ~ 更加 Semantic 了一些

这个小状态机的组件实现也非常简单 :

// StateMachine.tsx

import React from 'react'
const StateMachine = React.memo((props: { status: number; children: React.ReactElement[] }) => {
  return (
    <>
      {React.Children.map(props.children, (c) =>
        React.cloneElement(c as React.ReactElement, { status: props.status }),
      )}
    </>
  )
})

const createState = (status: number) => {
  return React.memo((props: { status?: number; children: React.ReactElement }) => {
    return props.status === status ? props.children : null
  })
}

export default Object.assign(StateMachine, { createState })

其它比如 Authorized 鉴权组件: 有权限和无权限状态, 对应显示和隐藏 children 组件, 也算是一个状态机的应用

它并不是一个功能完备的状态机, 而是用 JSX , 语义化的来描述一个状态机, 可读性较强 ~