Можете ли вы заставить компонент React перерисовываться без вызова setState?

690

У меня есть внешний (для компонента), наблюдаемый объект, который я хочу прослушивать на предмет изменений. Когда объект обновляется, он генерирует события изменения, а затем я хочу повторно визуализировать компонент при обнаружении любого изменения.

На верхнем уровне React.renderэто было возможно, но внутри компонента это не работает (что имеет некоторый смысл, поскольку renderметод просто возвращает объект).

Вот пример кода:

export default class MyComponent extends React.Component {

  handleButtonClick() {
    this.render();
  }

  render() {
    return (
      <div>
        {Math.random()}
        <button onClick={this.handleButtonClick.bind(this)}>
          Click me
        </button>
      </div>
    )
  }
}

Щелчок по кнопке вызывает изнутри this.render(), но это не то, что фактически вызывает рендеринг (вы можете увидеть это в действии, потому что созданный текст {Math.random()}не изменяется). Тем не менее, если я просто позвоню this.setState()вместо this.render(), он работает нормально.

Поэтому я предполагаю, что мой вопрос таков: должны ли компоненты React иметь состояние для повторного рендеринга? Есть ли способ заставить компонент обновляться по требованию без изменения состояния?

Филип Уолтон
источник
7
принятый ответ говорит this.forceUpdate()это правильное решение , тогда как остальные все ответы и нескольких замечаний против использования forceUpdate(). Тогда будет ли хорошо сказать, что вопрос еще не получил правильного решения / ответа?
ади
7
Исключенный ответ ответил на мой вопрос в то время. Технически это ответ, который я искал, и я все еще думаю правильный ответ. Другие ответы, я думаю, являются хорошей дополнительной информацией для людей с таким же вопросом, о которых нужно знать.
Филипп Уолтон
Интересно отметить, что вам НЕ НУЖНО НИЧЕГО В STATE, кроме как инициализировать его простым объектом, а затем вызывать this.setState ({}) просто запускает новый рендер. Реакция отличная, но иногда странная. Следовательно, вы можете напрямую циклически перемещаться по хранилищам данных, когда инициируете изменение, без лишних затрат или беспокойства о данных на экземпляр компонента.
Джейсон Себринг,
В целом я бы сказал да. Если вы используете принудительное обновление, это предназначено для обновления компонентов, когда они могут зависеть от изменений за пределами управления состоянием вашего приложения. Я не могу придумать хороший пример этого. Полезно знать, хотя.
Даниил

Ответы:

765

В вашем компоненте вы можете вызвать this.forceUpdate()принудительное повторное рендеринг.

Документация: https://facebook.github.io/react/docs/component-api.html

Crob
источник
170
Другой способ - это this.setState (this.state);
Кар
25
Использование forceupdate не рекомендуется, смотрите последний ответ.
Варан Пезешкян
42
Хотя this.forceUpdate()это не очень хорошее решение проблемы спрашивающих, это правильный ответ на вопрос, заданный в заголовке. Хотя этого, как правило, следует избегать, бывают ситуации, когда forceUpdate является хорошим решением.
ArneHugo
8
@kar На самом деле, дополнительная логика может быть реализована для того, чтобы shouldComponentUpdate()сделать ваше решение бесполезным.
Qwerty
4
Превышен
326

forceUpdateследует избегать, потому что это отклоняется от мышления React. Документы React приводят пример того, когда forceUpdateможно использовать:

По умолчанию, когда состояние вашего компонента или реквизит меняются, ваш компонент будет повторно визуализироваться. Однако, если они изменяются неявно (например, данные глубоко внутри объекта изменяются без изменения самого объекта) или если ваш метод render () зависит от некоторых других данных, вы можете указать React, что ему нужно повторно запустить render (), вызвав forceUpdate ().

Однако я хотел бы предложить идею, что даже с глубоко вложенными объектами нет forceUpdateнеобходимости. При использовании неизменного источника данных отслеживание изменений становится дешевым; изменение всегда приведет к появлению нового объекта, поэтому нам нужно только проверить, изменилась ли ссылка на объект. Вы можете использовать библиотеку Immutable JS для реализации неизменяемых объектов данных в вашем приложении.

Обычно вы должны стараться избегать любого использования forceUpdate () и читать только из this.props и this.state в render (). Это делает ваш компонент «чистым», а ваше приложение намного проще и эффективнее. forceUpdate ()

Изменение ключа элемента, который вы хотите перерисовать, будет работать. Установите ключевую опору для вашего элемента через состояние, а затем, когда вы хотите обновить установленное состояние, чтобы иметь новый ключ.

<Element key={this.state.key} /> 

Затем происходит изменение, и вы сбрасываете ключ

this.setState({ key: Math.random() });

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

Хотя истинный ответ на вопрос ОП был бы, forceUpdate()я нашел это решение полезным в различных ситуациях. Я также хочу отметить, что, если вы обнаружите, что используете, forceUpdateвы можете пересмотреть свой код и посмотреть, есть ли другой способ сделать что-то.

ПРИМЕЧАНИЕ 1-9-2019:

Выше (изменение ключа) полностью заменит элемент. Если вы обнаружите, что обновляете ключ для внесения изменений, возможно, у вас есть проблема где-то еще в вашем коде. Использование Math.random()в ключе заново создаст элемент с каждым рендером. Я НЕ рекомендовал бы обновлять ключ, как это, так как реагирует использует ключ, чтобы определить лучший способ перерисовать вещи.

pizzarob
источник
16
Мне было бы интересно узнать, почему это получило понижательную оценку? Я бился головой, пытаясь заставить программы чтения с экрана реагировать на предупреждения арии, и это единственная найденная мною техника, которая работает надежно. Если в вашем пользовательском интерфейсе есть что-то, генерирующее одно и то же предупреждение при каждом нажатии, по умолчанию реакция не выполняет повторную визуализацию DOM, поэтому программа чтения с экрана не объявляет об изменении. Установка уникального ключа заставляет его работать. Может быть, это было связано с тем, что оно все еще включает в себя настройку состояния. Но форсирование повторного рендеринга в DOM путем установки ключа является золотым!
Мартин Бэйли
2
Мне очень понравился этот ответ, потому что - 1: использование forceupdate () не рекомендуется, и 2: этот способ очень полезен для сторонних компонентов, которые вы установили через npm, то есть не для ваших собственных компонентов. Например, React-resizable-and-movable - это сторонний компонент, который я использую, но он не реагирует на состояние моего компонента. это отличное решение.
Варан Пезешкян
81
Это взлом и злоупотребление key. Во-первых, это неясно. Читатель вашего кода должен будет усердно работать, чтобы понять, почему вы используете keyэтот способ. Во-вторых, это не более чисто, чем forceUpdate. «Чистый» React означает, что визуальное представление вашего компонента на 100% зависит от его состояния, поэтому, если вы изменяете какое-либо состояние, он обновляется. Однако, если у вас есть несколько глубоко вложенных объектов (сценарий, в котором forceUpdateдокументы приводят причину его использования), тогда использование forceUpdateделает это понятным. В-третьих, Math.random()это ... случайно. Теоретически, он может генерировать одно и то же случайное число.
Атомкирк
8
@xtraSimplicity Я не могу согласиться с тем, что это соответствует способу действий React. forceUpdateРеактивный способ сделать это.
Роб Грант
3
@ Роберт Грант, оглядываясь назад, я согласен. Добавленная сложность не стоит того.
XtraSimplicity
80

На самом деле, forceUpdate()это единственное правильное решение, которое setState()может не вызвать повторную визуализацию, если в ней реализована дополнительная логика shouldComponentUpdate()или когда она просто возвращается false.

forceUpdate ()

Вызов forceUpdate()вызовет render()вызов на компоненте, пропуская shouldComponentUpdate(). Больше...

SetState ()

setState()всегда будет вызывать повторный рендеринг, если в нем не реализована логика условного рендеринга shouldComponentUpdate(). Больше...


forceUpdate() может быть вызван из вашего компонента this.forceUpdate()


Hooks: Как заставить компонент перерисовать с помощью хуков в React?


Кстати: вы изменяете состояние или ваши вложенные свойства не размножаются?

Qwerty
источник
1
Одна интересная вещь, которую следует отметить, это то, что изменения состояния вне setState, такие как this.state.foo = 'bar', не будут запускать метод жизненного цикла рендеринга
lfender6445
4
@ lfender6445 Присваивание состояния напрямую this.stateконструктору также должно вызвать ошибку. Может быть, не сделали этого в 2016 году; но я уверен, что теперь это так.
Тайлер
Я добавил несколько ссылок для работы с прямыми назначениями.
Qwerty
36

Я избежал forceUpdateделать следующее

НЕПРАВИЛЬНЫЙ ПУТЬ : не используйте индекс в качестве ключа

this.state.rows.map((item, index) =>
   <MyComponent cell={item} key={index} />
)

ПРАВИЛЬНЫЙ СПОСОБ : используйте идентификатор данных в качестве ключа, это может быть некоторый гид и т.д.

this.state.rows.map((item) =>
   <MyComponent item={item} key={item.id} />
)

таким образом, улучшая код, ваш компонент будет УНИКАЛЬНЫМ и будет отображаться естественным образом.

Виджей
источник
this.state.rows.map((item) => <MyComponent item={item} key={item.id} /> )
Тал
Большое спасибо за то, что подтолкнули меня в правильном направлении. Использование индекса в качестве ключа действительно было моей проблемой.
RoiEX
Чтобы оправдать мое отрицательное мнение, несмотря на то, что использование этого подхода является хорошей практикой, этот ответ не связан с вопросом.
micnic
34

Если вы хотите, чтобы взаимодействовали два компонента React, которые не связаны отношениями (parent-child), рекомендуется использовать Flux или аналогичные архитектуры.

То, что вы хотите сделать, это прослушать изменения в наблюдаемом хранилище компонентов , которое содержит модель и ее интерфейс, и сохранить данные, которые вызывают изменение рендеринга, как stateв MyComponent. Когда хранилище загружает новые данные, вы меняете состояние вашего компонента, что автоматически запускает рендеринг.

Обычно вы должны стараться избегать использования forceUpdate(). Из документации:

Обычно вы должны стараться избегать любого использования forceUpdate () и читать только из this.props и this.state в render (). Это делает ваше приложение намного проще и эффективнее

gcedo
источник
2
Каков статус памяти состояния компонента? Если у меня есть пять компонентов на странице, и все они слушают одно хранилище, у меня есть данные в памяти пять раз, или это ссылки? И разве все эти слушатели не складывают быстро? Почему лучше «просачиваться», чем просто передавать данные вашей цели?
AJFarkas
5
На самом деле, рекомендации состоят в том, чтобы передавать данные хранилища в реквизиты компонента и использовать состояние компонента только для таких вещей, как состояние прокрутки и другие незначительные специфические для пользовательского интерфейса вещи. Если вы используете redux (я рекомендую его), вы можете использовать функцию connect from react-reduxдля автоматического сопоставления состояния магазина с реквизитами компонентов, когда это необходимо, на основе предоставленной вами функции отображения.
Стейн де Витт
1
@AJFarkas состояние будет присвоено данным магазина, которые в любом случае являются просто указателем на него, поэтому память не является проблемой, если вы не клонируете.
Джейсон Себринг,
4
«forceUpdate () всегда следует избегать» неверно. В документации ясно сказано: «Обычно вы должны стараться избегать любого использования forceUpdate ()». Есть действительные случаи использованияforceUpdate
ОЮЛ
2
@AJP ты абсолютно прав, это был личный уклон :) Я отредактировал ответ.
Гседо
18

использовать крючки или HOC выбрать

Используя хуки или шаблон HOC (компонент высшего порядка) , вы можете автоматически обновлять их при изменении магазинов. Это очень легкий подход без основы.

Использование магазина Хукс способ обработки обновлений магазина

interface ISimpleStore {
  on: (ev: string, fn: () => void) => void;
  off: (ev: string, fn: () => void) => void;
}

export default function useStore<T extends ISimpleStore>(store: T) {
  const [storeState, setStoreState] = useState({store});
  useEffect(() => {
    const onChange = () => {
      setStoreState({store});
    }
    store.on('change', onChange);
    return () => {
      store.off('change', onChange);
    }
  }, []);
  return storeState.store;
}

withStores HOC обрабатывает обновления магазина

export default function (...stores: SimpleStore[]) {
  return function (WrappedComponent: React.ComponentType<any>) {
    return class WithStore extends PureComponent<{}, {lastUpdated: number}> {
      constructor(props: React.ComponentProps<any>) {
        super(props);
        this.state = {
          lastUpdated: Date.now(),
        };
        this.stores = stores;
      }

      private stores?: SimpleStore[];

      private onChange = () => {
        this.setState({lastUpdated: Date.now()});
      };

      componentDidMount = () => {
        this.stores &&
          this.stores.forEach((store) => {
            // each store has a common change event to subscribe to
            store.on('change', this.onChange);
          });
      };

      componentWillUnmount = () => {
        this.stores &&
          this.stores.forEach((store) => {
            store.off('change', this.onChange);
          });
      };

      render() {
        return (
          <WrappedComponent
            lastUpdated={this.state.lastUpdated}
            {...this.props}
          />
        );
      }
    };
  };
}

Класс SimpleStore

import AsyncStorage from '@react-native-community/async-storage';
import ee, {Emitter} from 'event-emitter';

interface SimpleStoreArgs {
  key?: string;
  defaultState?: {[key: string]: any};
}

export default class SimpleStore {
  constructor({key, defaultState}: SimpleStoreArgs) {
    if (key) {
      this.key = key;
      // hydrate here if you want w/ localState or AsyncStorage
    }
    if (defaultState) {
      this._state = {...defaultState, loaded: false};
    } else {
      this._state = {loaded: true};
    }
  }
  protected key: string = '';
  protected _state: {[key: string]: any} = {};
  protected eventEmitter: Emitter = ee({});
  public setState(newState: {[key: string]: any}) {
    this._state = {...this._state, ...newState};
    this.eventEmitter.emit('change');
    if (this.key) {
      // store on client w/ localState or AsyncStorage
    }
  }
  public get state() {
    return this._state;
  }
  public on(ev: string, fn:() => void) {
    this.eventEmitter.on(ev, fn);
  }
  public off(ev: string, fn:() => void) {
    this.eventEmitter.off(ev, fn);
  }
  public get loaded(): boolean {
    return !!this._state.loaded;
  }
}

Как пользоваться

В случае крючков:

// use inside function like so
const someState = useStore(myStore);
someState.myProp = 'something';

В случае HOC:

// inside your code get/set your store and stuff just updates
const val = myStore.myProp;
myOtherStore.myProp = 'something';
// return your wrapped component like so
export default withStores(myStore)(MyComponent);

УБЕДИТЕСЬ, чтобы экспортировать свои магазины как единое целое, чтобы получить выгоду от глобальных изменений следующим образом:

class MyStore extends SimpleStore {
  public get someProp() {
    return this._state.someProp || '';
  }
  public set someProp(value: string) {
    this.setState({...this._state, someProp: value});
  }
}
// this is a singleton
const myStore = new MyStore();
export {myStore};

Этот подход довольно прост и работает для меня. Я также работаю в больших командах и использую Redux и MobX, и нахожу, что они тоже хороши, но просто много шаблонов. Мне просто нравится мой собственный подход, потому что я всегда ненавидел много кода за что-то, что может быть простым, когда вам это нужно.

Джейсон Себринг
источник
2
Я думаю , что есть опечатка в вашем примере класса магазина в способе обновления , имеющем написать первую строку метода следующим образом : this._data = {...this._data, ...newData}.
Норберт
2
Реакция препятствует наследованию в пользу композиции. reactjs.org/docs/composition-vs-inheritance.html
Фред,
1
Просто интересно о ясности / читабельности, как может this.setState (this.state) быть лучше this.forceUpdate ()?
Zoman
17

Поэтому я предполагаю, что мой вопрос таков: должны ли компоненты React иметь состояние для повторного рендеринга? Есть ли способ заставить компонент обновляться по требованию без изменения состояния?

Другие ответы пытались проиллюстрировать, как вы могли бы, но дело в том, что вы не должны . Даже хакерское решение о смене ключа не имеет смысла. Сила React заключается в том, что он отказывается от управления вручную, когда что-то должно отображаться, и вместо этого просто заботится о том, как что-то должно отображаться на входах. Затем поставьте поток входов.

Если вам нужно вручную выполнить повторный рендеринг, вы почти наверняка что-то делаете неправильно.

Кайл Бейкер
источник
@ art-solopov, о какой проблеме ты говоришь? Какие документы представляют их? И, предполагая, что вы ссылаетесь на вложенные объекты в состоянии, ответ заключается в использовании другой структуры для хранения вашего состояния. Это явно неоптимальный способ обработки состояния в React. Вы также не должны управлять государством отдельно. Вы теряете одно из самых больших преимуществ React, если не работаете с системой государственного управления. Управление обновлениями вручную - это анти-паттерн.
Кайл Бейкер
Пожалуйста, вы можете проверить этот вопрос stackoverflow.com/questions/61277292/… ?
HuLu ViCa
4

Вы можете сделать это несколькими способами:

1. Используйте forceUpdate()метод:

При использовании forceUpdate()метода могут возникнуть некоторые глюки . Одним из примеров является то, что он игнорирует shouldComponentUpdate()метод и повторно отображает представление независимо от того, shouldComponentUpdate()возвращает ли false. Из-за этого следует избегать использования forceUpdate (), когда это возможно.

2. Передача this.state в setState()метод

Следующая строка кода устраняет проблему с предыдущим примером:

this.setState(this.state);

На самом деле все, что это делает, это перезаписывает текущее состояние текущим состоянием, которое запускает повторный рендеринг. Это по-прежнему не обязательно лучший способ сделать что-то, но он преодолевает некоторые глюки, с которыми вы можете столкнуться, используя метод forceUpdate ().

kojow7
источник
2
Поскольку setState является пакетным, это, вероятно, безопаснее:this.setState(prevState => prevState);
Марк Галлоуэй
1
Не совсем уверен, почему это «глюк». Имя метода ('forceUpdate') не может быть более понятным: принудительное обновление всегда.
Zoman
4

Есть несколько способов перерисовки вашего компонента:

Самое простое решение - использовать метод forceUpdate ():

this.forceUpdate()

Еще одно решение - создать неиспользуемый ключ в состоянии (nonUsedKey) и вызвать функцию setState с обновлением этого nonUsedKey:

this.setState({ nonUsedKey: Date.now() } );

Или переписать все текущее состояние:

this.setState(this.state);

Изменение реквизита также обеспечивает повторное отображение компонента.

Jackkobec
источник
3

Для полноты вы также можете добиться этого в функциональных компонентах:

const [, updateState] = useState();
const forceUpdate = useCallback(() => updateState({}), []);
// ...
forceUpdate();

Или, как многоразовый крючок:

const useForceUpdate = () => {
  const [, updateState] = useState();
  return useCallback(() => updateState({}), []);
}
// const forceUpdate = useForceUpdate();

Смотрите: https://stackoverflow.com/a/53215514/2692307

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

Лукас Бах
источник
2

Мы можем использовать this.forceUpdate (), как показано ниже.

       class MyComponent extends React.Component {



      handleButtonClick = ()=>{
          this.forceUpdate();
     }


 render() {

   return (
     <div>
      {Math.random()}
        <button  onClick={this.handleButtonClick}>
        Click me
        </button>
     </div>
    )
  }
}

 ReactDOM.render(<MyComponent /> , mountNode);

Элемент «Math.random» в DOM обновляется только в том случае, если вы используете setState для повторной визуализации компонента.

Все ответы здесь верны, дополняя вопрос для понимания ... поскольку мы знаем, что перерисовать компонент без использования setState ({}) можно с помощью forceUpdate ().

Приведенный выше код работает с setState, как показано ниже.

 class MyComponent extends React.Component {



             handleButtonClick = ()=>{
                this.setState({ });
              }


        render() {
         return (
  <div>
    {Math.random()}
    <button  onClick={this.handleButtonClick}>
      Click me
    </button>
  </div>
)
  }
 }

ReactDOM.render(<MyComponent /> , mountNode);
Dhana
источник
1

Еще один ответ для подтверждения принятого ответа :-)

React не одобряет использование, forceUpdate()потому что у них, как правило, очень функциональный подход «это единственный способ сделать это». Это хорошо во многих случаях, но многие разработчики React поставляются с OO-фоном, и при таком подходе совершенно нормально слушать наблюдаемый объект.

И если вы это сделаете, вы, вероятно, знаете, что вы ДОЛЖНЫ перерисовывать, когда наблюдаемые «выстрелы», и поэтому вы ДОЛЖНЫ использовать, forceUpdate()и это на самом деле плюс, который shouldComponentUpdate()НЕ задействован здесь.

Такие инструменты, как MobX, который использует OO-подход, фактически делают это под поверхностью (на самом деле MobX вызывает render()напрямую)

Plaul
источник
0

Я нашел, что лучше избегать forceUpdate (). Одним из способов принудительного повторного рендеринга является добавление зависимости render () от временной внешней переменной и изменение значения этой переменной по мере необходимости.

Вот пример кода:

class Example extends Component{
   constructor(props){
      this.state = {temp:0};

      this.forceChange = this.forceChange.bind(this);
   }

   forceChange(){
      this.setState(prevState => ({
          temp: prevState.temp++
      })); 
   }

   render(){
      return(
         <div>{this.state.temp &&
             <div>
                  ... add code here ...
             </div>}
         </div>
      )
   }
}

Вызовите this.forceChange (), когда вам нужно выполнить принудительный повторный рендеринг.

raksheetbhat
источник
0

ES6 - я включаю пример, который был полезен для меня:

В «коротком операторе if» вы можете передать пустую функцию следующим образом:

isReady ? ()=>{} : onClick

Это, кажется, самый короткий подход.

()=>{}
MrDoda
источник
0

forceUpdate(); метод будет работать, но желательно использовать setState();

Chiamaka Ikeanyi
источник
0

Чтобы выполнить то, что вы описываете, попробуйте this.forceUpdate ().

API-Andy
источник
Что делает это действие?
Доминик
0

Другой способ вызова setState, И сохранить состояние:

this.setState(prevState=>({...prevState}));

dvvtms
источник
0

forceUpdate (), но каждый раз, когда я слышал, что кто-то говорит об этом, за этим следят, чтобы вы никогда не использовали это.

Ян
источник
-1

Вы можете использовать forceUpdate () для более подробной проверки ( forceUpdate () ).

Харшит Сингхай
источник
1
Из любопытства зачем оставлять этот ответ, если в течение 4 лет люди уже предлагали это в качестве возможного решения в этой теме?
Байрон Кётзее