如何将包装好的组件传递到React路由器而不经常重新安装

时间:2017-10-31 18:05:18

标签: javascript reactjs redux react-router react-router-v4

我们正在升级我们的React App,经过几个小时的痛苦已经意识到将包装的组件传递到React Router(V4和其他人)会导致组件每次重新安装时都会“重新安装”传入。

这是包裹的组件......

export default function preload(WrappedComponent, props) {
    class Preload extends React.Component {
        componentWillMount() {
            getDataForComponent(props);
        }

        render() {
            return <WrappedComponent {...props} />;
        }
    }
    return Preload;
}

以下是我们如何使用它......

const FlagsApp = (props) => {
    return (
        <Route path="/report/:reportId/flag/:id/edit" component{preload(FlagForm, props)} />
    );
};

无论何时我们发送操作然后接收更新,组件都会重新安装,从而导致很多问题。根据github上的this thread,组件将重新安装,如果:

  
      
  • 在渲染过程中调用withRouter(..),每次都会创建一个新的组件类
  •   
  • 您将每个渲染的新函数传递给Route.component,例如:使用匿名功能   {...}} /&gt ;,这也会创建一个新组件
  •   

如果我直接传递FlagForm组件,问题就解决了,但我无法利用preload功能。

那么,如何在没有组件重新安装的情况下实现相同的结果?

提前感谢您的帮助!

2 个答案:

答案 0 :(得分:1)

Route在每次更新中添加新组件的原因是每次都通过preload为其分配了一个新类。

事实上,每次拨打preload始终会返回 不同 匿名类,甚至  当使用相同的参数调用时:

console.log( preload(FlagForm,props) != preload(FlagForm,props) ) // true

因此,问题是在preload组件FlagsApp方法中调用render,首先将其移到该范围之外:

const PreloadedFlagForm = preload(FlagForm, props) //moved out

const FlagsApp = (props) => {
    return (
        <Route path="/report/:reportId/flag/:id/edit"
            component={PreloadedFlagForm} /> //assign component directly
    );
};

这样Route的组件在更新之间不会发生变化。

现在关于props的那个挥之不去的preload参数:这实际上是一种反模式。传递props的正确方法只是标准方式:

const PreloadedFlagForm = preload(FlagForm) //drop the props arg

const FlagsApp = (props) => {
    return (
        <Route path="/report/:reportId/flag/:id/edit"
            component={<PreloadedFlagForm {...props} />} //spread it in here instead
        />
    );
};

因此preload的代码变为:

export default function preload(WrappedComponent) {
    class Preload extends React.Component {
        componentWillMount() {
            getDataForComponent(this.props);
        }

        render() {
            return <WrappedComponent {...this.props} />;
        }
    }
    return Preload;
}

希望有所帮助!

答案 1 :(得分:0)

如果像我一样你没有阅读说明,答案就在于render组件的<Route>道具

https://reacttraining.com/react-router/web/api/Route/render-func

  

渲染:func

     

这样可以方便地进行内联渲染和包装,而不会出现上述不需要的重新安装。

因此,您必须使用component道具,而不是将包装函数传递到render道具中。但是,您不能像上面那样传递包装组件。我仍然不完全理解发生了什么,但为了确保params正确传递,这是我的解决方案。

My Preload wrapper函数现在是一个React组件,用于呈现Route ...

export default class PreloadRoute extends React.Component {

    static propTypes = {
        preload: PropTypes.func.isRequired,
        data: PropTypes.shape().isRequired,
        location: PropTypes.shape({
            pathname: PropTypes.string.isRequired,
        }),
    }

    componentWillMount() {
        this.props.preload(this.props.data);
    }

    componentWillReceiveProps({ location = {}, preload, data }) {
        const { location: prevLocation = {} } = this.props;

        if (prevLocation.pathname !== location.pathname) {
            preload(data);
        }
    }

    render() {
        return (
            <Route {...this.props} />
        );
    }
}

然后我就这样使用它......

const FlagsApp = (props) => {
    return (
        <Switch>
            <PreloadRoute exact path="/report/:reportId/flag/new" preload={showNewFlagForm} data={props} render={() => <FlagForm />} />
            <PreloadRoute exact path="/report/:reportId/flag/:id" preload={showFlag} data={props} render={() => <ViewFlag />} />
            <PreloadRoute path="/report/:reportId/flag/:id/edit" preload={showEditFlagForm} data={props} render={() => <FlagForm />} />
        </Switch>
    );
};

我在this.props.preloadcomponentWillMount中同时调用componentWillReceiveProps的原因是因为我在导航时遇到了PreloadRoute组件无法重新安装的相反问题,所以这解决了这个问题。

希望这能为很多人节省很多时间,因为我花了很多时间才使这个工作得恰到好处。我认为这是成本最低的成本!