You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React双setState执行顺序异常及初始渲染优化技术问询

问题1:状态更新顺序导致图表不更新的原因及解决

你遇到的核心问题是React状态更新是异步的:在clickHandler里,你调用await GetStockData后立刻执行dataPrep(),但此时setChartData还没完成状态更新,chartData依然是旧值(第一次点击时是sampleData,第二次点击才是新的API数据),所以生成的preppedData还是基于旧数据的,自然图表显示不对。另外你的GetStockData里根本没调用setSuccess,这也是个无效参数的小问题。

解决办法

有两种靠谱的实现方式:

方式1:让服务函数返回数据,组件内自行处理状态与预处理

修改服务文件,不在内部调用状态更新,直接返回解析后的数据:

// GetStockData.js
export default async function GetStockData(token, ticker) {
    const myHeaders = new Headers();
    myHeaders.append("Authorization", "Bearer " + token);

    const formdata = new FormData();
    formdata.append("Tick", ticker);

    const requestOptions = {
        method: 'POST',
        headers: myHeaders,
        body: formdata,
        redirect: 'follow'
    };

    const response = await fetch("https://abc.azurewebsites.net/api/stocks", requestOptions);
    const data = await response.json();
    return JSON.parse(data.content);
}

然后修改组件的clickHandler与dataPrep,直接用返回的数据做处理,跳过状态更新的异步等待:

const clickHandler = async()=>{
    const rawData = await GetStockData(token, ticker);
    setChartData(rawData);
    // 用新获取的rawData直接生成图表数据
    const {data, preplayout} = dataPrep(rawData);
    setpreppedData(data);
    setlayout(preplayout);
}

// 修改dataPrep,让它接收参数而非依赖组件状态
const dataPrep = (rawChartData)=>{
    const dateData  = [];
    const closeData = [];
    const openData = [];
    const lowData = [];
    const highData = []; 

    for(const prop in rawChartData["Time Series (Daily)"]){
        dateData.push(prop);
        const dailyData = rawChartData["Time Series (Daily)"][prop];
        openData.push(dailyData["1. open"]);
        highData.push(dailyData["2. high"]);
        lowData.push(dailyData["3. low"]);
        closeData.push(dailyData["5. adjusted close"]);
    }

    // 后续trace和layout代码保持不变...
}
方式2:用useEffect监听chartData变化,自动触发预处理

如果不想修改服务文件,就在组件中添加useEffect,当chartData更新时自动生成图表数据:

// 组件内新增useEffect
useEffect(() => {
    // 排除初始的sampleData,只处理API返回的新数据
    if (chartData !== sampleData) {
        const {data, preplayout} = dataPrep();
        setpreppedData(data);
        setlayout(preplayout);
    }
}, [chartData]);

// 简化clickHandler,只负责更新原始数据
const clickHandler = async()=>{
    await GetStockData(token,ticker,setChartData, setChartSuccess);
}

问题2:优化首次渲染逻辑

用sampleData占位避免报错的方式不够优雅,可以从状态合并、加载状态管理、减少冗余状态三个方向优化:

1. 合并状态,统一管理数据生命周期

把分散的chartData、preppedData、layout、chartSuccess合并成核心状态,比如:

const [state, setState] = useState({
    loading: false,
    rawData: null,
    error: null
});

2. 用加载/空状态替代示例数据

渲染时根据状态显示对应内容,不需要用sampleData占位:

  • 首次渲染:显示引导提示
  • 加载中:显示加载动画/文字
  • 加载成功:显示图表
  • 加载失败:显示错误提示(后续可补充错误处理逻辑)

3. 用useMemo缓存预处理数据,减少冗余状态

不需要单独存储preppedData和layout,用useMemo基于rawData实时计算,避免额外的状态更新:

import { useState, useContext, useEffect, useMemo } from 'react';

// 组件核心状态
const [rawData, setRawData] = useState(null);
const [loading, setLoading] = useState(false);

// 用useMemo缓存预处理后的图表数据和布局
const {chartData, layout} = useMemo(() => {
    if (!rawData) return {chartData: null, layout: null};
    
    const dateData  = [];
    const closeData = [];
    const openData = [];
    const lowData = [];
    const highData = []; 

    for(const prop in rawData["Time Series (Daily)"]){
        dateData.push(prop);
        const dailyData = rawData["Time Series (Daily)"][prop];
        openData.push(dailyData["1. open"]);
        highData.push(dailyData["2. high"]);
        lowData.push(dailyData["3. low"]);
        closeData.push(dailyData["5. adjusted close"]);
    }

    const trace1 = {
        x: dateData,
        close: closeData,
        increasing: {line: {color: 'green'}},
        decreasing: {line: {color: 'red'}},
        high: highData,
        line: {color: 'rgba(31,119,180,1)'}, 
        low: lowData,
        open: openData,
        type: 'candlestick', 
        xaxis: 'x', 
        yaxis: 'y'
    };

    const layout = {
        dragmode: 'zoom', 
        margin: {r: 10, t: 25, b: 40, l: 60}, 
        showlegend: false, 
        xaxis: {autorange: true, domain: [0,1], title: 'Date', type: 'date'}, 
        yaxis: {autorange: true, domain: [0,1], type: 'linear'}
    };

    return {chartData: [trace1], layout};
}, [rawData]);

// 带加载状态的点击事件
const clickHandler = async()=>{
    setLoading(true);
    try {
        const data = await GetStockData(token, ticker);
        setRawData(data);
    } catch (err) {
        console.error('加载数据失败:', err);
    } finally {
        setLoading(false);
    }
}

// 渲染部分
return (
    <>
        <div>
            {loading && <p>加载中...</p>}
            {!loading && !rawData && <p>点击下方按钮加载股票数据</p>}
            {!loading && rawData && chartData && (
                <Plot data={chartData} layout={layout} />
            )}
        </div>
        <button onClick={clickHandler} disabled={loading}>
            {loading ? '加载中' : 'Refresh'}
        </button>
    </>
);

4. 可选:自动触发首次加载

如果希望页面打开就自动加载数据,在useEffect中调用clickHandler即可:

useEffect(() => {
    clickHandler();
}, []);

内容的提问来源于stack exchange,提问作者Chris

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 15:00:58