反应组件中的网络工作者未在更新中发布消息

时间:2019-09-14 22:21:47

标签: reactjs web-worker

我在React应用程序中使用Web Worker处理数据以在条形图中查看。我有一个BarChart组件。我在componentDidMount上实例化worker,并将消息发布给worker以格式化componentDidMount和componentDidUpdate上的数据。它在初始渲染时效果很好,但是当我从UI修改道具以修改图表视图时,由于某种原因该消息不会发送给工作人员。它会重新呈现,但不会与新数据一起呈现。有人知道为什么吗?

这是我的组成部分:

import React from "react";
import { Bar } from "components/_shared/react-chartjs";
import datalabels from "chartjs-plugin-datalabels";
import { observable, runInAction, toJS } from "mobx";
import { BarChartWorker as Worker } from "_entry/workers";
// import { FlatUiColors } from "./color-schemes";
import { aggregationType, formatDataForBarChart } from "./data-utils";
import { IBarData } from "./client-report";

export interface IBarDatasetSettings {
    label: string;
    backgroundColor?: string;
    borderColor?: string;
    data: number[];
    type?: string;
    fill?: boolean;
    yAxisID?: string;
    xAxisID?: string;
    datalabels?: any;
}

export interface IBarChartSettings {
    labels: string[];
    datasets: IBarDatasetSettings[];
}

interface IProps {
    data: any[];
    xAxis?: string | string[]; // function or array of column names
    yAxis?: string; // column name or function
    dataColNames?: string[]; // alternative to xAxis and yAxis if data is pivoted (E&M report) - TODO: phase this out to use xAxis/yAxis properties
    categoryCols: string[];
    expectedValCol?: string;
    expectedValBy?: string;
    lineForExpected?: boolean;
    stacked?: boolean;
    legendPosition?: "bottom" | "right" | "left" | "top";
    hideLegend?: boolean;
    percentValues?: boolean;
    aggregation?: aggregationType;
    reportConfigId: string;
    onBarClick?: (barData: IBarData) => void;
}

export interface ISeriesData {
    [dataColName: string]: number[];
    goal?: any;
}

export default class BarChartWidget extends React.Component<IProps> {
    private barChart: Bar;
    private barWorker: any;
    @observable private barData: any = {};

    componentDidMount() {
        this.barWorker = new Worker();
        this.barWorker.onmessage = event => {
            const { action, args } = event.data;
            runInAction(() => {
                if (action === "formatForBarChart") {
                    this.barData = args[0];
                }
                this.barWorker.terminate();
                this.forceUpdate();
            });
        };
        this.barWorker.onerror = e => {
            console.log("error occurred in bar worker", e);
        };
        this.setBarData();
    }

    componentWillUnmount() {
        if (this.barWorker) {
            console.log("terminating worker");
            this.barWorker.terminate();
        }
    }

    componentDidUpdate(prevProps: IProps) {
        const { data, xAxis, yAxis, categoryCols, dataColNames, expectedValCol, lineForExpected, expectedValBy, aggregation, reportConfigId } = this.props;
        const {
            data: prevData,
            xAxis: prevXAxis,
            yAxis: prevYAxis,
            categoryCols: prevCategoryCols,
            dataColNames: prevDataColNames,
            expectedValCol: prevExpectedValCol,
            lineForExpected: prevLineForExpected,
            expectedValBy: prevExpectedValBy,
            aggregation: prevAggregation,
            reportConfigId: prevReportConfigId,
        } = prevProps;

        if (
            prevData !== data ||
            prevXAxis !== xAxis ||
            prevYAxis !== yAxis ||
            prevCategoryCols !== categoryCols ||
            prevDataColNames !== dataColNames ||
            prevExpectedValCol !== expectedValCol ||
            prevLineForExpected !== lineForExpected ||
            prevExpectedValBy !== expectedValBy ||
            prevAggregation !== aggregation ||
            prevReportConfigId !== reportConfigId
        ) {
            this.setBarData();
        }
    }

    setBarData = () => {
        // format data for chartjs
        const { data, xAxis, yAxis, categoryCols, dataColNames, expectedValCol, lineForExpected, expectedValBy, aggregation, reportConfigId, percentValues, stacked } = this.props;
        if ("serviceWorker" in navigator && data && data.length) {
            this.barWorker.postMessage({
                action: "formatForBarChart",
                args: [
                    data ? data.slice() : [],
                    xAxis,
                    yAxis,
                    categoryCols,
                    dataColNames,
                    expectedValCol,
                    lineForExpected,
                    expectedValBy,
                    aggregation,
                    reportConfigId,
                    percentValues,
                    stacked,
                ],
            });
        } else {
            this.barData = formatDataForBarChart(
                data,
                xAxis!,
                yAxis!,
                categoryCols,
                dataColNames!,
                expectedValCol!,
                lineForExpected!,
                expectedValBy!,
                aggregation!,
                reportConfigId,
                percentValues || false,
                stacked || false
            );
        }
    };

    get overlappedBarPlugin() {
        const overlappedBarPlugin = {
            afterDatasetsUpdate: chart => {
                const { expectedValCol, stacked, lineForExpected, expectedValBy } = this.props;
                if (!expectedValCol || stacked || lineForExpected) return;
                const { datasets } = chart.data;
                chart.legend.options.onClick = (_e, legendItem) => {
                    const index = legendItem.datasetIndex;
                    const ci = chart;
                    const meta = ci.getDatasetMeta(index);
                    const metaGoal = ci.getDatasetMeta(index + datasets.length / 2); // hide goal data too

                    // See controller.isDatasetVisible comment
                    meta.hidden = meta.hidden === null ? !ci.data.datasets[index].hidden : null;
                    metaGoal.hidden = metaGoal.hidden === null ? !ci.data.datasets[index + datasets.length / 2].hidden : null;

                    // We hid a dataset ... rerender the chart
                    ci.update();
                };
                let barWidth = null;
                // Get the x position for data bars
                const xVals: number[][] = datasets.slice(0, datasets.length / 2).map(ds => {
                    const metaKeys = Object.keys(ds._meta);
                    const rects = metaKeys.length ? ds._meta[metaKeys[0]].data : [];
                    return rects.map(rectangle => {
                        if (!barWidth) barWidth = rectangle._model.width;
                        rectangle._model.width = rectangle._view.width = 0.6 * barWidth!; // set to 60% of width
                        return rectangle._model.x;
                    });
                });
                // Set x position for goal bars to be behind data bars
                datasets.slice(datasets.length / 2).forEach(gds => {
                    const gMetaKeys = Object.keys(gds._meta);
                    const goalRects = gMetaKeys.length ? gds._meta[gMetaKeys[0]].data : [];
                    goalRects.forEach(rectangle => {
                        const index = rectangle._datasetIndex - datasets.length / 2;
                        rectangle._model.x = rectangle._view.x = xVals[index][rectangle._index]; // set
                        if (!expectedValBy || expectedValBy === "bar") rectangle._model.width = rectangle._view.width = barWidth;
                    });
                });
            },
        };
        return overlappedBarPlugin;
    }

    divideParameters = (dataString: string, params: string[], spacer: string) => {
        const data: { [x: string]: any } = {};
        const paramValues = dataString.split(spacer);
        for (const i of Object.keys(params)) {
            data[params[i]] = paramValues[i];
        }
        return data;
    };

    handleBarClick = (e: any) => {
        // Handle bar click
        const { onBarClick, xAxis, expectedValCol, categoryCols } = this.props;
        const elem = this.barChart.chartInstance!.getElementAtEvent(e)[0];
        if (!elem || !onBarClick) return;

        const { datasetLabel: category, label: xValue } = elem._model;
        const { _index, _datasetIndex, _chart: chart } = elem;
        const yValue = chart.data.datasets[_datasetIndex].data[_index];
        const xAxisData = Array.isArray(xAxis) ? this.divideParameters(xValue, xAxis, " | ") : {};
        const categoryData = categoryCols ? this.divideParameters(category, categoryCols, " | ") : {};
        const isGoalBar = expectedValCol !== undefined && category.indexOf("Goal") !== -1;
        const data = { ...xAxisData, ...categoryData, yValue, isGoalBar };
        onBarClick(data);
    };

    get options() {
        const options = {
            onClick: this.handleBarClick,
            hover: {
                mode: false,
            },
            responsive: true,
            maintainAspectRatio: false,
            layout: {
                padding: {
                    top: 30,
                },
            },
            scales: {
                xAxes: [
                    {
                        id: "actual",
                        stacked: this.props.stacked,
                        ticks: {
                            callback: value => {
                                if (value.length > 15) return this.splitStringIntoChunks(value, 15);
                                return value;
                            },
                            fontColor: "#000",
                        },
                        categoryPercentage: 0.8,
                        barPercentage: 0.8,
                        gridLines: {
                            offsetGridLines: true,
                        },
                    },
                    {
                        display: false,
                        offset: true,
                        id: "expected",
                        type: "category",
                        categoryPercentage: !this.props.expectedValBy || this.props.expectedValBy === "bar" ? 0.8 : 1,
                        barPercentage: 0.8,
                        gridLines: {
                            offsetGridLines: true,
                        },
                    },
                    {
                        display: false,
                        id: "expected-line",
                        type: "category",
                        categoryPercentage: 1,
                        barPercentage: 1,
                        gridLines: {
                            offsetGridLines: true,
                        },
                    },
                ],
                yAxes: [
                    {
                        stacked: this.props.stacked,
                        ticks: {
                            beginAtZero: true,
                            fontColor: "#000",
                            callback: value => {
                                return this.props.percentValues ? `${value}%` : value;
                            },
                        },
                    },
                ],
            },
        };
        return options;
    }

    splitStringIntoChunks = (text: string, maxSize: number) => {
        const chunks: string[] = [];
        const words = text.split(" ");
        let current = "";
        for (let i = 0; i <= words.length; i++) {
            if (`${current} ${words[i]}`.length <= maxSize) {
                current = `${current} ${words[i]}`;
            } else {
                chunks.push(current.trim());
                current = words[i];
            }
        }
        return chunks;
    };

    render() {
        // console.log("rendering bar chart");
        const barData = toJS(this.barData);
        return (
            <div className="bar-chart">
                <Bar
                    ref={el => {
                        this.barChart = el!;
                    }}
                    data={barData}
                    legend={{
                        display: !this.props.hideLegend,
                        position: this.props.legendPosition || "right",
                        fullWidth: false,
                        labels: {
                            boxWidth: 15,
                            fontColor: "#000",
                            filter: (itm: any) => {
                                return itm.text && !itm.text.includes("Goal-");
                            },
                        },
                    }}
                    options={this.options}
                    plugins={[this.overlappedBarPlugin]}
                />
            </div>
        );
    }
}

这是我的工作脚本:

import { formatDataForBarChart } from "../../components/reports/data-utils";

const ctx: Worker = self as any;

ctx.addEventListener("message", event => {
    const { action, args } = event.data;
    console.log("in bar chart worker", action);
    if (action === "formatForBarChart")
        ctx.postMessage({
            action: "formatForBarChart",
            args: [formatDataForBarChart(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11])],
        });
});

任何想法将不胜感激!谢谢!

0 个答案:

没有答案