React Component Mounting дважды

Внутри небольшой части моего приложения React/Redux/ReactRouterV4 у меня есть следующая иерархия компонентов:

- Exhibit (Parent)
-- ExhibitOne
-- ExhibitTwo
-- ExhibitThree

В дочерних элементах Exhibit есть около 6 различных возможных маршрутов, которые также могут быть отображены. Не волнуйтесь, я объясню с кодом.

Вот мой компонент Parent Exhibit:

export class Exhibit extends Component {
  render() {
    const { match, backgroundImage } = this.props

    return (
      <div className="exhibit">
        <Header />
        <SecondaryHeader />

        <div className="journey"
          style={{
            color: 'white',
            backgroundImage: `url(${backgroundImage})`,
            backgroundSize: 'cover',
            backgroundRepeat: 'no-repeat',
            backgroundPosition: 'center-center'
          }}>

          <Switch>
            <Route path={`${match.url}/exhibit-one`} component={ExhibitOne} />
            <Route path={`${match.url}/exhibit-two`} component={ExhibitTwo} />
            <Route path={`${match.url}/exhibit-three`} component={ExhibitThree} />
            <Redirect to="/" />
          </Switch>
        </div>
      </div>
    )
  }
}

По сути, все, что он делает для своей работы, — это отображает один из подкомпонентов экспонатов и устанавливает фоновое изображение.

Вот один из подкомпонентов, ExhibitOne:

export default class ExhibitOne extends Component {
  constructor(props) {
    super(props)
  }

  render() {
    const { match } = this.props

    return (
      <div className="exhibit-one">
        <Switch>
          <Route path={`${match.url}/wall-one`} component={ExhibitHOC(WallOne)} />
          <Route path={`${match.url}/wall-two`} component={ExhibitHOC(WallTwo)} />
          <Route path={`${match.url}/wall-three`} component={ExhibitHOC(WallThree)} />
          <Route path={`${match.url}/wall-four`} component={ExhibitHOC(WallFour)} />
          <Route path={`${match.url}/wall-five`} component={ExhibitHOC(WallFive)} />
          <Route path={`${match.url}/wall-six`} component={ExhibitHOC(WallSix)} />
        </Switch>
      </div>
    )
  }
}

Чтобы сократить набор текста, я решил обернуть компоненты в компонент более высокого порядка, целью которого является отправка действия, которое установит правильное фоновое изображение в родительском компоненте Exhibit верхнего уровня.

Это компонент высшего порядка:

import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actions from '../../actions/wall-background-image'

export default function(ComposedComponent) {
  class ExhibitHoc extends Component {

    componentDidMount = () => this.props.setBackgroundImage(`./img/exhibit-one/${this.getWall()}/bg.jpg`)

    getWall = () => {
      // this part isnt important. it is a function that determines what wall I am on, in order to set
      // the proper image.
    }

    render() {
      return <ComposedComponent />
    }
  }

  return connect(null, actions)(ExhibitHoc);
}

При начальной загрузке ExhibitOne я вижу, что создатель действия setBackgroundImage выполняется дважды, взглянув на Redux Logger в консоли. Первоначально я склонялся к использованию componentDidMount, потому что я думал, что его использование ограничит создателя действия выполнением только один раз. Вот скриншот журнала:

введите описание изображения здесь

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


person Dan Zuzevich    schedule 01.03.2018    source источник


Ответы (3)


Проблема в том, что реквизит component здесь является приложением функции, которое создает новый класс при каждом рендеринге. Это приведет к отключению предыдущего компонента и подключению нового (см. документацию для react-router для получения дополнительной информации). Обычно вы должны использовать render для обработки этого, но это не будет работать с компонентами более высокого порядка, так как любой компонент, созданный с помощью приложения HOC во время рендеринга, все равно будет перемонтирован во время согласования React.

Простое решение — создать свои компоненты вне класса ExhibitOne, например:

const ExhibitWallOne = ExhibitHOC(WallOne);
const ExhibitWallTwo = ExhibitHOC(WallTwo);
..
export default class ExhibitOne extends Component {
  ..
          <Route path={`${match.url}/wall-one`} component={ExhibitWallOne} />
          <Route path={`${match.url}/wall-two`} component={ExhibitWallTwo} />
          ..
}

В качестве альтернативы, в зависимости от того, что делает оболочка, можно было бы объявить ее как обычный компонент, который отображает {this.props.children} вместо параметра <ComposedComponent/>, и обернуть компоненты в каждый Route:

<Route path={`${match.url}/wall-one`}
       render={(props) => <Wrap><WallOne {...props}/></Wrap>}
/>

Обратите внимание, что вам нужно будет использовать render вместо component, чтобы предотвратить перемонтирование. Если компоненты не используют параметры маршрутизации, вы можете даже удалить {...props}.

person Oblosys    schedule 01.03.2018
comment
Сейчас запускаю часть этого кода в свой текстовый редактор. Ответим в ближайшее время ›_‹ - person Dan Zuzevich; 01.03.2018
comment
Великолепно! Спас меня от большой боли здесь. Теперь, когда вы объяснили, что это дает новый класс при каждом рендеринге, это имеет массу смысла. - person Dan Zuzevich; 01.03.2018
comment
Рад помочь :-) - person Oblosys; 01.03.2018
comment
Рад видеть, что это все еще помогает людям сегодня. Отличный ответ @Oblosys! - person Dan Zuzevich; 10.08.2020

Здесь, в 2020 году, это было вызвано компонентом <React.StrictMode>, который был обернут вокруг <App /> в новых версиях приложения Create React. Удаление проблемного компонента из index.js устранило проблему двойного монтирования для всех моих компонентов. Я не знаю, было ли это задумано или что, но было раздражающим и вводящим в заблуждение видеть console.logs() дважды для всего.

person SacWebDeveloper    schedule 06.06.2020
comment
Хороший улов. Я не замечал этого раньше. Сообщить об ошибке, может быть? - person Dara Java; 21.10.2020
comment
Великолепно! Сэкономил мне пару часов чесания головы! Мне кажется, что StrictMode будет работать таким образом, поэтому я сомневаюсь, что это можно считать ошибкой. Тем не менее, двойное монтирование настолько неожиданно, особенно когда вы даже не знаете, что <React.StrictMode> существует... было бы неплохо увидеть вывод в консоли, показывающий, что происходит. - person Rolandus; 13.04.2021

Если вы используете «Hidden Material UI React», он монтирует ваш компонент каждый раз, когда вы его вызываете. Например, я написал ниже:

<Hidden mdDown implementation="css">
    <Container component="main" maxWidth="sm">
        {content}
    </Container>
</Hidden>
<Hidden smUp implementation="css">
    {content}
</Hidden>

Он вызывает оба содержимого в обоих скрытых компонентах. это заняло у меня много времени.

person Mohammad    schedule 23.12.2019