Эквивалент shouldComponentUpdate для функционального компонента, чтобы игнорировать изменения состояния - PullRequest
3 голосов
/ 31 марта 2020

В моем коде есть компонент, который принимает оба реквизита и имеет свое собственное внутреннее состояние.
Компонент должен перерисовываться ТОЛЬКО при смене реквизита. Изменения состояния НЕ должны вызывать повторное рендеринг.
Это поведение может быть реализовано с помощью компонента на основе классов и пользовательской функции shouldComponentUpdate.
Однако это будет первый компонент на основе классов в кодовой базе. Все сделано с функциональными компонентами и крючками. Поэтому я хотел бы знать, можно ли кодировать желаемую функциональность с помощью функциональных компонентов.

После нескольких ответов, которые не подошли к реальной проблеме, я думаю, что мне нужно переформулировать свой вопрос. Вот минимальный пример с двумя компонентами:

  • Внутренний получает опору и имеет состояние. Это рассматриваемый компонент. Он не должен повторно отображаться после изменения состояния. Изменения реквизита должны вызвать повторное рендеринг.
  • Внешняя оболочка - это внутренняя оболочка. Это не имеет никакого смысла в рамках этого вопроса и предназначено только для того, чтобы поддержать Inner и смоделировать его изменения.

Чтобы продемонстрировать желаемую функциональность, я реализовал Inner с компонентом на основе классов. Живая версия этого кода может быть найдена на codeandbox . Как перенести его в функциональный компонент:

Inner.tsx:

import React, { Component } from 'react'

interface InnerProps{outerNum:number}
interface InnerState{innerNum:number}

export default class Inner extends Component<InnerProps, InnerState> {
    state = {innerNum:0};

    shouldComponentUpdate(nextProps:InnerProps, nextState:InnerState){
        return this.props != nextProps;
    }
    render() {
        return (
            <button onClick={()=>{
                this.setState({innerNum: Math.floor(Math.random()*10)})
            }}>
                {`${this.props.outerNum}, ${this.state.innerNum}`}
            </button>
        )
    }
}

Outer.tsx:

import React, { useState } from "react";
import Inner from "./Inner";

export default function Outer() {
  const [outerState, setOuterState] = useState(1);

  return (
    <>
      <button
        onClick={() => {
          setOuterState(Math.floor(Math.random() * 10));
        }}
      >
        change outer state
      </button>
      <Inner outerNum={outerState}></Inner>
    </>
  );
}

В официальных документах сказано , чтобы обернуть компонент в React.memo. Но это, кажется, не работает для предотвращения повторных обращений при смене состояния. Это относится только к изменениям реквизита.

Я пытался заставить React.memo работать. Вы можете увидеть версию кода с внешними и внутренними функциональными компонентами здесь .

Смежные вопросы:

Как использовать shouldComponentUpdate с React Hooks? : Этот вопрос касается только изменений проп. Принятый ответ рекомендует использовать React.memo

shouldComponentUpdate в функциональных компонентах : Этот вопрос предшествует функциональным компонентам с состоянием. Принятый ответ объясняет, почему функциональным компонентам не нужно shouldComponentUpdate, поскольку они не сохраняют состояние.

Ответы [ 4 ]

0 голосов
/ 01 апреля 2020

Реакция по проекту основана на setState -> re-render l oop. Изменение реквизита на самом деле является setState где-то в родительских компонентах. Если вы не хотите, чтобы setState запускал повторный рендеринг, то зачем вообще его использовать?

Вы можете использовать const state = useRef({}).current для хранения своего внутреннего состояния.

function InnerFunc(props) {
  const state = useRef({ innerNum: 0 }).current;
  return (
    <button
      onClick={() => {
        state.innerNum = Math.floor(Math.random() * 10);
      }}
    >
      {`${props.outerNum}, ${state.innerNum}`}
    </button>
  );
}

Тем не менее, все еще актуален вопрос: "как реализовать shouldComponentUpdate в мода реагировать крюк? " Вот решение:

function shouldComponentUpdate(elements, predicate, deps) {
  const store = useRef({ deps: [], elements }).current
  const shouldUpdate = predicate(store.deps)
  if (shouldUpdate) {
    store.elements = elements
  }
  store.deps = deps
  return store.elements
}

// Usage:

function InnerFunc(props) {
  const [state, setState] = useState({ innerNum: 0 })
  const elements = (
    <button
      onClick={() => {
        setState({ innerNum: Math.floor(Math.random() * 10) });
      }}
    >
      {`${props.outerNum}, ${state.innerNum}`}
    </button>
  );

  return shouldComponentUpdate(elements, (prevDeps) => {
    return prevDeps[0] !== props
  }, [props, state])
}

Заметив, что невозможно предотвратить цикл повторного рендеринга при вызове setState, вышеупомянутая ловушка просто гарантирует, что результат повторного рендеринга останется таким же, как предыдущий результат рендеринга.

0 голосов
/ 31 марта 2020

React памятка не останавливает изменения состояния

React.memo проверяет только изменения пропеллера. Если ваш компонент функции, обернутый в React.memo, имеет в своей реализации useState или useContext Hook, он все равно будет перерисовываться при изменении состояния или контекста.

Ref: - https://reactjs.org/docs/react-api.html#reactmemo

0 голосов
/ 31 марта 2020

Вы должны использовать событие, которое обеспечивает браузер и захват в функции перед setState, как это

function setState = (e) =>{ //the e is the event that give you the browser
//changing the state
e.preventDefault();
}
0 голосов
/ 31 марта 2020

Ваш Inner компонент зависит от свойства num компонента Outer, вы не можете предотвратить его рендеринг при изменении свойства, так как React.memo делает Сравнение свойств:

// The default behaviour is shallow comparison between previous and current render properties.
const areEqual = (a, b) => a.num === b.num;
export default React.memo(Inner, areEqual);

Запомнив компонент Inner и удалив зависимость num, он не будет отображаться при рендеринге Outer, см. вложенную песочницу.

export default function Outer() {
  const [outerState, setOuterState] = useState(1);

  return (
    <>
      ...
    // v Inner is memoized and won't render on `outerState` change.
      <Inner />
    </>
  );
}

Edit clever-silence-6xigt


Если вы хотите реализовать shouldComponentUpdate с крючками, вы можете попробовать:

const [currState] = useState();
// shouldUpdateState your's custom function to compare and decide if update state needed
setState(prevState => {
  if(shouldUpdateState(prevState,currState)) {
    return currState;
  }
  return prevState;
});
...