Нужно более четкое объяснение того, как избежать бесконечного повторного рендеринга с помощью React-хуков - PullRequest
2 голосов
/ 30 апреля 2020

Не так бегло с перехватчиками React, раньше использовали множество компонентов класса, надеюсь, вы простите.

Текущий код вызывает бесконечный повторный рендеринг, и я думаю, я понимаю, почему - все тело функции вызывается при повторном рендеринге.

const NavTabs = () => {
  const classes = useStyles();

  const [categories, setCategories] = React.useState();
  const axiosPromise = getRequest(consts.categoriesURL);

  axiosPromise.then(data => {
    setCategories(data.value);
  })

  return (
    <div className={classes.root}>
      <AppBar position="static">
      </AppBar>
      {categories && <DynamicTabs categories={categories}/>}
    </div>
  );
}

Полагаю, я мог бы сделать что-то вроде if (!categories) { const axiosPromise [...] и т. д., т. е. выполнить http-запрос, только если категории еще не заполнены. Я думаю, это также может быть решено с помощью useEffect? Или завернуть хук во внутреннюю функцию?

Наверное, мой реальный вопрос - почему React рендерит все тело функции? Разве он не должен перерисовывать только функцию возврата? И тогда какой смысл использовать хуки, которые будут повторно запускаться при каждом рендеринге?

По сравнению с компонентами класса - разве код в теле функции не должен быть эквивалентен коду конструктора в компонентах класса, и функция возврата - эквивалент метода рендеринга?

Ответы [ 3 ]

2 голосов
/ 30 апреля 2020

Полагаю, я мог бы сделать что-то вроде if (! Categories) {const axiosPromise [...] и т. Д., Т.е. выполнить http-запрос, только если категории еще не заполнены. Я думаю, это также может быть решено с помощью useEffect? Или завернуть ловушку во внутреннюю функцию?

Да, useEffect - это путь к go здесь. Создание запроса и установка результата в качестве состояния являются побочными эффектами, которые должны выполняться только один раз в вашем случае. Мы можем легко добиться этого с помощью useEffect.

Наверное, мой настоящий вопрос - почему React рендерит все тело функции? Разве он не должен перерисовывать только функцию возврата? И тогда какой смысл использовать перехватчики, которые будут перезапускаться при каждом рендеринге?

React не имеет способа разбить функцию js и только перерисовать результат. Функция атоми c и должна быть завершена. Вот для чего нужны крючки. Реагируйте на элементы управления при запуске хуков, чтобы он мог выполнять забавные вещи, такие как обновления состояния пакетов, игнорировать устаревшие эффекты и расставлять приоритеты для работы с высоким приоритетом, например анимации.

По сравнению с компонентами класса - не должен ли код в теле функции быть эквивалентным коду конструктора в компонентах класса, а функция возврата - эквивалентна методу рендеринга?

Функциональный компонент эквивалентен методу рендеринга компонента класса. Они называются аналогичным образом. Все остальные методы жизненного цикла заменяются хуками.

Я рекомендую реагирующие документы - отличное место для старта, и Дан Абрамов имеет большое глубокое погружение на крюках .

1 голос
/ 30 апреля 2020

Чтобы ответить «почему реакция запускает всю функцию», ответ состоит в том, что javascript функции работают таким образом: вам всегда нужно запускать все это, они не останавливаются в середине *. Я понимаю, что вы думаете здесь, если вы привыкли к компонентам класса: разве у меня нет секции конструктора и секции рендеринга? и ответ: не совсем, если вы используете функциональные компоненты. У вас есть только рендер. Но крючки - это волшебные c, и они позволяют вам притворяться, что они состоят из двух частей.

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

  1. React обнаруживает компонент функции и создает или повторно использует существующий контекст рендеринга для этого компонента. Здесь хранится информация о перехватчиках.
  2. React вызывает ваш функциональный компонент, и он начинает работать.
  3. Вы вызываете перехватчики внутри своего функционального компонента. Они проверяют текущий контекст рендеринга и сохраняют / получают соответствующую информацию из этого контекста. В некотором смысле контекст рендеринга является «глобальной» переменной.
  4. Вы делаете все, что хотите внутри функции, и в конечном итоге возвращаете дерево компонентов (JSX) или ноль.
  5. реагирует затем ( в конечном итоге) обновляет DOM в соответствии с тем, что вы вернули, и сохраняет изменения в контексте рендеринга, чтобы при следующем вызове рендера он мог повторно использовать контекст.

Маги c в том, что контекст рендеринга может делать причудливые вещи с хуками, например, запускать их только один раз, всегда возвращать одно и то же значение из хука или любого другого количества вещей. Но в некотором смысле компонент «класс» становится контекстом рендеринга внутреннего реагирования, к которому хуки знают, как обращаться.

Вот пример useState хука, реализованного в компоненте класса: (Вам никогда не понадобится это делать, но это пример того, как работают хуки).

class FakeHook extends React.Component {
  constructor(...args) {
    super(...args)
    this.state = {}
    this.useStateCalls = 0
  }
  
  useState(defaultValue){
    const currentRenderContext = this.state
    let value = defaultValue
    const currentStateKey = `useState${this.useStateCalls}`
    if (currentStateKey in currentRenderContext) value = currentRenderContext[currentStateKey]
    this.useStateCalls++
    
    return[value, (newValue) => this.setState({[currentStateKey]: newValue})]
  }
  
  render(){
    this.useStateCalls = 0
    let [fooState, setFoo] = this.useState("foo default")
    let [barState, setBar] = this.useState("bar default")
    
    return(
      
        Foo state
        
          Value:
          {fooState}
          
        
        Bar state
        
          Value:
          {barState}
          
        
        Render context state:
        {JSON.stringify(this.state)}
)}} ReactDOM.render ( , document.getElementById ('main'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<main id=main>loading or error occurred...</main>

Обратите внимание, что состояние сохраняется в зависимости от порядка вызова хука внутри render. В реальных хуках контекст рендеринга хранится где-то, кроме this.state, но хуки знают, как его получить, и вам все равно. Кроме того, это всего лишь пример, реальные хуки работают немного по-другому, но концепция та же самая.

*: async функции и генераторы не запускаются все сразу, а вместо этого возвращают специальный объект, который позволяет функции запускаться в несколько шагов, ожидая или останавливаясь на await или yield.

1 голос
/ 30 апреля 2020

Да, getRequest вызывается каждый цикл рендеринга, который устанавливает некоторое состояние и запускает рендеринг. Размещение этого в ловушке эффекта с массивом зависимостей, вероятно, является лучшим решением. Какие зависимости вы определяете, когда вы будете вызывать getRequest.

Почему React перерисовывает все тело функции?

Необходимо запустить все тело функции чтобы определить возвращаемое значение.

И тогда какой смысл использовать хуки, которые будут повторно запускаться при каждом рендеринге?

Хуки запускаются на каждом рендер, в том же порядке они определены, но в зависимости от зависимостей может не вызывать обратный вызов. Хуки - это то, что придает функциональным компонентам большую жизнеспособность и ощущение жизненного цикла компонентов, чтобы быть почти эквивалентными функциональным компонентам на основе классов. В большинстве случаев вы можете полностью преобразовать основанный на классе компонент в функциональный и не отбрасывать никакой функциональности.

По сравнению с компонентами класса - если код в теле функции не эквивалентен код конструктора в компонентах класса и возвращаемая функция - эквивалент метода рендеринга?

Более точным будет представление всего определения функциональных компонентов как основанная на классах функция render, которая может содержать некоторую логику c и возвращает вычисленный JSX для визуализации в DOM.

Пример решения:

const NavTabs = () => {
  const classes = useStyles();

  const [categories, setCategories] = React.useState(); // <-- no initial state!

  useEffect(() => {
    getRequest(consts.categoriesURL).then(data => {
      setCategories(data.value); // <-- will update state and trigger render
    });
  }, []); // <-- empty dependency is run once on component mount

  return (
    <div className={classes.root}>
      <AppBar position="static">
      </AppBar>
      {categories && <DynamicTabs categories={categories}/>}
    </div>
  );
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...