在反应路由器v4中添加道具到包装路径的组件

时间:2017-12-28 16:39:20

标签: reactjs react-router 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)

在上一个项目我工作过,我们在路由中使用路由,但在React-Router v4这是不允许的。

编辑:之前是这样的:

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

1 个答案:

答案 0 :(得分:1)

我怀疑这是问题所在:

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

child.props.component不是呈现的组件(如<Dashbard />),它是组件类(如Dashboard)。 cloneElement期望渲染的组件。并且您无法明确地将道具额外传递到组件类

有几种方法可以实现您的目标。克隆一条路线对我来说感觉“棘手”。

选项1:具有updateTitle逻辑

的高阶组件

我个人会尝试制作一个更高阶的组件(一个带有组件类并返回组件类的函数),它会添加这个prop,并导出包含在其中的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={}添加道具

如果你想保留你当前的设置,你暗中/“神奇地”添加道具,我没有办法在不使用Route的render道具的情况下做到这一点component。这样你就可以渲染组件并正常传递道具。

您可以保持相同:

<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是一个更高阶的组件,用于添加道具。

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>之前对孩子进行处理,我通常更喜欢将它们作为数组而不是子组件传递给它们并映射它们。