React Router v4根级别无法访问的嵌套匹配参数

nik*_*ohn 10 javascript routing nested reactjs react-router

测试用例

https://codesandbox.io/s/rr00y9w2wm

重现步骤

要么

预期的行为

  • match.params.topicId两个父主题组件应该是相同的,应该与match.params.topicIdTopic组件中访问时相同

实际行为

  • match.params.topicIdTopic组件中访问时未定义
  • match.params.topicId主题组件中访问时正在呈现

我从这个封闭的问题中了解到,这不一定是一个错误.

此要求在想要在工厂Web应用程序中创建运行的用户中非常常见,其中Topics父级别的组件需要访问match.params.paramId,其中paramId是与嵌套(子)组件匹配的URL参数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>
);
Run Code Online (Sandbox Code Playgroud)

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

更痛苦的是,导航菜单不需要与组件树具有一对一的关系.有时在菜单(比如说根级别的项目Topics,Sections等等)可能对应于一个嵌套的结构(Sections只有一个主题下显现,/topics/:topicId/sections/:sectionId但它有自己的标准化列表,提供给用户的标题下在导航酒吧).因此,当被点击,应当强调,而不是两个章节 主题.

随着sectionIdsections路径不可用导航栏组分,它是在应用程序的根目录下,有必要写这样的黑客攻击这样一个司空见惯的使用情况.

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

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

其他黑客/部分解决方案/相关问题:

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

  2. React Router v4全局与嵌套路由子项不匹配

TIA!

Mat*_*tta 7

尝试使用查询参数?以允许父级和子级访问当前选定的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

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>
  );
};
Run Code Online (Sandbox Code Playgroud)

另一种方法是创建一个HOC存储params的state子节点,并且state当params更改时,子节点会更新父节点.

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

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

✔使用匹配路由URL处理

✔不使用 this.props.location.pathname

✔使用lodash对象进行对象比较

✔不涉及hacky方法

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>
  );
}
Run Code Online (Sandbox Code Playgroud)

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>
);
Run Code Online (Sandbox Code Playgroud)


Shu*_*tri 7

React-router不会为您提供任何匹配的子级Route的匹配参数,而是根据当前匹配为您提供参数。因此,如果您的路线设置如下

<Route path='/topic' component={Topics} />
Run Code Online (Sandbox Code Playgroud)

Topics组件中,您有一条类似的路线

<Route path=`${match.url}/:topicId` component={Topic} />
Run Code Online (Sandbox Code Playgroud)

现在,如果您的网址/topic/topic1与内部路线匹配,但对于主题组件,则匹配的路线仍然是,/topic因此没有参数,这很有意义。

如果要获取主题组件中匹配的子路由的参数,则需要使用matchPathReact-router提供的实用程序,并针对要获取其参数的子路由进行测试

import { matchPath } from 'react-router'

render(){
    const {users, flags, location } = this.props;
    const match = matchPath(location.pathname, {
       path: '/topic/:topicId',
       exact: true,
       strict: false
    })
    if(match) {
        console.log(match.params.topicId);
    }
    return (
        <div>
            <Route exact path="/topic/:topicId" component={Topic} />
        </div>
    )
}
Run Code Online (Sandbox Code Playgroud)

编辑:

一种获取所有级别的所有参数的方法是利用上下文,并在上下文提供者中匹配时更新它们。

您需要围绕Route创建一个包装,以使其正常工作,一个典型的示例如下所示:

RouteWrapper.jsx

import React from "react";
import _ from "lodash";
import { matchPath } from "react-router-dom";
import { ParamContext } from "./ParamsContext";
import { withRouter, Route } from "react-router-dom";

class CustomRoute extends React.Component {
  getMatchParams = props => {
    const { location, path, exact, strict } = props || this.props;
    const match = matchPath(location.pathname, {
      path,
      exact,
      strict
    });
    if (match) {
      console.log(match.params);
      return match.params;
    }
    return {};
  };
  componentDidMount() {
    const { updateParams } = this.props;
    updateParams(this.getMatchParams());
  }
  componentDidUpdate(prevProps) {
    const { updateParams, match } = this.props;
    const currentParams = this.getMatchParams();
    const prevParams = this.getMatchParams(prevProps);
    if (!_.isEqual(currentParams, prevParams)) {
      updateParams(match.params);
    }
  }

  componentWillUnmount() {
    const { updateParams } = this.props;
    const matchParams = this.getMatchParams();
    Object.keys(matchParams).forEach(k => (matchParams[k] = undefined));
    updateParams(matchParams);
  }
  render() {
    return <Route {...this.props} />;
  }
}

const RouteWithRouter = withRouter(CustomRoute);

export default props => (
  <ParamContext.Consumer>
    {({ updateParams }) => {
      return <RouteWithRouter updateParams={updateParams} {...props} />;
    }}
  </ParamContext.Consumer>
);
Run Code Online (Sandbox Code Playgroud)

ParamsProvider.jsx

import React from "react";
import { ParamContext } from "./ParamsContext";
export default class ParamsProvider extends React.Component {
  state = {
    allParams: {}
  };
  updateParams = params => {
    console.log({ params: JSON.stringify(params) });
    this.setState(prevProps => ({
      allParams: {
        ...prevProps.allParams,
        ...params
      }
    }));
  };
  render() {
    return (
      <ParamContext.Provider
        value={{
          allParams: this.state.allParams,
          updateParams: this.updateParams
        }}
      >
        {this.props.children}
      </ParamContext.Provider>
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

Index.js

ReactDOM.render(
  <BrowserRouter>
    <ParamsProvider>
      <App />
    </ParamsProvider>
  </BrowserRouter>,
  document.getElementById("root")
);
Run Code Online (Sandbox Code Playgroud)

工作演示