React+Flux+TypeScript双下拉框加载完成后触发计算的问题
这种场景在Flux开发里太常见了,核心痛点就是要准确追踪两个异步操作的完成状态,同时避开Flux的“嵌套dispatch”限制。我给你几个实用的方案:
方案1:组件内追踪加载状态,异步触发计算
你可以在组件状态里加两个标记,分别记录两个下拉框的加载完成状态,每次某个Store更新后,就检查两个标记是否都为true,如果是就触发初始计算——注意要用异步延迟避开嵌套dispatch的问题:
constructor(props) { super(props); this.state = { // 你的原有状态 timePresetOptions: [], selectedTimePreset: null, anotherDropdownOptions: [], selectedAnotherValue: null, // 新增加载状态标记 isTimePresetsLoaded: false, isAnotherDropdownLoaded: false }; } componentWillMount() { // 同时监听两个Store AvailabilityTimePresetsStore.addChangeListener(this._onChangeTimePresets); AnotherDropdownStore.addChangeListener(this._onChangeAnotherDropdown); // 发起两个API请求 AvailabilityTimePresetActions.getTimePresets(); AnotherDropdownActions.getOptions(); } _onChangeTimePresets = () => { const timePresets = AvailabilityTimePresetsStore.getState().presets; const initialPreset = this.state.selectedTimePreset || timePresets[0]; this.setState({ timePresetOptions: timePresets, selectedTimePreset: initialPreset, isTimePresetsLoaded: true }, this.checkIfReady); // 在setState回调里检查状态 } _onChangeAnotherDropdown = () => { const anotherOptions = AnotherDropdownStore.getState().options; const initialAnotherValue = this.state.selectedAnotherValue || anotherOptions[0]; this.setState({ anotherDropdownOptions: anotherOptions, selectedAnotherValue: initialAnotherValue, isAnotherDropdownLoaded: true }, this.checkIfReady); } checkIfReady = () => { const { isTimePresetsLoaded, isAnotherDropdownLoaded, selectedTimePreset, selectedAnotherValue } = this.state; // 确保两个下拉都加载完成,且初始值已设置 if (isTimePresetsLoaded && isAnotherDropdownLoaded && selectedTimePreset && selectedAnotherValue) { // 用setTimeout把计算放到下一个事件循环,避开嵌套dispatch setTimeout(() => { this.recalc(selectedTimePreset, selectedAnotherValue); }, 0); } } recalc = (preset, anotherVal) => { // 这里触发你的查询逻辑,比如发起Action QueryActions.fetchResults(preset, anotherVal); }
方案2:用协调Store处理跨Store依赖
如果你的应用里这种多Store联动的场景比较多,不如抽离一个协调Store来专门处理这种逻辑。这个Store监听两个下拉Store的变化,当两者都加载完成后,自动触发初始计算:
// 协调Store示例 class CoordinationStore extends EventEmitter { private isTimePresetsReady = false; private isAnotherDropdownReady = false; private timePreset: Preset | null = null; private anotherValue: AnotherType | null = null; constructor() { super(); // 监听两个下拉Store的变化 AvailabilityTimePresetsStore.addChangeListener(this.handleTimePresetsUpdate); AnotherDropdownStore.addChangeListener(this.handleAnotherDropdownUpdate); } handleTimePresetsUpdate = () => { const presets = AvailabilityTimePresetsStore.getState().presets; this.isTimePresetsReady = true; this.timePreset = presets[0]; this.triggerInitialCalcIfReady(); } handleAnotherDropdownUpdate = () => { const options = AnotherDropdownStore.getState().options; this.isAnotherDropdownReady = true; this.anotherValue = options[0]; this.triggerInitialCalcIfReady(); } triggerInitialCalcIfReady = () => { if (this.isTimePresetsReady && this.isAnotherDropdownReady && this.timePreset && this.anotherValue) { // 触发初始查询Action,这里不会有嵌套dispatch问题 QueryActions.fetchResults(this.timePreset, this.anotherValue); // 解绑监听,避免重复触发 AvailabilityTimePresetsStore.removeChangeListener(this.handleTimePresetsUpdate); AnotherDropdownStore.removeChangeListener(this.handleAnotherDropdownUpdate); } } } export default new CoordinationStore();
之后你的组件只需要监听这个协调Store的变化,更新查询结果的状态即可。这种方案更符合Flux的单一数据源思想,把跨Store的逻辑从组件里抽离出来,让组件更专注于渲染。
方案3:用Promise并行发起请求(如果Action支持)
如果你的Action里的API调用是基于Promise的(也就是Action会返回API请求的Promise),那可以直接在组件里用Promise.all并行发起两个请求,等两者都完成后再设置状态并触发计算:
componentWillMount() { // 假设你的Action返回Promise Promise.all([ AvailabilityTimePresetActions.getTimePresets(), AnotherDropdownActions.getOptions() ]).then(([timePresets, anotherOptions]) => { // 两个请求都完成,直接设置状态 this.setState({ timePresetOptions: timePresets, selectedTimePreset: timePresets[0], anotherDropdownOptions: anotherOptions, selectedAnotherValue: anotherOptions[0] }, () => { // 在setState回调里触发计算 this.recalc(this.state.selectedTimePreset, this.state.selectedAnotherValue); }); }); }
这个方案最简洁,但前提是你的Flux Action要支持返回Promise——如果你的Action只是触发API调用然后依赖Store的change事件,那这个方案就不适用。
关键注意点
不管用哪种方案,都要避免在Store的emitChange同步回调里直接触发新的dispatch。因为Flux的dispatch是同步执行的,嵌套dispatch会导致状态不一致,所以要么用setTimeout把计算逻辑放到下一个事件循环,要么用协调Store在所有依赖Store更新完成后再触发新的Action。
内容的提问来源于stack exchange,提问作者RNDThoughts

