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

时间:2018-10-06 22:53:11

标签: javascript reactjs routing nested react-router

测试用例

https://codesandbox.io/s/rr00y9w2wm

复制步骤

OR

预期行为

    主题组件中访问时,
  • match.params.topicId应该与父主题组件相同,并且应该与match.params.topicId相同。

实际行为

    主题组件中访问
  • match.params.topicId未定义
  • 主题组件中访问
  • match.params.topicId时,将呈现

我从this closed issue了解到,这不一定是错误。

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

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>
);

从一般意义上讲,Topics可以是抽屉或导航菜单组件,Topic可以是任何子组件,就像我正在开发的应用程序中一样。子组件具有其自己的:topicId参数,而该参数也具有其自身(例如)<Route path="sections/:sectionId" component={Section} />路线/组件。

更痛苦的是,导航菜单不必与组件树具有一对一的关系。有时,菜单根级的项目(例如TopicsSections等)可能对应于嵌套结构(Sections仅在主题/topics/:topicId/sections/:sectionId尽管有自己的规范化列表,但导航栏中标题为 Sections 的用户可以使用。 因此,当点击 Sections 时,应突出显示 ,而不同时选中 Sections Topics < / strong>。

在应用程序的根目录级别的导航栏组件上没有sectionIdsections路径的情况下,有必要为这种常见用例编写hacks like this

我根本不是React Router的专家,因此,如果任何人都可以对此用例提出合适的优雅解决方案,我将认为这是富有成果的努力。优雅,我的意思是

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

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

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

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

TIA!

5 个答案:

答案 0 :(得分:6)

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

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>
  );
};

另一种方法是创建一个HOC来将参数存储到state,并且子级的state的参数更改时,子级会更新其父级的/topics/rendering/optimization/pure-components/shouldComponentUpdate

URL的结构类似于文件夹树:this.props.location.pathname

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

✔使用匹配项进行路由URL处理

✔不使用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> ); }

✔使用lodash进行对象之间的比较

✔不涉及黑客手段

Topics.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>
);

NestedRoutes.js

document.getElementById('draw').addEventListener('click', function(){

document.getElementById('here').innerHTML = 
'<svg height="100" width="100">' + 
  '<circle cx="50" cy="50" r="40" stroke="black" stroke-width="1" fill="rgba(130,130,130,0.6)">' + 
'</svg>';

});

答案 1 :(得分:4)

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

<Route path='/topic' component={Topics} />

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

<Route path=`${match.url}/:topicId` component={Topic} />

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

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

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>
    )
}

编辑:

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

您需要围绕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>
);

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>
    );
  }
}

Index.js

ReactDOM.render(
  <BrowserRouter>
    <ParamsProvider>
      <App />
    </ParamsProvider>
  </BrowserRouter>,
  document.getElementById("root")
);

Working DEMO

答案 2 :(得分:0)

尝试执行以下操作:

<Switch>
  <Route path="/auth/login/:token" render={props => <Login  {...this.props} {...props}/>}/>
  <Route path="/auth/login" component={Login}/>

首先是带有参数的路由,然后是没有参数的链接。 在我的Login组件内部,我将这行代码console.log(props.match.params.token);进行了测试并为我工作。

答案 3 :(得分:0)

如果您有一组已知的子路线,则可以使用以下方式:


W1 = init_weights([100, 7*7*512])
W2 = init_weights([3, 3, 512, 256])
W3 = init_weights([3, 3, 256, 128])
W4 = init_weights([3, 3, 128, 1])

在上面的示例中,Parent将获得ExpectedTag,expectedEvent作为匹配参数,并且与子组件没有冲突,Child组件将获得activeTag,activeEvent,activeIndex作为参数。也可以使用相同名称的params,我也尝试过使用。

答案 4 :(得分:0)

如果你碰巧使用 React.FC,有一个钩子 useRouteMatch
例如,父组件路由:

<div className="office-wrapper">
  <div className="some-parent-stuff">
    ...
  </div>
  <div className="child-routes-wrapper">
    <Switch>
      <Route exact path={`/office`} component={List} />
      <Route exact path={`/office/:id`} component={Alter} />
    </Switch>
  </div>
</div>

在您的子组件中:

...
import { useRouteMatch } from "react-router-dom"
...
export const Alter = (props) => {
  const match = useRouteMatch()
  const officeId = +match.params.id
  //... rest function code
}