【问题标题】:Migrate IIEF outside of JSX module and import it将 IIEF 迁移到 JSX 模块之外并导入
【发布时间】:2020-11-20 13:43:10
【问题描述】:

我有以下名为 chart 的 jsx 组件,我想将 IIFE (d3.legend.js) 移动到它自己的文件中。

import React, { useEffect, useRef } from "react";
import PropTypes from "prop-types";
import * as d3 from "d3";

/* d3.legend.js */
(function () {
  d3.legend = function (g) {
    g.each(function () {
      const g = d3.select(this),
        svg = d3.select(g.property("nearestViewportElement")),
        legendPadding = g.attr("data-style-padding") || 5;
      let items = {},
        lb = g.selectAll(".legend-box").data([true]),
        li = g.selectAll(".legend-items").data([true]);

      lb = lb.enter().append("rect").classed("legend-box", true).merge(lb);
      li = li.enter().append("g").classed("legend-items", true).merge(li);

      svg.selectAll("[data-legend]").each(function () {
        const self = d3.select(this);
        items[self.attr("data-legend")] = {
          pos: self.attr("data-legend-pos") || this.getBBox().y,
          color:
            self.attr("data-legend-color") ||
            self.style("stroke") ||
            self.style("fill")
        };
      });

      items = Object.entries(items).sort(
        ([keyA, valA], [keyB, valB]) => valA.pos - valB.pos
      );

      li.selectAll("g")
        .data(items, ([key]) => key)
        .join(
          (enter) => {
            const g = enter
              .append("g")
              .attr("data-key", ([key, value]) => key)
              .classed("legend-item", true);
            g.append("text")
              .attr("y", (d, i) => i * 1.25 + "em")
              .attr("x", "1em")
              .text(([key]) => key);
            g.append("circle")
              .attr("cy", (d, i) => i * 1.25 - 0.25 + "em")
              .attr("cx", 0)
              .attr("r", "0.4em")
              .style("fill", ([key, value]) => value.color);
            return g;
          },
          (update) => update,
          (exit) => exit.remove()
        );

      // Reposition and resize the box
      var lbbox = li.node().getBBox();
      lb.attr("x", lbbox.x - legendPadding)
        .attr("y", lbbox.y - legendPadding)
        .attr("height", lbbox.height + 2 * legendPadding)
        .attr("width", lbbox.width + 2 * legendPadding);
    });

    return g;
  };
})();

const palette = [
  "#e41a1c",
  "#377eb8",
  "#4daf4a",
  "#984ea3",
  "#ff7f00",
  "#ffff33",
  "#a65628",
  "#f781bf",
  "#999999"
];

const Chart = (props) => {
  const { config: { xField, yField, groupingKey }, data } = props;

  const ref = useRef();

  const margin = { top: 10, right: 60, bottom: 20, left: 60 },
    width = 720 - margin.left - margin.right,
    height = 600 - margin.top - margin.bottom;

  useEffect(() => {
    const svgElement = d3.select(ref.current);

    // Empty the children
    svgElement.selectAll("*").remove();

    // Inner-SVG
    const svgInner = svgElement
      .append("g")
      .attr("transform", `translate(${margin.left}, ${margin.top})`);

    // Group the data: I want to draw one line per group
    const groupedData = [
      ...d3.group(data, (d) => d[groupingKey])
    ].map(([key, values]) => ({ key, values }));

    const halfway = Math.floor(groupedData.length / 2);
    const dataLeft = groupedData.slice(0, halfway);
    const dataRight = groupedData.slice(halfway);

    // Add X axis --> it is a date format
    const xScale = d3
      .scaleLinear()
      .domain(d3.extent(data, (d) => d[xField]))
      .range([0, width]);

    svgInner
      .append("g")
      .attr("class", "d3-axis d3-axis-bottom")
      .attr("transform", `translate(0, ${height})`)
      .call(
        d3
          .axisBottom(xScale)
          .ticks(2)
          .tickFormat((year) => year.toString())
      );

    // Add Y axis
    const yScaleLeft = d3
      .scaleLinear()
      .domain([
        0,
        d3.max(
          dataLeft.flatMap((d) => d.values),
          (d) => d[yField]
        )
      ])
      .range([height, 0]);

    const yScaleRight = d3
      .scaleLinear()
      .domain([
        0,
        d3.max(
          dataRight.flatMap((d) => d.values),
          (d) => d[yField]
        )
      ])
      .range([height, 0]);

    svgInner
      .append("g")
      .attr("class", "d3-axis d3-axis-left")
      .call(d3.axisLeft(yScaleLeft));

    svgInner
      .append("g")
      .attr("class", "d3-axis d3-axis-right")
      .attr("transform", `translate(${width}, 0)`)
      .call(d3.axisRight(yScaleRight));

    // Color palette
    const res = groupedData.map((d) => d.key).sort(); // list of group names

    const color = d3.scaleOrdinal().domain(res).range(palette);

    svgInner
      .append("g")
      .attr("transform", `translate(${margin.left}, ${margin.top})`);

    // Draw the line
    svgInner
      .selectAll(".line")
      .data(dataLeft)
      .enter()
      .append("path")
      .attr("fill", "none")
      .attr("data-legend", (d) => d.key)
      .attr("stroke", (d) => color(d.key))
      .attr("stroke-width", 1.5)
      .attr("d", (d) =>
        d3
          .line()
          .x((d) => xScale(d[xField]))
          .y((d) => yScaleLeft(+d[yField]))(d.values)
      );

    svgInner
      .selectAll(".line")
      .data(dataRight)
      .enter()
      .append("path")
      .attr("fill", "none")
      .attr("data-legend", (d) => d.key)
      .attr("stroke", (d) => color(d.key))
      .attr("stroke-width", 1.5)
      .attr("d", (d) =>
        d3
          .line()
          .x((d) => xScale(d[xField]))
          .y((d) => yScaleRight(+d[yField]))(d.values)
      );

    let legend = svgInner
      .append("g")
      .classed("legend", true)
      .attr("transform", "translate(30,20)")
      .style("font-size", "12px")
      .call(d3.legend);
  }, [
    data,
    groupingKey,
    xField,
    yField,
    width,
    height,
    margin.top,
    margin.left,
    margin.right
  ]);

  return (
    <div className="d3-chart">
      <svg
        ref={ref}
        width={width + margin.left + margin.right}
        height={height + margin.top + margin.bottom}
      />
    </div>
  );
};

Chart.defaultChartConfig = {
  groupingKey: null,
  xField: null,
  yField: null
};

Chart.defaultProps = {
  config: Chart.defaultChartConfig,
  data: []
};

Chart.propTypes = {
  config: PropTypes.object,
  data: PropTypes.arrayOf(PropTypes.object)
};

export default Chart;

我将如何导出这个?

export default (() => { d3.legend = (g) => { /* ... */ } })()

所以我可以导入它并调用d3.legend


注意:这里是snippet此时的代码。

【问题讨论】:

  • 该 IIFE 最初的目的是什么?无论如何都没有发生范围污染,拥有 IIFE 似乎完全没用。
  • @str 是一个d3插件,我更新了,想集成。
  • 假设你只想要副作用,你需要做import 'myModule'。如果您想导出执行此更改的能力,则应导出执行此逻辑的函数。因此,将其从 IIFE 更改以避免立即执行。

标签: javascript reactjs d3.js


【解决方案1】:

你可以把它放在一个封口上

export default () => (() => { d3.legend = (g) => { /* ... */ } })()

这样你就只需要 thunked 并且只需要担心 IIFE 会被执行。

【讨论】:

  • 经过实验,看起来我可以按原样 export default IIFE 并使用 import './d3.legend' 导入它。
【解决方案2】:

我最终将 IIFE 变成了一个独立的 legend 函数。

然后我将该函数与d3 合并,以便我可以扩展库并仍将其称为d3.legend。但如果我不尝试将其导入 JSX 模块,我可能会将其保留为 IIFE 以实现可移植性。

src/lib/d3/d3-legend.js

import * as d3 from `d3`;

const legend = (g) => { /* ... */ };

export default legend;

src/lib/d3/index.js

import * as d3 from `d3`;
import legend from './d3-legend'

export default {
  ...d3,
  legend
};

src/components/Chart.jsx

import d3 from '../lib/d3'

【讨论】:

    猜你喜欢
    • 2019-05-07
    • 2021-11-26
    • 2018-02-06
    • 1970-01-01
    • 2013-08-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多