React Router v4 嵌套匹配参数无法在根级别访问 [英] React Router v4 Nested match params not accessible at root level
问题描述
测试用例
https://codesandbox.io/s/rr00y9w2wm
重现步骤
- 点击主题
- 点击使用 React 渲染
或
预期行为
match.params.topicId
应与父 Topics 组件相同,访问时应与match.params.topicId
相同主题组件内
实际行为
match.params.topicId
在 Topic 组件中访问时为 undefinedmatch.params.topicId
在 Topics 组件中访问时是rendering
我从 这个已关闭的问题了解到这不一定是错误.
此要求在想要在 Mill Web 应用程序中创建运行的用户中非常普遍,其中父级别的组件 Topics
需要访问 match.params.paramId 其中 paramId
是与嵌套(子)组件 Topic
匹配的 URL 参数:
const Topic = ({ match }) =>(<h2>来自主题组件的主题 ID 参数</h2><h3>{match.params.topicId}</h3></div>);const Topics = ({ match }) =>(<h2>主题</h2><h3>{match.params.topicId ||未定义"}</h3><路由路径={`${match.url}/:topicId`}组件={Topic}/>...</div>);在一般意义上,Topics
可以是 Drawer 或 Navigation Menu 组件,而 Topic
可以是任何子组件,就像在我正在开发的应用程序中一样.子组件有它自己的 :topicId
参数,它有它自己的(比方说) <Route path="sections/:sectionId" component={Section}/>
路由/组件.
更痛苦的是,导航菜单不必与组件树建立一对一的关系.有时,菜单根级别的项目(例如 Topics
、Sections
等)可能对应于 嵌套 结构(Sections
仅在主题 /topics/:topicId/sections/:sectionId
下呈现,尽管它有自己的规范化列表,用户可以在标题 Sections下使用strong> 在导航栏中).因此,当 Sections 被点击时,it 应该被突出显示,而不是 Sections 和 Topics.
由于 sectionId
或 sections
路径对于位于应用程序根级别的导航栏组件不可用,因此有必要编写 像这样的黑客攻击这样一个常见的用例.
我根本不是 React Router 方面的专家,所以如果有人可以为这个用例冒险一个适当的优雅解决方案,我会认为这是一项富有成果的努力.优雅,我的意思是
- 使用
match
而不是 history.location.pathname
- 不涉及手动解析
window.location.xxx
等 hacky 方法 - 不使用
this.props.location.pathname
- 不使用
path-to-regexp
等第三方库- 不使用查询参数
其他技巧/部分解决方案/相关问题:
TIA!
解决方案 尝试利用查询参数?
让父子访问当前选中的topic
.不幸的是,您将需要使用模块 qs 因为 react-router-dom
不会自动解析查询(react-router v3 会).
工作示例:https://codesandbox.io/s/my1ljx40r9
URL 的结构类似于串联字符串:
topic?topic=props-v-state
然后您将使用 &
添加到查询中:
/topics/topic?topic=optimization&category=pure-components&subcategory=shouldComponentUpdate
✔ 使用匹配进行路由 URL 处理
✔ 不使用 this.props.location.pathname
(使用 this.props.location.search
)
✔ 使用 qs
解析 location.search
✔ 不涉及骇人听闻的方法
Topics.js
从react"导入反应;从react-router-dom"导入{链接,路由};从qs"导入 qs;从./Topic"导入主题;导出默认({匹配,位置})=>{常量 { 主题 } = qs.parse(location.search, {忽略查询前缀:真});返回 (<h2>主题</h2><ul><li><链接到={`${match.url}/topic?topic=rendering`}>使用 React 渲染</链接></li><li><链接到={`${match.url}/topic?topic=components`}>组件</Link></li><li><链接到={`${match.url}/topic?topic=props-v-state`}>道具诉状态</链接></li></ul><h2>来自 Topic<strong>s</strong> 的主题 ID 参数组件</h2><h3>{主题&&话题}</h3><路线路径={`${match.url}/:topicId`}渲染={道具=><主题 {...props} 主题={主题}/>}/><路线精确的路径={match.url}渲染={() =><h3>请选择一个主题.</h3>}/></div>);};<小时>
另一种方法是创建一个 HOC
将参数存储到 state
并且子级在其参数发生更改时更新父级的 state
.
URL 的结构类似于文件夹树:/topics/rendering/optimization/pure-components/shouldComponentUpdate
工作示例:https://codesandbox.io/s/9joknpm9jy
✔ 使用匹配进行路由 URL 处理
✔ 不使用 this.props.location.pathname
✔ 使用 lodash 进行对象间比较
✔ 不涉及骇人听闻的方法
Topics.js
从lodash/map"导入地图;从反应"导入反应,{片段,组件};从./NestedRoutes"导入 NestedRoutes;从./Links"导入链接;从./createPath"导入createPath;导出默认类主题扩展组件 {状态 = {参数:",路径:[]};componentDidMount = () =>{常量 urlPaths = [this.props.match.url,":topicId",:子类别",:物品",:生命周期"];this.setState({ 路径: createPath(urlPaths) });};handleUrlChange = 参数 =>this.setState({ 参数 });显示参数 = 参数 =>!参数?空值: map(params, name => <Fragment key={name}>{name} </Fragment>);渲染 = () =>(<h2>主题</h2><链接匹配={this.props.match}/><h2>来自 Topic<strong>s</strong> 的主题 ID 参数组件</h2><h3>{this.state.params &&this.showParams(this.state.params)}</h3><嵌套路由handleUrlChange={this.handleUrlChange}匹配={this.props.match}路径={this.state.paths}showParams={this.showParams}/></div>);}NestedRoutes.js
从lodash/map"导入地图;从反应"导入反应,{片段};从react-router-dom"导入 { Route };从./Topic"导入主题;导出默认值 ({ handleUrlChange, match, paths, showParams }) =>(<片段>{地图(路径,路径=>(<路线精确的键={路径}路径={路径}渲染={道具=>(<主题{...道具}handleUrlChange={handleUrlChange}显示参数={显示参数}/>)}/>))}<路线精确的路径={match.url}渲染={() =><h3>请选择一个主题.</h3>}/></片段>);
Test Case
https://codesandbox.io/s/rr00y9w2wm
Steps to reproduce
- Click on Topics
- Click on Rendering with React
OR
Expected Behavior
match.params.topicId
should be identical from both the parent Topics component should be the same as match.params.topicId
when accessed within the Topic component
Actual Behavior
match.params.topicId
when accessed within the Topic component is undefined
match.params.topicId
when accessed within the Topics component is rendering
I understand from this closed issue that this is not necessarily a bug.
This requirement is super common among users who want to create a run in the mill web application where a component Topics
at a parent level needs to access the match.params.paramId where paramId
is a URL param that matches a nested (child) component Topic
:
const Topic = ({ match }) => (
<div>
<h2>Topic ID param from Topic Components</h2>
<h3>{match.params.topicId}</h3>
</div>
);
const Topics = ({ match }) => (
<div>
<h2>Topics</h2>
<h3>{match.params.topicId || "undefined"}</h3>
<Route path={`${match.url}/:topicId`} component={Topic} />
...
</div>
);
In a generic sense, Topics
could be a Drawer or Navigation Menu component and Topic
could be any child component, like it is in the application I'm developing. The child component has it's own :topicId
param which has it's own (let's say) <Route path="sections/:sectionId" component={Section} />
Route/Component.
Even more painful, the Navigation Menu needn't have a one-to-one relationship with the component tree. Sometimes the items at the root level of the menu (say Topics
, Sections
etc.) might correspond to a nested structure (Sections
is only rendered under a Topic, /topics/:topicId/sections/:sectionId
though it has its own normalized list that is available to the user under the title Sections in the Navigation Bar).
Therefore, when Sections is clicked, it should be highlighted, and not both Sections and Topics.
With the sectionId
or sections
path unavailable to the Navigation Bar component which is at the Root level of the application, it becomes necessary to write hacks like this for such a commonplace use case.
I am not an expert at all at React Router, so if anyone can venture a proper elegant solution to this use case, I would consider this to be a fruitful endeavor. And by elegant, I mean
- Uses
match
and not history.location.pathname
- Does not involve hacky approaches like manually parsing the
window.location.xxx
- Doesn't use
this.props.location.pathname
- Does not use third party libraries like
path-to-regexp
- Does not use query params
Other hacks/partial solutions/related questions:
TIA!
解决方案 Try utilizing query parameters ?
to allow the parent and child to access the current selected topic
. Unfortunately, you will need to use the module qs because react-router-dom
doesn't automatically parse queries (react-router v3 does).
Working example: https://codesandbox.io/s/my1ljx40r9
URL is structured like a concatenated string:
topic?topic=props-v-state
Then you would add to the query with &
:
/topics/topic?topic=optimization&category=pure-components&subcategory=shouldComponentUpdate
✔ Uses match for Route URL handling
✔ Doesn't use this.props.location.pathname
(uses this.props.location.search
)
✔ Uses qs
to parse location.search
✔ Does not involve hacky approaches
Topics.js
import React from "react";
import { Link, Route } from "react-router-dom";
import qs from "qs";
import Topic from "./Topic";
export default ({ match, location }) => {
const { topic } = qs.parse(location.search, {
ignoreQueryPrefix: true
});
return (
<div>
<h2>Topics</h2>
<ul>
<li>
<Link to={`${match.url}/topic?topic=rendering`}>
Rendering with React
</Link>
</li>
<li>
<Link to={`${match.url}/topic?topic=components`}>Components</Link>
</li>
<li>
<Link to={`${match.url}/topic?topic=props-v-state`}>
Props v. State
</Link>
</li>
</ul>
<h2>
Topic ID param from Topic<strong>s</strong> Components
</h2>
<h3>{topic && topic}</h3>
<Route
path={`${match.url}/:topicId`}
render={props => <Topic {...props} topic={topic} />}
/>
<Route
exact
path={match.url}
render={() => <h3>Please select a topic.</h3>}
/>
</div>
);
};
Another approach would be to create a HOC
that stores params to state
and children update the parent's state
when its params have changed.
URL is structured like a folder tree: /topics/rendering/optimization/pure-components/shouldComponentUpdate
Working example: https://codesandbox.io/s/9joknpm9jy
✔ Uses match for Route URL handling
✔ Doesn't use this.props.location.pathname
✔ Uses lodash for object to object comparison
✔ Does not involve hacky approaches
Topics.js
import map from "lodash/map";
import React, { Fragment, Component } from "react";
import NestedRoutes from "./NestedRoutes";
import Links from "./Links";
import createPath from "./createPath";
export default class Topics extends Component {
state = {
params: "",
paths: []
};
componentDidMount = () => {
const urlPaths = [
this.props.match.url,
":topicId",
":subcategory",
":item",
":lifecycles"
];
this.setState({ paths: createPath(urlPaths) });
};
handleUrlChange = params => this.setState({ params });
showParams = params =>
!params
? null
: map(params, name => <Fragment key={name}>{name} </Fragment>);
render = () => (
<div>
<h2>Topics</h2>
<Links match={this.props.match} />
<h2>
Topic ID param from Topic<strong>s</strong> Components
</h2>
<h3>{this.state.params && this.showParams(this.state.params)}</h3>
<NestedRoutes
handleUrlChange={this.handleUrlChange}
match={this.props.match}
paths={this.state.paths}
showParams={this.showParams}
/>
</div>
);
}
NestedRoutes.js
import map from "lodash/map";
import React, { Fragment } from "react";
import { Route } from "react-router-dom";
import Topic from "./Topic";
export default ({ handleUrlChange, match, paths, showParams }) => (
<Fragment>
{map(paths, path => (
<Route
exact
key={path}
path={path}
render={props => (
<Topic
{...props}
handleUrlChange={handleUrlChange}
showParams={showParams}
/>
)}
/>
))}
<Route
exact
path={match.url}
render={() => <h3>Please select a topic.</h3>}
/>
</Fragment>
);
这篇关于React Router v4 嵌套匹配参数无法在根级别访问的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!
查看全文