![88d23ca9223ccad7a397869f3580debf.png](https://img-blog.csdnimg.cn/img_convert/88d23ca9223ccad7a397869f3580debf.png)
最近接了一个需要PK玩法的直播间内挂件,用户可以发起PK进行比赛,比赛过程中实时更新数据,结束之后展示结算并重新等待主播发起PK,一个小型的带交互流程任务。
一开始思路是上来就是淦,React,肯定能实现,定义function
, 引入 useState
一把梭, 写着写着忽然觉得有点失控,因为我竟然无法很好的解耦每个状态的逻辑,靠React
原生state
做区分不同视图&不同行为很罗嗦,判断逻辑写了一堆(策略模式这时候也无法解决我的问题了,因为我暂时没办法统一处理所有切换策略发生的地方)。 而且切换流程稍微复杂,所以才会有力不从心的感觉。
每当遇到这种时候,慢即是快,硬着莽写出来的代码肯定bug很多。于是稍微整理了下,将整个流程的状态切换画了一下。
![9edcd7fceff8cadbdcb27add2b697d15.png](https://img-blog.csdnimg.cn/img_convert/9edcd7fceff8cadbdcb27add2b697d15.png)
绘制状态流程图后发现所有状态都是能预测出来的,所以我想到了能否用状态模式来解决这种问题。
运用状态模式首先需要知道我们切换不同的状态,是想让哪些行为发生变更。在这里我期望的是根据不同状态变更数据源和渲染函数的行为。之所以有这两个是因为:由于是实时请求,所以目前我们底层的数据源由一个轮询实现,这个轮询和渲染函数解耦,会自动和服务器协商调整更新频率,轮询器同时会将数据送给这个渲染函数,也就是渲染函数里的React组件仅负责渲染。
此时我每个状态可以封装如下
const getInitState = () => {
return {
fetch: fetchInitState,
render: renderInitPage
}
}
const getWaittingState = () => {
return {
fetch: fetchWaittingState,
render: renderWaittingPage
}
}
const getPKState = () => {
return {
fetch: fetchPKState,
render: renderPKPage
}
}
const getResultState = () => {
return {
fetch: fetchResultState,
render: rendeResultState
}
}
// fetch: () => fetch('/api/xxx') 单次请求,轮询器会以此用来轮询数据
// render: () => ReactElement 当数据来到之后使用这个渲染页面
子状态封装好了之后,我们需要有个统一的状态机使用这些状态
function StateMachine(state, onStateChange) {
this.state = this.setState(state || getInitState);
this.onChange= onStateChange;
}
StateMachine.prototype.setState = function (getNewState) {
this.state = getNewState(this);
// restart & refresh page immediately
this.onChange();
}
StateMachine.prototype.fetch = function() {
return this.state.fetch();
}
StateMachine.prototype.render = function() {
return this.state.render();
}
这里我将StateMachine
的实例传入了每个getState
函数中,目的是为了让每个State
可以自由地切换到想要的State
, 比如getInitState
的实现如下
const getInitState = (stateMachine) => {
function renderInitPage({ data }) {
const onClick = () => {
stateMachine.setState(getWaittingState);
};
return (
<div onClick={onClick}> click me </div>
)
}
return {
fetch: fetchInitState,
render: renderInitPage
}
}
至于使用方,则使用起来就清晰很多, 我将额外逻辑剥离,基本上一个程序骨架变换如下。
function APP() {
const [data, setData] = useState(null);
const timerRef = useRef(-1);
const stateMachineRef = useRef(null);
const onStateChange = useCallback(() => {
clearInterval(timerRef.current);
timerRef.current = setInterval(() => {
stateMachineRef.current.fetch()
.then(setData);
}, []);
}, []);
useEffect(() => {
stateMachineRef.current = new StateMachine(null, onStateChange);
return () => {
clearInterval(timerRef.current);
}
}, []);
return stateMachineRef.render({ data });
}
这样的好处是未来我如果想新增一种状态可以以一种非侵入原有逻辑的形式添加新的状态。
比如在上面的状态中,需求发生变更,需要新增一种状态,需要玩家等待过程中可能随机进入乱斗模式。。。(屁股)
![7ac32f0b077a911f4adb213f1e195484.png](https://img-blog.csdnimg.cn/img_convert/7ac32f0b077a911f4adb213f1e195484.png)
在这种设计模式基础上,相信解决思路很清晰,很容易的就能独立的写出相应状态逻辑,而且将变更的可能性减少到最小规模。
最后补上状态模式原语
状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类
![167873a4db6d7e82dd2853ca4e1ff6dc.png](https://img-blog.csdnimg.cn/img_convert/167873a4db6d7e82dd2853ca4e1ff6dc.png)