在 react router v4 中将 props 添加到包装路由的组件

Add props to component of wrapped route in react router v4

我有共享相同行为、布局等的路由。我想将道具从布局传递到路由中的那些组件(仪表板和登录)

我的routes.js文件如下

//imports omited    
export default (
    <AppLayout>
        <Route component={Dashboard} path="/" key="/" />
        <Route component={Login} path="/login" key="/login" />
    </AppLayout>
);

AppLayout.js的render方法有这段代码

const childrenWithExtraProp = React.Children.map(this.props.children, (child) => {
            return React.cloneElement(child, {
                component: React.cloneElement(child.props.component, {
                    functions: {
                        updateMenuTitle: this.updateTitle //function
                    }
                })
            });
        });

此代码导致许多错误:

Warning: Failed prop type: Invalid prop `component` of type `object` supplied to `Route`, expected `function`.
    in Route
    in AppHeader
    in Router (created by BrowserRouter)
    in BrowserRouter (created by App)
    in App


Check the render method of `Route`.
    in Route
    in AppHeader
    in Router (created by BrowserRouter)
    in BrowserRouter (created by App)
    in App


Uncaught Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.

    Check the render method of `Route`.
        at invariant (invariant.js?7313:42)
        at createFiberFromElement (react-dom.development.js?cada:5753)
        at reconcileSingleElement (react-dom.development.js?cada:7531)
        at reconcileChildFibers (react-dom.development.js?cada:7635)
        at reconcileChildrenAtExpirationTime (react-dom.development.js?cada:7756)
        at reconcileChildren (react-dom.development.js?cada:7747)
        at finishClassComponent (react-dom.development.js?cada:7881)
        at updateClassComponent (react-dom.development.js?cada:7850)
        at beginWork (react-dom.development.js?cada:8225)
        at performUnitOfWork (react-dom.development.js?cada:10224)

在我工作的最后一个项目中,我们在 Route 内部使用 Routes,但在 React-Router v4 中这是不允许的。

编辑:之前是这样的:

//Array of routes declared before
export default (
    <Router history={browserHistory}>
        <Route path="/" component={General}>
            <IndexRoute component={Index} />
            {routes}
        </Route>
    </Router>
);

我怀疑是这个问题:

component: React.cloneElement(child.props.component, {

child.props.component 不是渲染组件(如 <Dashbard />),它是 组件 class(如 Dashboard ). cloneElement 需要渲染的组件。而且你不能显式地将 props extra 传递到 组件 class.

有几种方法可以实现您正在做的事情。克隆路线对我来说 "tricky"。

选项 1:具有 updateTitle 逻辑的高阶组件

我个人会尝试制作一个高阶组件(一个函数需要一个组件class和returns一个组件class)添加此道具,并导出包裹在其中的 Dashboard/Login 组件。稍微冗长但不那么棘手:

HOC 文件:

const WithExtraProp = (ContentComponent) => {
    return WithPropWrapper extends Component {

        updateMenuTitle() {...}

        render() {
            // Add extra props here
            return <ContentComponent {...this.props} functions={{ updateMenuTitle: this.updateMenuTitle }}/>
        }

    }
}
export default WithExtraProp;

并在仪表板中

class Dashboard extends Component {...}
export default WithExtraProp(Dashboard);

用这个方法,你也可以做到(虽然我不太喜欢)

<AppLayout>
    <Route component={WithExtraProp(Dashboard)} path="/" key="/" />
    <Route component={WithExtraProp(Login)} path="/login" key="/login" />
</AppLayout>

选项 2:使用 <Route render={} /> 而不是 component={} 来添加道具

如果你想保留你当前的设置,你隐式/"magically" 添加道具,我看不出不使用路由的方法render 道具而不是 component。这样就可以正常渲染组件和传入props了

你可以保持不变:

<Route component={Dashboard} path="/" key="/" />

还有这样的东西:

const childrenWithExtraProp = React.Children.map(this.props.children, (child) => {
    // Clone the <Route />, remove the `component` prop, add `render` prop
    return React.cloneElement(child, {

        // Remove the `component` prop from the Route, since you can't use
        // `component` and `render` on a Route together. This way component
        // just becomes an API for this withExtraPropClass to use to find
        // the right component
        component: null,

        render = () => {
            const ChildComponent = child.props.component;
            const functions = {
                updateMenuTitle: this.updateTitle //function
            };
            return <ChildComponent functions={functions} />;
        }
        })
    });
});

选项 3:使 AppLayout 成为高阶组件

这与选项 1 相同,最后您这样做:

//imports omited
export default (
    <Route component={Dashboard} path="/" key="/" />
    <Route component={Login} path="/login" key="/login" />
);

AppLayout是添加prop的高阶组件

const AppLayout = (ContentComponent) => {
    return WithPropWrapper extends Component {

        updateMenuTitle() {...}

        render() {
            // Add extra props here
            return (
                <MyLayoutStuff>
                    <ContentComponent {...this.props} functions={{ updateMenuTitle: this.updateMenuTitle }}/>
                </MyLayoutStuff>
            );
        }

    }
}
export default AppLayout;

并导出包裹在布局中的组件:

class Dashboard extends Component {...}
export default AppLayout(Dashboard);

其他想法

我个人一直在使用最接近#3 的东西。具体来说,我有一个像 dashboard/Dashboard.js 这样的文件,在同一个文件夹中,dashboard/index.js,它导出包裹在布局中的仪表板。您可以在 this React boilerplate Github folder.

查看该模式的示例

还有其他选择。您可以制作一个不必处理克隆的 <AppRoutes children=[{component: Dashboard, path="/"}, {...}] /> 组件。如果您需要在 <render> 之上处理子组件,我通常更喜欢将它们作为数组传递,而不是子组件并映射到它们。