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

React-Plotly.js结合Redux存储布局时图表不渲染问题排查

问题描述

我使用react-plotly.js开发图表功能,希望将图表的layout状态存储在Redux中。使用React本地状态时功能完全正常,但切换到Redux后图表无法渲染。已经确认Redux状态能正确更新,推测问题和状态管理逻辑有关,但搞不懂为什么React状态可行、Redux却不行。


复现步骤与环境

1. 创建项目

npm create vite@latest test-app --template react

2. 依赖版本

"dependencies": {
  "@reduxjs/toolkit": "^1.9.3",
  "plotly.js": "^2.22.0",
  "react": "^18.2.0",
  "react-dom": "^18.2.0",
  "react-plotly.js": "^2.6.0",
  "react-redux": "^8.0.5",
  "react-resize-detector": "^9.1.0",
}

3. 目录结构

├── src
│   ├── App.jsx
│   ├── components
│   │   ├── PlotReactState.jsx
│   │   ├── PlotReduxState.jsx
│   │   └── plotSlice.js
│   ├── main.jsx
│   └── store
│       └── store.js

4. 代码文件

App.jsx
import PlotReactState from "./components/PlotReactState";
import PlotReduxState from "./components/PlotReduxState";

export default function App() {
  return (
    <div>
      <h4>使用React状态的图表</h4>
      <PlotReactState />
      <h4>使用Redux状态的图表</h4>
      <PlotReduxState />
    </div>
  );
}
components/PlotReactState.jsx
import { useState, useEffect } from "react";
import Plot from "react-plotly.js";
import { useResizeDetector } from "react-resize-detector";

export default function PlotReactState() {
  const { width, ref } = useResizeDetector();
  const [title, setTitle] = useState("");

  const [layout, setLayout] = useState({
    width: width,
    title: {
      text: title,
    },
    xaxis: {
      title: "",
    },
    yaxis: {
      title: "",
    },
    paper_bgcolor: "lightgrey",
    plot_bgcolor: "lightgrey",
  });

  useEffect(() => {
    setLayout((prevLayout) => ({ ...prevLayout, width: width }));
  }, [width]);

  useEffect(() => {
    setLayout((prevLayout) => ({ ...prevLayout, title: { text: title } }));
  }, [title]);

  const handleChange = (value) => {
    setLayout((prevLayout) => ({ ...prevLayout, xaxis: { title: value } }));
  };

  return (
    <div ref={ref}>
      <input
        type="text"
        placeholder="编辑标题"
        onChange={(event) => setTitle(event.target.value)}
      />
      <input
        type="text"
        placeholder="编辑X轴标题"
        onChange={(event) => handleChange(event.target.value)}
      />
      <Plot layout={layout} />
    </div>
  );
}
components/PlotReduxState.jsx
import Plot from "react-plotly.js";
import { useDispatch, useSelector } from "react-redux";
import { useResizeDetector } from "react-resize-detector";
import { updateTitle, updateXLabel } from "./plotSlice";

export default function PlotReduxState() {
  const dispatch = useDispatch();
  const { layout } = useSelector((state) => state.plot);
  const { width, ref } = useResizeDetector();

  return (
    <div ref={ref}>
      <input
        type="text"
        placeholder="编辑标题"
        onChange={(event) => dispatch(updateTitle(event.target.value))}
      />
      <input
        type="text"
        placeholder="编辑X轴标题"
        onChange={(event) => dispatch(updateXLabel(event.target.value))}
      />
      <Plot layout={layout} />
    </div>
  );
}
components/plotSlice.js
import { createSlice } from "@reduxjs/toolkit";

const baseLayout = {
  title: {
    text: "",
  },
  xaxis: {
    title: "",
  },
  paper_bgcolor: "lightgrey",
  plot_bgcolor: "lightgrey",
}

const plotSlice = createSlice({
  name: "plot",
  initialState: {
    layout: baseLayout,
  },
  reducers: {
    updateTitle: (state, {payload}) => {
      state.layout = {
        ...state.layout,
        title: {
          text: payload
        }
      }
    },
    updateXLabel: (state, {payload}) => {
      state.layout = {
        ...state.layout,
        xaxis: {
          title: payload
        }
      }
    },
  },
});

export const { updateTitle, updateXLabel } = plotSlice.actions;

export default plotSlice.reducer;
store/store.js
import { configureStore } from "@reduxjs/toolkit";
import plotReducer from "../components/plotSlice";

export const store = configureStore({
  reducer: {
    plot: plotReducer
  },
});
main.jsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import { Provider } from "react-redux";
import App from './App'
import { store } from "./store/store";

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>,
)

问题原因与修复方案

问题根源

  1. 缺失width属性:React状态版本的初始layout包含width字段,而Redux的baseLayout没有这个属性。react-plotly.js的Plot组件必须拿到有效的宽度值才能渲染图表。
  2. 未响应容器尺寸变化:React状态版本通过useEffect监听容器宽度变化并更新layout,但Redux版本完全没处理这个逻辑。

修复方案(二选一)

方案一:组件内合并layout与width(简单快捷)

修改PlotReduxState.jsx,把Redux中的layout和当前容器宽度合并后传给Plot:

import Plot from "react-plotly.js";
import { useDispatch, useSelector } from "react-redux";
import { useResizeDetector } from "react-resize-detector";
import { updateTitle, updateXLabel } from "./plotSlice";

export default function PlotReduxState() {
  const dispatch = useDispatch();
  const { layout: reduxLayout } = useSelector((state) => state.plot);
  const { width, ref } = useResizeDetector();

  // 合并Redux状态与当前宽度
  const layout = {
    ...reduxLayout,
    width: width
  };

  return (
    <div ref={ref}>
      <input
        type="text"
        placeholder="编辑标题"
        onChange={(event) => dispatch(updateTitle(event.target.value))}
      />
      <input
        type="text"
        placeholder="编辑X轴标题"
        onChange={(event) => dispatch(updateXLabel(event.target.value))}
      />
      <Plot layout={layout} />
    </div>
  );
}
方案二:Redux中管理width状态(全局场景适用)

如果需要全局同步图表宽度,可以给Redux添加更新width的逻辑:

  1. 更新plotSlice.js:
import { createSlice } from "@reduxjs/toolkit";

const baseLayout = {
  title: {
    text: "",
  },
  xaxis: {
    title: "",
  },
  paper_bgcolor: "lightgrey",
  plot_bgcolor: "lightgrey",
  width: undefined // 添加初始width
}

const plotSlice = createSlice({
  name: "plot",
  initialState: {
    layout: baseLayout,
  },
  reducers: {
    updateTitle: (state, {payload}) => {
      state.layout = {
        ...state.layout,
        title: {
          text: payload
        }
      }
    },
    updateXLabel: (state, {payload}) => {
      state.layout = {
        ...state.layout,
        xaxis: {
          title: payload
        }
      }
    },
    updateWidth: (state, {payload}) => {
      state.layout = {
        ...state.layout,
        width: payload
      }
    }
  },
});

export const { updateTitle, updateXLabel, updateWidth } = plotSlice.actions;

export default plotSlice.reducer;
  1. 修改PlotReduxState.jsx:
import { useEffect } from "react";
import Plot from "react-plotly.js";
import { useDispatch, useSelector } from "react-redux";
import { useResizeDetector } from "react-resize-detector";
import { updateTitle, updateXLabel, updateWidth } from "./plotSlice";

export default function PlotReduxState() {
  const dispatch = useDispatch();
  const { layout } = useSelector((state) => state.plot);
  const { width, ref } = useResizeDetector();

  useEffect(() => {
    if (width) {
      dispatch(updateWidth(width));
    }
  }, [width, dispatch]);

  return (
    <div ref={ref}>
      <input
        type="text"
        placeholder="编辑标题"
        onChange={(event) => dispatch(updateTitle(event.target.value))}
      />
      <input
        type="text"
        placeholder="编辑X轴标题"
        onChange={(event) => dispatch(updateXLabel(event.target.value))}
      />
      <Plot layout={layout} />
    </div>
  );
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 16:44:57