React Router v4 嵌套匹配参数无法在根级别访问 [英] React Router v4 Nested match params not accessible at root level

查看:30
本文介绍了React Router v4 嵌套匹配参数无法在根级别访问的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

测试用例

https://codesandbox.io/s/rr00y9w2wm

重现步骤

预期行为

  • match.params.topicId 应与父 Topics 组件相同,访问时应与 match.params.topicId 相同主题组件内

实际行为

  • match.params.topicIdTopic 组件中访问时为 undefined
  • match.params.topicIdTopics 组件中访问时是rendering

我从 这个已关闭的问题了解到这不一定是错误.

此要求在想要在 Mill Web 应用程序中创建运行的用户中非常普遍,其中父级别的组件 Topics 需要访问 ma​​tch.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}/>路由/组件.

更痛苦的是,导航菜单不必与组件树建立一对一的关系.有时,菜单根级别的项目(例如 TopicsSections 等)可能对应于 嵌套 结构(Sections 仅在主题 /topics/:topicId/sections/:sectionId 下呈现,尽管它有自己的规范化列表,用户可以在标题 Sections 在导航栏中).因此,当 Sections 被点击时,it 应该被突出显示,而不是 Sections Topics.

由于 sectionIdsections 路径对于位于应用程序根级别的导航栏组件不可用,因此有必要编写 像这样的黑客攻击这样一个常见的用例.

我根本不是 React Router 方面的专家,所以如果有人可以为这个用例冒险一个适当的优雅解决方案,我会认为这是一项富有成果的努力.优雅,我的意思是

  • 使用 match 而不是 history.location.pathname
  • 不涉及手动解析 window.location.xxx 等 hacky 方法
  • 不使用 this.props.location.pathname
  • 不使用 path-to-regexp
  • 等第三方库
  • 不使用查询参数

其他技巧/部分解决方案/相关问题:

  1. React Router v4 - 如何获取当前路由?

  2. React Router v4 全局不匹配到嵌套路由子节点

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

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:

  1. React Router v4 - How to get current route?

  2. React Router v4 global no match to nested route childs

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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆