策略页面架构设计

需求概述

在旧项目中,有一个策略页面包含四种策略,分别为:Auto、Spot、Long 和 Short。页面中涉及多种配置(Config),包括公共配置(Common Config)、期货配置(Futures Config)以及仅适用于现货的配置(Spot Only Config)。

旧项目实现方式

旧项目采用 MVC 架构,页面数据全部存储在 Controller 中。四种策略共用同一个 config。在页面中使用 panel 时,整个 Controller 会被直接传入 props。由于 panel 是一个独立的组件,需要进行数据分离,这种实现方式导致策略页面运行非常卡顿。具体问题包括:每个 panel 都复制了一份完整的数据;当数据发生修改时,所有 panel 都会重新渲染(rerender),用户体验极差。

旧项目里面有一个策略页面, 里面假设有四种策略, 分别为: Auto, Spot, Long, Short. 下面有一些 Config, 他们有的是公共 Config, 有的是 Futures Config,还有的是 Spot Only Config.

新项目实现方式

Strategy Page Architecture

  • 配置分离:为四种策略分别设置独立的 config。对于公共配置(Common Config),在更新时会同步更新其他三个 config。(为何不单独设置一个 common config?一方面,四种策略的配置已完全分离,公共配置数量较少时,这种方式是可接受的;另一方面,产品需求变动频繁,单独的公共配置不利于后续扩展。)
  • Panel 交互优化:panel 组件通过 onChange 回调管理当前页面的数据变动,而 visible 状态则由 Global Store 统一管理,因为这部分与页面无关。

优点:

  • 结构清晰:各 panel 之间的数据实现隔离,互不影响,避免了旧项目中数据共享导致的混乱问题。
  • 性能提升:单个 config 的变更不会触发其他 panel 的重新渲染(rerender),显著提高页面性能。
  • 扩展性强:面对产品需求的变动,只需修改对应的 config,即可快速适应调整。

伪代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
const Page = () => {
const {
commonVisible,
futuresVisible,
autoVisible,
setCommonVisible,
setAutoVisible,
setFuturesVisible,
} = useGlobalStore()

const [strategyType, setStrategyType] = useState<
"Auto",
"Long",
"Spot",
"Short"
>("Auto")

const [autoConfig, setAutoConfig] = useState({
// Auto Config
})
const [spotConfig, setSpotConfig] = useState({
// Spot Config
})
const [longConfig, setLongConfig] = useState({
// Long Config
})
const [shortConfig, setShortConfig] = useState({
// Short Config
})

const onAutoConfigChange = (value) => {
setAutoConfig({ ...autoConfig, value })
}

const onCommonConfigChange = (value) => {
setAutoConfig({ ...autoConfig, value })
setSpotConfig({ ...spotConfig, value })
setLongConfig({ ...longConfig, value })
setShortConfig({ ...shortConfig, value })
}

const onFuturesConfigChange = (value) => {
setLongConfig({ ...longConfig, value })
setShortConfig({ ...shortConfig, value })
}

return (
<div>
{/* Change Strategy Type */}
<div>
<div onClick={() => setStrategyType("Auto")}>Auto</div>
<div onClick={() => setStrategyType("Long")}>Long</div>
<div onClick={() => setStrategyType("Short")}>Short</div>
<div onClick={() => setStrategyType("Spot")}>Spot</div>
</div>

<div>
{/* Open Config Panel */}
<div onClick={() => setCommonVisible(true)}>Common Config</div>
{(strategyType === "Long" || strategyType === "Short") && (
<div onClick={() => setFuturesVisible(true)}>Futures Config</div>
)}
{strategyType === "Auto" && (
<div onClick={() => setAutoVisible(true)}>Auto Config</div>
)}
</div>

<Panel
visible={commonVisible}
title="Common"
value={autoConfig.value}
onChange={onCommonConfigChange}
/>
<Panel
visible={autoVisible}
title="Auto"
value={autoConfig.value}
onChange={onAutoConfigChange}
/>
<Panel
visible={futuresVisible}
title="Futures"
value={longConfig.value}
onChange={onFuturesConfigChange}
/>
</div>
)
}