React & Deck.GL:为每个 child 组件添加默认道具

React & Deck.GL: Add default props to each child component

我正在使用 Deck.GL 和 React 开发一组可配置的地图层。我有一个 BaseMap 组件,我会将数据层传递给它作为反应 children.

目前,我有这个:

底图:

export const BaseMap = ({ latitude = 0, longitude = 0, zoom = 4, children }) => {
    const deckProps = {
        initialViewState: { latitude, longitude, zoom },
        controller: true
    };
    return (
        <DeckGL {...deckProps}>
            {children}
            <StaticMap />
        </DeckGL>
    );
};

它的用法是这样的:

<BaseMap>
    <ScatterplotLayer
        data={scatterData}
        getPosition={getPositionFn}
        getRadius={1}
        radiusUnits={'pixels'}
        radiusMinPixels={1}
        radiusMaxPixels={100}
        filled={true}
        getFillColor={[255, 255, 255]}
    />
    <TextLayer
        data={textData}
        getPosition={getPositionFn}
        getColor={[255, 0, 0]}
        getText={getTextFn}
    />
</BaseMap>

没关系,但我想为每个 child 添加默认道具。

尝试 1

我在 BaseMap 中尝试过此操作,但出现错误 cannot assign to read only property props of object #<Object>:

...
return (
    <DeckGL {...deckProps}>
        {React.Children.map(children, (c) => {
            const defaultProps = {
                loaders: [CSVLoader]
            }
            c.props = { ...defaultProps, ...c.props };
            return c;
        })}
    </DeckGL>
);

尝试 2

我也尝试为每种类型的图层创建一个包装器组件,但出现错误 Cannot call a class as a function:

包装器:

export const ScatterplotLayerWrapper = (props) => {
    const defaultScatterProps = {
        loaders: [CSVLoader]
    };
    const scatterLayerProps = {
        ...defaultScatterProps,
        ...props
    };
    return <ScatterplotLayer {...scatterLayerProps} />;
};

这样使用:

<BaseMap>
    <ScatterplotLayerWrapper
        data={scatterData}
        getPosition={getPositionFn}
    />
</BaseMap>

我怀疑第二次尝试的问题与 caveat here 有关。

解决方案?

我可以想象两种解决方案(显然,可能还有其他解决方案!):

混淆来自对 deck.gl 的 React 组件如何工作以及那些 <ScatterplotLayer> 组件到底是什么(它们不是反应组件)的误解。

Deck.gl 的反应组件,DeckGL,拦截所有子项并确定它们是否实际上是“伪装成反应元素的层”(参见 code)。然后它从每个“元素”构建层并将它们传递回 DeckGLlayers 属性.

它们看起来像 React 组件,但实际上不是。它们不能在 React 上下文中自行呈现。它们根本无法在 DeckGL 组件之外呈现,因为它们仍然只是普通的 deck.gl 层。

这里的解决方案是创建一个新的地图层 class 就像您在任何其他上下文中所做的那样(不是包裹层的 React 组件)。文档是 here.

class WrappedTextLayer extends CompositeLayer {
    renderLayers() { // a method of `Layer` classes
        // special logic here
        return [new TextLayer(this.props)];
    }
}
WrappedTextLayer.layerName = 'WrappedTextLayer';
WrappedTextLayer.defaultProps = {
    getText: (): string => 'x',
    getSize: (): number => 32,
    getColor: [255, 255, 255]
};

export { WrappedTextLayer };

然后可以在 BaseMap 组件(或未包装的 DeckGL 组件`)中使用这个新层,如下所示:

<BaseMap>
    <WrappedTextLayer
        data={dataUrl}
        getPosition={(d) => [d.longitude, d.latitude]}
    />
</BaseMap>

此外,可以将完全相同的图层作为图层属性传递给 DeckGL

<DeckGL
    layers={[
        new WrappedTextLayer({
            data: dataUrl,
            getPosition: (d) => [d.longitude, d.latitude]
        })
    ]}
></DeckGL>

稍微修改 BaseMap 组件将允许它通过 layers 属性接受图层作为类似 JSX 的子级,

export const BaseMap = ({ latitude = 0, longitude = 0, zoom = 4, children, layers }) => {
    const deckProps = {
        initialViewState: { latitude, longitude, zoom },
        controller: true,
        layers
    };
    return (
        <DeckGL {...deckProps}>
            {children && !layers ? children : null}
            <StaticMap />
        </DeckGL>
    );
};