【问题标题】:React Redux-a child component does not re-render on state changeReact Redux - 子组件不会在状态更改时重新渲染
【发布时间】:2018-06-04 06:30:16
【问题描述】:

架构是这样的:我有一个plotMap 组件,它从状态获取绘图列表并将它们映射到一组plotMarker 组件,这些组件根据地图缩放返回多边形/标记(另请阅读来自国家)。如果选择了给定的绘图进行编辑,plotMarker 组件将返回一个可编辑的plotPolygon 组件。当用户保存编辑的plotPolygon 组件时,这会更新状态图列表中的相应图。

问题:plotMarker 的多边形在成功保存编辑的plotPolygon 组件后立即显示,它不会更新为新形状,而是保留旧形状。只有当缩小,plotMarker 渲染其标记组件,然后放大,plotMarker 再次渲染其多边形组件时,才会显示新形状。

这可能是由于应用程序内部延迟造成的吗?如何让plotMarker 在成功保存后立即显示新多边形?

plotMap 组件

import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actions from '../../actions';
import { Map, TileLayer, LayersControl, MapControl } from 'react-leaflet';
import { GoogleLayer } from './GoogleLayer';
import { geolocated } from 'react-geolocated';
import 'leaflet-geocoder-mapzen';
import SearchBox from './searchBox';
import Control from 'react-leaflet-control';
import { centroid } from '@turf/turf';
import PlotMarker from './plotMarker';

const { BaseLayer } = LayersControl;
const key = 'key';
const hybrid = 'HYBRID';
const terrain = 'TERRAIN';
const road = 'ROADMAP';
const satellite = 'SATELLITE';

const centerLat = props => {
    if (
        props.isGeolocationAvailable &&
        props.isGeolocationEnabled &&
        props.coords
    ) {
        return props.coords.latitude;
    }
    return 32.11;
};

const centerLong = props => {
    if (
        props.isGeolocationAvailable &&
        props.isGeolocationEnabled &&
        props.coords
    ) {
        return props.coords.longitude;
    }
    return 34.963;
};

const mapCenterPoint = props => {
    if (props.plots && (props.selectedPlot || props.plotBeingEdited)) {
        let ourPlot = props.plots.filter(
            plot => plot._id === (props.selectedPlot || props.plotBeingEdited)
        )[0];
        try {
            let center = centroid(ourPlot.feature).geometry.coordinates.reverse();
            return { center: center, zoom: 16 };
        } catch (e) {
            console.log(e);
        }
    }
    return { center: [centerLat(props), centerLong(props)], zoom: 8 };
};

export class PlotMap extends Component {
    markers = props => {
        if (props.plots) {
            return (
                <div>
                    {(props.filteredPlots || props.plots).map(
                        plot =>
                            plot &&
                            plot.feature &&
                            plot._id && (
                                <PlotMarker
                                    key={plot._id}
                                    id={plot._id}
                                    name={plot.name}
                                    geoJSON={plot.feature}
                                />
                            )
                    )}
                </div>
            );
        }
    };

    render() {
        return (
            <div
                className="col-sm-8 m-auto p-0 flex-column float-right"
                style={{ height: `85vh` }}>
                <Map
                    center={mapCenterPoint(this.props).center}
                    zoom={mapCenterPoint(this.props).zoom}
                    zoomControl={true}
                    onZoomend={e => {
                        this.props.setZoomLevel(e.target.getZoom());
                    }}
                    onMoveEnd={e => {
                        this.props.setMapCenter(e.target.getCenter());
                    }}>
                    <LayersControl position="topright">
                        <BaseLayer name="Google Maps Roads">
                            <GoogleLayer googlekey={key} maptype={road} />
                        </BaseLayer>
                        <BaseLayer name="Google Maps Terrain">
                            <GoogleLayer googlekey={key} maptype={terrain} />
                        </BaseLayer>
                        <BaseLayer checked name="Google Maps Hybrid">
                            <GoogleLayer
                                googlekey={key}
                                maptype={hybrid}
                                libraries={['geometry', 'places']}
                            />
                        </BaseLayer>
                    </LayersControl>
                    <SearchBox postion="bottomright" />
                    {this.markers(this.props)}
                </Map>
            </div>
        );
    }
}

function mapStateToProps(state) {
    return {
        filteredPlots: state.plots.filteredPlots,
        plots: state.plots.plots,
        selectedPlot: state.plots.selectedPlot,
        mapCenter: state.plots.mapCenter
    };
}

export default geolocated({
    positionOptions: {
        enableHighAccuracy: false
    },
    userDecisionTimeout: 5000,
    suppressLocationOnMount: false
})(connect(mapStateToProps, actions)(PlotMap));

plotMarker 组件:

import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actions from '../../actions';
import { Marker, Popup, GeoJSON } from 'react-leaflet';
import { centroid } from '@turf/turf';
import PlotPolygon from './plotPolygon';

const position = geoJSON => {
    return centroid(geoJSON).geometry.coordinates.reverse();
};

export class PlotMarker extends Component {
    render() {
        const {
            id,
            name,
            geoJSON,
            zoomLevel,
            selectedPlot,
            plotBeingEdited
        } = this.props;
        const markerPosition = position(geoJSON);
        let style = () => {
            return {
                color: 'blue'
            };
        };
        if (selectedPlot === id) {
            style = () => {
                return {
                    color: 'red'
                };
            };
        }
        if (zoomLevel > 14 && plotBeingEdited === id) {
            return <PlotPolygon id={id} geoJSON={geoJSON} />;
        } else if (zoomLevel > 14) {
            return (
                <GeoJSON
                    id={id}
                    data={geoJSON}
                    style={style}
                    onClick={() => {
                        this.props.selectPlot(id);
                    }}
                />
            );
        }
        return (
            <Marker
                id={id}
                className="marker"
                position={markerPosition}
                onClick={() => {
                    this.props.selectPlot(id);
                }}>
                <Popup>
                    <span>{name}</span>
                </Popup>
            </Marker>
        );
    }
}

function mapStateToProps(state) {
    return {
        selectedPlot: state.plots.selectedPlot,
        plotBeingEdited: state.plots.plotBeingEdited,
        zoomLevel: state.plots.zoomLevel,
        plots: state.plots.plots,
        filteredPlots: state.plots.filteredPlots
    };
}

export default connect(mapStateToProps, actions)(PlotMarker);

plotPolygon 组件

import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actions from '../../actions';
import { Polygon, FeatureGroup } from 'react-leaflet';
import { EditControl } from 'react-leaflet-draw';

const positions = props => {
    return props.geoJSON.geometry.coordinates[0].map(a => [a[1], a[0]]);
};

export class PlotPolygon extends Component {
    render() {
        const { id, geoJSON } = this.props;
        return (
            <FeatureGroup>
                <EditControl
                    position="topright"
                    onEdited={e => {
                        e.layers.eachLayer(a => {
                            this.props.updatePlot({
                                id: id,
                                feature: a.toGeoJSON()
                            });
                        });
                    }}
                    edit={{ remove: false }}
                    draw={{
                        marker: false,
                        circle: false,
                        rectangle: false,
                        polygon: false,
                        polyline: false
                    }}
                />
                <Polygon positions={[positions(this.props)]} />;
            </FeatureGroup>
        );
    }
}

function mapStateToProps(state) {
    return { plots: state.plots.plots, filteredPlots: state.plots.filteredPlots };
}

export default connect(mapStateToProps, actions)(PlotPolygon);

【问题讨论】:

  • 退出编辑模式后是否需要重新设置缩放级别?您的逻辑在 mapCenterPoint 和 plotMarker 的 if(zoomLevel... 部分之间紧密耦合。我想知道您是否可能有一种状态导致它在编辑后总是评估为真(直到手动缩放):props.plots &amp;&amp; (props.selectedPlot || props.plotBeingEdited)
  • 是的,这行得通。我必须想出一种比我想出的 hacky 方法更优雅的方法(在操作中保存成功后立即发送 13 的缩放级别,然后是 15 的缩放级别),但它确实有效。您可以将评论作为答案,我会接受吗?

标签: javascript reactjs redux react-redux


【解决方案1】:

原解决方案:

退出编辑模式后是否需要重置缩放级别?您的逻辑在mapCenterPointplotMarkerif(zoomLevel... 部分之间紧密耦合。我想知道您是否可能有一种状态导致它在编辑后(直到手动缩放)总是评估为真:props.plots &amp;&amp; (props.selectedPlot || props.plotBeingEdited)


重构思考:

想出更优雅的方式

选项 1:HOC

高阶组件(或 HOC)可能是一个很好的方法。您的代码中已经有一个 HOC 示例:

export default connect(mapStateToProps, actions)(PlotMarker);

HOC 实际上只是在 React 世界中返回对您有用的东西的函数。因此,它们是一种很好的代码重用形式。 connect 是本场景中返回函数的 HOC,但 HOC 通常用于返回组件。所以你可能有一个像 plotMarker(zoomLevel, beingEdited) 这样的 HOC,它自己执行逻辑来确定要呈现哪种类型的标记组件。

查看React's Higher Order Component Documentation 了解更多信息。


选项 2:单一返回语句

我一直喜欢在渲染方法中使用单个返回语句。我还没有深入研究 React 如何处理差异,但根据我的经验,我很确定它以这种方式响应更好 - 所以它可能不需要你抽动缩放级别来重置它。

我将如何在一次返回中执行 PlotMarker 的所有逻辑:

export class PlotMarker extends Component{
  render(){
    const {
      id,
      name,
      geoJSON,
      zoomLevel,
      selectedPlot,
      plotBeingEdited
    } = this.props;

    const markerPosition = position(geoJSON);

    let style = () =>{
      return {
        color: 'blue'
      };
    };
    if(selectedPlot === id){
      style = () =>{
        return {
          color: 'red'
        };
      };
    }

    return (
      <div>
        {
          zoomLevel > 14 && plotBeingEdited === id &&
          <PlotPolygon id={id} geoJSON={geoJSON}/> ||
          
          zoomLevel > 14 &&
          <GeoJSON
            id={id}
            data={geoJSON}
            style={style}
            onClick={() =>{
              this.props.selectPlot(id);
            }}
          /> ||
          
          <Marker
            id={id}
            className="marker"
            position={markerPosition}
            onClick={() => {
              this.props.selectPlot(id);
            }}>
            <Popup>
              <span>{name}</span>
            </Popup>
          </Marker>
        }
      </div>
    );
  }
}

【讨论】:

    猜你喜欢
    • 2020-03-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-22
    相关资源
    最近更新 更多