在 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>
之上处理子组件,我通常更喜欢将它们作为数组传递,而不是子组件并映射到它们。
我有共享相同行为、布局等的路由。我想将道具从布局传递到路由中的那些组件(仪表板和登录)
我的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>
之上处理子组件,我通常更喜欢将它们作为数组传递,而不是子组件并映射到它们。