React Router v4 Вложенные параметры совпадения недоступны на корневом уровне - PullRequest
0 голосов
/ 07 октября 2018

Контрольный пример

https://codesandbox.io/s/rr00y9w2wm

Шаги для воспроизведения

ИЛИ

Ожидаемое поведение

  • match.params.topicId должен совпадать с обоими родительскими Темы должны совпадать с match.params.topicId при доступе к компоненту Topic

ActualПоведение

  • match.params.topicId при обращении к компоненту Тема равно неопределено
  • match.params.topicId при обращении к темам Компонент рендеринг

Я понимаю из этого закрытого вопроса , что это не обязательно ошибка.

Это требованиеРаспространена среди пользователей, которые хотят создать запуск в веб-приложении mill, где компоненту 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>
);

В общем смысле, Topics может быть компонентом Drawer или Navigation Menu, а Topic может быть любым дочерним компонентом, напримерэто в приложении, которое я разрабатываю.Дочерний компонент имеет свой собственный параметр :topicId, который имеет свой собственный (скажем) <Route path="sections/:sectionId" component={Section} /> Маршрут / Компонент.

Еще более болезненным, меню навигации не должно иметь отношения один к одному сдерево компонентов.Иногда элементы в корневом уровне меню (скажем, Topics, Sections и т. Д.) Могут соответствовать вложенной структуре (Sections отображается только в теме, /topics/:topicId/sections/:sectionId, хотя иимеет собственный нормализованный список, который доступен пользователю под заголовком Разделы на панели навигации).Поэтому при нажатии Sections следует выделять it , а не оба Sections и Topics .

Поскольку путь sectionId или sections недоступен для компонента панели навигации, который находится на корневом уровне приложения, возникает необходимость написать таких хаков для такого обычного использования.дело.

Я вообще не являюсь экспертом в React Router, поэтому, если кто-нибудь сможет найти правильное элегантное решение для этого варианта использования, я считаю это плодотворным делом.И под элегантным я имею в виду

  • Использует match, а не history.location.pathname
  • Не включает в себя хакерские подходы, такие как ручной анализ window.location.xxx
  • Не работаетt использовать this.props.location.pathname
  • Не использовать сторонние библиотеки, такие как path-to-regexp
  • Не использовать параметры запроса

Другие хаки / частичные решения / связанные вопросы:

  1. React Router v4 - Как получить текущий маршрут?

  2. Глобальный React Router v4 не соответствуетвложенный маршрут childs

TIA!

Ответы [ 3 ]

0 голосов
/ 11 октября 2018

React-router не дает вам параметры совпадения ни одного из сопоставленных дочерних маршрутов, скорее, оно дает вам параметры, основанные на текущем совпадении.Поэтому, если у вас есть настройки маршрутов, например

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

, а в компоненте Topics у вас есть маршрут, например

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

Теперь, если ваш URL-адрес /topic/topic1 соответствует внутреннему маршрутуно для компонента Темы соответствующий Маршрут по-прежнему /topic и, следовательно, не содержит никаких параметров, что имеет смысл.

Если вы хотите получить параметры дочернего Маршрута, соответствующего в компоненте тем, выпотребуется использовать утилиту matchPath, предоставляемую React-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>
    )
}

EDIT:

Один метод для получения всехПараметр params на любом уровне - это использование контекста и обновление параметров в соответствии с тем, когда они совпадают в провайдере контекста.

Вам потребуется создать обертку вокруг 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")
);

РабочийDEMO

0 голосов
/ 19 февраля 2019

Попробуйте сделать что-то вроде этого:

<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); для тестирования и работал на меня.

0 голосов
/ 07 октября 2018

Попробуйте использовать параметры запроса ?, чтобы родитель и потомок могли получить доступ к текущему выбранному topic.К сожалению, вам нужно будет использовать модуль qs , поскольку react-router-dom не выполняет автоматический анализ запросов (реагирует маршрутизатор v3).

Рабочий пример: https://codesandbox.io/s/my1ljx40r9

URL структурирован как конкатенированная строка:

topic?topic=props-v-state

Тогда вы добавите к запросу с помощью &:

/topics/topic?topic=optimization&category=pure-components&subcategory=shouldComponentUpdate

match Использует соответствие для обработки 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 при изменении его параметров.

URL структурирован как дерево папок: /topics/rendering/optimization/pure-components/shouldComponentUpdate

Рабочий пример: https://codesandbox.io/s/9joknpm9jy

Match Использует соответствие для обработки 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>
  );
}

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>
);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...