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

问题描述

测试用例

https://codesandbox.io/s/rr00y9w2wm

重现步骤

预期行为

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

实际行为

  • match.params.topicIdTopic 组件中访问时未定义
  • match.params.topicIdTopics 组件中访问时是呈现

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

此要求在想要在工厂 Web 应用程序中创建运行的用户中非常普遍,其中父级的组件 Topics 需要访问 ma​​tch.params.paramId 其中 paramId 是与嵌套(子)组件 Topic 匹配的 URL 参数:

const Topic = ({ match }) =>(<div><h2>来自主题组件的主题 ID 参数</h2><h3>{match.params.topicId}</h3>

);const 主题 = ({ 匹配 }) =>(<div><h2>主题</h2><h3>{match.params.topicId ||未定义"}<路由路径={`${match.url}/:topicId`} component={Topic}/>...

);

在一般意义上,Topics 可以是抽屉或导航菜单组件,Topic 可以是任何子组件,就像我正在开发的应用程序一样.子组件有它自己的 :topicId 参数,它有它自己的(假设)<Route path="sections/:sectionId" component={Section}/>路由/组件.

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

如果 sectionIdsections 路径对位于应用程序根级别的导航栏组件不可用,则需要编写 像这样的 hacks 用于这种常见的用例.

我在 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

✔ 不涉及hacky方法

Topics.js

从react"导入React;从react-router-dom"导入{链接,路由};从qs"导入 qs;从./Topic"导入主题;导出默认值({匹配,位置})=>{const { 主题 } = qs.parse(location.search, {忽略查询前缀:true});返回 (<div><h2>主题</h2><ul><li><链接到={`${match.url}/topic?topic=rendering`}>使用 React 渲染</链接><li><Link to={`${match.url}/topic?topic=components`}>组件</Link><li><链接到={`${match.url}/topic?topic=props-v-state`}>道具诉国家</链接><h2>来自 Topics 的主题 ID 参数组件<h3>{主题&&主题}<路线path={`${match.url}/:topicId`}渲染={道具=><主题{...道具}主题={主题}/>}/><路线精确的路径={匹配.url}渲染={() =><h3>请选择一个主题.</h3>}/>

);};

另一种方法是创建一个 HOC 将参数存储到 state 并且当它的参数改变时孩子更新父的 state.

URL 的结构类似于文件夹树:/topics/rendering/optimization/pure-components/shouldComponentUpdate

工作示例:https://codesandbox.io/s/9joknpm9jy

✔ 使用匹配来处理路由 URL

✔ 不使用 this.props.location.pathname

✔ 使用 lodash 进行对象间比较

✔ 不涉及hacky方法

Topics.js

从lodash/map"导入地图;从反应"导入反应,{片段,组件};从./NestedRoutes"导入 NestedRoutes;从./Links"导入链接;从./createPath"导入createPath;导出默认类主题扩展组件{状态 = {参数:"",路径:[]};componentDidMount = () =>{const urlPaths = [this.props.match.url,":topicId",":子类别",:物品",:生命周期"];this.setState({ 路径: createPath(urlPaths) });};handleUrlChange = params =>this.setState({ params });showParams = params =>!params?空值: map(params, name => );渲染 = () =>(<div><h2>主题</h2><链接匹配={this.props.match}/><h2>来自 Topics 的主题 ID 参数组件<h3>{this.state.params &&this.showParams(this.state.params)}<嵌套路由handleUrlChange={this.handleUrlChange}匹配={this.props.match}路径={this.state.paths}showParams={this.showParams}/>

);}

NestedRoutes.js

从lodash/map"导入地图;import React, { Fragment } from "react";从react-router-dom"导入{路由};从./Topic"导入主题;导出默认值({handleUrlChange,match,paths,showParams})=>(<片段>{地图(路径,路径=>(<路线精确的键={路径}路径={路径}渲染={道具=>(<话题{...道具}handleUrlChange={handleUrlChange}showParams={showParams}/>)}/>))}<路线精确的路径={匹配.url}渲染={() =><h3>请选择一个主题.</h3>}/></片段>);

Test Case

https://codesandbox.io/s/rr00y9w2wm

Steps to reproduce

OR

Expected Behavior

Actual Behavior

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

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 嵌套匹配参数在根级别无法访问的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-21 13:51