首页 > 解决方案 > 是否可以将这种 HoC 模式转换为 React Hook 语法?

问题描述

我正在尝试创建一个可重用的组件,它会改变它的行为——本质上是渲染为 SVG 或 Canvas。我目前正试图通过将它包装在一个 HoC 中来做到这一点(但是我正在尝试使用 React 钩子),所以这一切都变得有点平淡。


编辑额外信息

使用当前方法(HoC 返回一个函数)我收到以下错误:

函数作为 React 子级无效。如果您返回一个组件而不是从渲染中返回,则可能会发生这种情况。或者,也许您打算调用此函数而不是返回它。

如果我删除 HoC 中的函数调用:

React.jsx:类型无效 - 需要一个字符串(对于内置组件)或一个类/函数(对于复合组件)但得到:对象


我已经看到了一个将 HoC 转换为 React 钩子的示例,但我正在努力弄清楚如何转换它 - 如果它甚至可能的话,并且想知道是否有人可以给我一个指针?我可能试图以错误的方式构建它,因为它感觉像是一个相当复杂的用例。

所以我现在有这两个 HoC,我觉得我需要以某种方式重构以使用称为withSVGand的钩子withCanvas。他们设置了不同的 DOM 来表示,重要的是有一个调用的函数renderLoop,需要从下面的 Scatter 组件中调用。

有点古怪,因为它Scatter现在只是业务逻辑(利用 useEffect 中的依赖项),它实际上不需要返回任何 DOM,因为它只是在操作父 HoC 的 DOM。我期待Scatter在未来会有更多的组件,但是所以不想将这个逻辑移动到 HoC 中(如果它们甚至是这样做的正确方法)。

const duration = 2000;

const withSVG = ({ width, height, ...props }) => WrappedComponent => {
    const layer = useRef(null);

    return (props) => (
        <g width={width} height={height} ref={layer}>
            <WrappedComponent {...props} layer={layer} />
        </g>
    );
};

const withCanvas = ({ width, height, ...props }) => WrappedComponent => {
    const layer = useRef(null);
    const canvas = useRef(null);
    const renderLoop = getRenderLoop(canvas.current, width, height);

    return (props) => (
        <React.Fragment>
            <custom ref={layer}/>
            <canvas width={width} height={height} ref={canvas}></canvas>
            <WrappedComponent {...props} layer={layer} renderLoop={renderLoop} />
        </React.Fragment>
    );
};

const Scatter = ({ data, layer, renderLoop }) => {
    useEffect(() => {
        if (!layer.current) { return; }

        // D3 data join
        const join = d3
          .select(layer.current)
          .selectAll("circle")
          .data(data, d => d.key);

        // Shrink the circles to 0 size
        const exit = join.exit()
            .transition("radius")
            .duration(duration)
            .attr("r", 0)
            .remove();

        const enter = join.enter()
            .append("circle")
            .attr("cx", d => d.x)
            .attr("cy", d => d.y)
            .attr("r", 0)
            .style("fill", d => d.color);

        const update = enter
            .merge(join)
            .transition("radius")
            .duration(duration)
            .attr("cx", d => d.x)
            .attr("cy", d => d.y)
            .attr("r", 30)
            .style("fill", d => d.color);
            
        if (renderLoop) {
            renderLoop(exit, update);
        }

    }, [layer, data]);

    return null;
};

const CanvasScatter = withCanvas(Scatter);
const SVGScatter = withSVG(Scatter);




// index.js
const width = 400;
const height = 400;

const App = () => {
    
    const [data, setData] = useState([
        {
            key: 1,
            x: 50,
            y: 50,
            color: "red"
        },
        {
            key: 2,
            x: 150,
            y: 150,
            color: "blue"
        },
    ]);

    setTimeout(() => {
        setData([
            {
            key: 1,
            x: 100,
            y: 100,
            color: "orange"
        },
        {
            key: 3,
            x: 150,
            y: 50,
            color: "green"
        },
        ]);
    }, 3000);

    return (
        <div>
            <svg width={width} height={height}>
                <SVGScatter width={width} height={height} data={data} />
            </svg>
            <CanvasScatter width={width} height={height} data={data} />
        </div>
    );

    // return <div>Hello React,Webpack 4 & Babel 7!</div>;
};

ReactDOM.render(<App />, document.querySelector("#root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="root"></div>

标签: javascriptreactjsreact-hooks

解决方案


问题

我认为您的高阶组件格式错误。看来您已经将它们定义为使用一些道具,然后是要包装的组件。

const withSVG = ({ width, height, ...props }) => WrappedComponent => {...}

但是您按预期调用它们

const SVGScatter = withSVG(Scatter);

这里Scatter组件首先被消费,HOC 尝试从中解构值并返回一个函数来消费一个组件,即undefined. 我认为这会导致您看到的错误。

解决方案

根据您使用装饰组件的方式

<SVGScatter width={width} height={height} data={data} />
<CanvasScatter width={width} height={height} data={data} />

我认为您的意思是从内部组件中解构widthheight即 HOC 返回的组件。

const withSVG = WrappedComponent => ({ width, height, ...props }) => {
  const layer = useRef(null);

  return (
    <g width={width} height={height} ref={layer}>
      <WrappedComponent
        layer={layer}
        {...props} // <-- data passed here
      />
    </g>
  );
};

const withCanvas = WrappedComponent => ({ width, height, ...props }) => {
  const layer = useRef(null);
  const canvas = useRef(null);
  const renderLoop = getRenderLoop(canvas.current, width, height);

  return (
    <React.Fragment>
      <custom ref={layer}/>
      <canvas width={width} height={height} ref={canvas}></canvas>
      <WrappedComponent
        layer={layer}
        renderLoop={renderLoop}
        {...props} // <-- data passed here
      />
    </React.Fragment>
  );
};

推荐阅读