Вложенные маршруты сопоставления с React Router v4 и MatchWithFade

Этот вопрос является продолжением этого:

Проблемы с React Router v4 и MatchWithFade

У меня есть еще один (потенциально глупый) вопрос об использовании MatchWithFade и React Router v4. То, что я хотел бы сделать, это иметь вложенные маршруты, поэтому компонент верхнего уровня может иметь это:

<MatchWithFade pattern='/one' component={One} />

... и тогда One может иметь это:

<Match pattern='/one/one' Component={OneOne} />

Это не кажется мне необычным паттерном (хотя, возможно, это так). В любом случае поведение, которое я наблюдаю, заключается в том, что, используя приведенный выше пример, если я загружаю OneOne, он монтируется, а затем немедленно вызывается componentWillUnmount. Если бы я угадал, я бы сказал, что TransitionMotion отслеживает (возможно, скрытый) экземпляр OneOne, и после завершения перехода он размонтирует этот скрытый компонент. Что касается основного пользовательского интерфейса, отображается OneOne. Однако, если componentWillUnmount выполняет какую-либо очистку (например, удаляет что-то из Redux), то, конечно же, это действие запускается, и все данные, связанные с OneOne, удаляются.

Вот полный пример, иллюстрирующий проблему:

import React, { Component } from 'react';
import BrowserRouter from 'react-router/BrowserRouter'

import { TransitionMotion, spring } from 'react-motion'
import Match from 'react-router/Match'
import Link from 'react-router/Link';

const styles = {
  fill: { position: 'absolute', top: 0, left: 0 }
};

const MatchWithFade = ({ component:Component, ...rest }) => {
  const willLeave = () => ({ zIndex: 1, opacity: spring(0) })

  return (
    <Match {...rest} children={({ matched, ...props }) => {
      return (
        <TransitionMotion
          willLeave={willLeave}
          styles={matched ? [ {
            key: props.location.pathname,
            style: { opacity: 1 },
            data: props
          } ] : []}
        >
          {interpolatedStyles => {
            return (
              <div>
                {interpolatedStyles.map(config => (
                  <div
                    key={config.key}
                    style={{...styles.fill, ...config.style }}>
                    <Component {...config.data}/>
                  </div>
                ))}
              </div>
            )
          }}
        </TransitionMotion>
      )
    }}/>
  )
}

const TwoOne = () => {
  return (
    <div>Two One</div>
  )
}


class TwoTwo extends Component {
  componentWillUnmount() {
    console.log("TwoTwo will unmount")
  }

  render () {
    return (
      <div>Two Two</div>
    )
  }
}


const TwoHome = () => {
  return (
    <div>Two Home</div>
  )
}


class One extends Component {
  componentWillUnmount () {
    console.log("ONE UNMOUNTING")
  }
  render () {
    return (
      <div style={{ width: 300, border: '1px solid black', backgroundColor: 'orange', minHeight: 200}}>
        One one one one one one one one one one<br />
        One one one one one one one one one one<br />
      </div>
    )
  }
}

const Two = () => {
  return (
    <div style={{ width: 300, border: '1px solid black', backgroundColor: 'yellow', minHeight: 200}}>
      <Match pattern='/two/one' component={TwoOne} />
      <Match pattern='/two/two' component={TwoTwo} />
      <Match pattern='/two(/)?' exactly={true} component={TwoHome} />
    </div>
  )
}


class App extends Component {

  render () {
    return (
        <BrowserRouter>
          <div style={{padding: 12}}>
            <div style={{marginBottom: 12}}>
              <Link to='/one'>One</Link> || <Link to='/two'>Two</Link>
              || <Link to='/two/one'>Two One</Link>
              || <Link to='/two/two'>Two Two</Link>
            </div>
            <div style={{position: 'relative'}}>
              <MatchWithFade pattern='/one' component={One} />
              <MatchWithFade pattern='/two' component={Two} />
            </div>
          </div>
        </BrowserRouter>
    )
  }
}

export default App;

Если вы загрузите это и откроете консоль, переключитесь между ссылками One и Two. Вы увидите, как в пользовательском интерфейсе произойдет перекрестное затухание, и вы увидите «ONE UNMOUNTING» в консоли, когда переход от One к Two завершится. Так что это правильно.

Теперь щелкните между Two One и Two Two. В этом случае, когда вы нажмете Two One, вы сразу увидите в консоли сообщение «TwoTwo размонтируется», что хорошо. Однако, если вы нажмете Two Two, вы увидите, что "TwoTwo размонтируется" примерно через секунду, что, как я понимаю, является количеством времени, которое требуется родительскому MatchWithFade для выполнения.

Так что я не уверен, что здесь происходит. Мой код просто взломан? Я делаю что-то, что RRv4 не поддерживает? Я обнаружил ошибку?

Любая помощь / руководство приветствуется!


person hairbo    schedule 29.12.2016    source источник


Ответы (1)


Ваша проблема заключается в том, что вы используете props.location.pathname в качестве ключа. Это всегда должно быть одинаковым для компонента, но то, как вы его написали, меняется каждый раз, когда вы перемещаетесь. Попробуйте изменить это:

const styles = {
  fill: { position: 'absolute', top: 0, left: 0 }
};

to:

const styles = {
  fill: { position: 'relative', top: 0, left: 0 }
};

и вы увидите, что рендерите два экземпляра <Two> (по одному для каждой клавиши).

Если бы вы использовали константу key, такую ​​как rest.pattern (шаблон, связанный с этим <Match>), ваша проблема исчезла бы.

styles={matched ? [ {
  key: rest.pattern,
  style: { opacity: 1 },
  data: props
} ] : []}
person Paul S    schedule 16.01.2017
comment
Ах! Интересно! Код MatchWithFade был дословно взят из документации React-Router, поэтому я предположил, что он именно такой, каким и должен быть. И теперь я вижу разницу между props.location.pathname и rest.pattern. rest.pattern — это часть пути, которая соответствует текущему компоненту Match, а props.location.pathname — это полный путь, что означает да, ключ меняется, и поэтому загружаются несколько экземпляров. Таким образом, это, вероятно, означает, что документы RR должны быть обновлены для использования rest.pattern, а не props.location.pathname, чтобы другие не столкнулись с этой проблемой. - person hairbo; 17.01.2017
comment
Об этом может быть немного сложно рассуждать, но я думаю об этом так: 1) Каждый <TransitionMotion> уникален. Если у вас их два, то это две разные анимации. 2) Элемент создается для каждого ключа в <TransitionMotion>. В примере с документами новый <HSL> должен отображаться для каждого местоположения, поэтому использование props.location.pathname хорошо работает в качестве ключа. В вашем коде вам нужен только один <Two>, поэтому вам нужен ключ, который не меняется. - person Paul S; 17.01.2017
comment
Да, это имеет смысл. И да, мне немного сложно все это уложить в голове, но это начинает просачиваться. Чтобы было ясно, в их примере rest.pattern будет работать как ключ, так же как и props.location.pathname`, правильно ? И у него есть дополнительное преимущество, заключающееся в том, что он немного более защищен от идиотов, поскольку кто-то (например, о, я) может смахнуть этот код и изменить его, не беспокоясь об изменении этого ключа. - person hairbo; 17.01.2017
comment
Нет, не будет. В примере есть только один шаблон (/:h/:s/:l), поэтому изменение местоположения не создаст новый элемент, как это делает привязка ключа к props.location.pathname. В вашем коде у вас в основном есть две анимации: первая — это вход и выход из <One>, а вторая — вход и выход из <Two>. На самом деле они должны быть частью одного и того же <TransitionMotion>. В текущей альфа-версии это проблема, но я считаю, что в следующей бета-версии будет проще использовать компонент <Switch>. (Я еще не пробовал, но я совершенно уверен.) - person Paul S; 17.01.2017
comment
О, это потому, что у шаблона здесь есть переменные? (например, :h и т. д.) Я предполагаю, что если бы они были жестко закодированы, как в моем примере, то rest.pattern работал бы. Я очень хочу попробовать предстоящую бета-версию. Я понимаю, что произошли существенные изменения. Есть идеи, когда это будет выпущено? - person hairbo; 17.01.2017
comment
Да. Пути /10/20/30, /0/0/0 и /100/750/50 должны каждый создавать новый <HSL>, что сделает путь ключом. Если бы ключ был шаблоном, то изменение пути не создавало бы новый элемент <HSL>, а вместо этого заменяло бы свойства существующего (как обычно делает React). - person Paul S; 17.01.2017