Чи можете ви змусити компонент 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(). Чи буде це тоді добре сказати, що питання ще не отримало належного рішення / відповіді?
adi

6
Виняткова відповідь відповіла на моє запитання в той час. Технічно це відповідь, яку я шукав, і я все ще вважаю правильною відповідь. Інші відповіді, я думаю, є хорошою додатковою інформацією для людей з тим самим питанням, яких слід знати.
Філіп Уолтон

Цікаво зауважити, що вам не потрібно нічого іншого в державі, окрім як ініціалізувати його на звичайний об'єкт, тоді виклик this.setState ({}) просто запускає нову візуалізацію. Реагувати чудово, але теж дивно, іноді. Тому ви можете безпосередньо перебирати дані в сховищах, коли викликати зміни без додаткової сантехніки або турбуватися про дані для кожного компонента.
Джейсон Себрінг

В цілому я б сказав так. Якщо ви використовуєте примусове оновлення, це стосується оновлення компонентів, де вони можуть залежати від змін поза державним керуванням вашої програми. Я не можу придумати хорошого прикладу цього. Корисно все-таки знати.
Даніель

Відповіді:


765

У своєму компоненті ви можете зателефонувати, this.forceUpdate()щоб примусити передати.

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


169
Інший спосіб - це this.setState (this.state);
кар

25
Використання прискореного оновлення не перешкоджає. Перегляньте останню відповідь.
Варан Пезешян

42
Хоча this.forceUpdate()це не дуже вдале рішення проблеми запитуючих, це правильна відповідь на питання, поставлене в заголовку. Хоча цього, як правило, слід уникати, але є ситуації, коли forceUpdate є хорошим рішенням.
ArneHugo

8
@kar Насправді додаткова логіка може бути реалізована, щоб shouldComponentUpdate()зробити ваше рішення марним.
Qwerty

4
Максимальний розмір
стеки

326

forceUpdateцього слід уникати, оскільки він відхиляється від розумового мислення. Документи React наводять приклад, коли forceUpdateможна використовувати:

За замовчуванням, коли стан вашого компонента або реквізити змінюються, ваш компонент буде рендерірован. Однак, якщо вони змінюються неявно (наприклад: дані глибоко в межах об'єкта змінюються, не змінюючи сам об’єкт) або якщо ваш метод render () залежить від деяких інших даних, ви можете сказати React, що потрібно повторно запустити render (), зателефонувавши примусове оновлення().

Однак я хотів би запропонувати ідею, що навіть із глибоко вкладеними об'єктами forceUpdateнепотрібна. За допомогою використання незмінного джерела даних відстеження змін стає дешевим; Зміна завжди призведе до нового об'єкта, тому нам потрібно лише перевірити, чи змінилася посилання на об'єкт. Ви можете використовувати бібліотеку Immutable JS для реалізації незмінних об'єктів даних у вашій програмі.

Зазвичай ви повинні намагатися уникати будь-якого використання forceUpdate () і читати тільки з цього.props та this.state в render (). Це робить ваш компонент "чистим", а ваш додаток набагато простішим та ефективнішим. примусове оновлення()

Зміна ключа елемента, який потрібно повторно винести, спрацює. Встановіть опору ключа на своєму елементі через стан, а потім, коли ви хочете оновити встановлений стан, мати новий ключ.

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

Потім відбувається зміна, і ви скидаєте ключ

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

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

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

ПРИМІТКА 1-9-2019:

Вищенаведене (зміна ключа) повністю замінить елемент. Якщо ви знайдете оновлення ключа для внесення змін, можливо, у вашому коді є проблема десь в іншому місці. Використання Math.random()ключа повторно створить елемент із кожним візуалізацією. Я НЕ рекомендую оновлювати такий ключ, як react використовує ключ, щоб визначити найкращий спосіб відновити речі.


16
Мені було б цікаво дізнатись, чому це спричинило зниження? Я стукав головою, намагаючись змусити читачів екрану реагувати на сповіщення про арію, і це єдина методика, яку я знайшов, яка працює надійно. Якщо у вашому інтерфейсі є щось, що генерує те саме оповіщення щоразу, коли воно клацається, реакція за замовчуванням не повторює DOM, тому зчитувач екрана не оголошує про зміни. Встановлення унікального ключа змушує його працювати. Можливо, скорочення було тому, що воно все ще включає встановлення стану. Але примушувати повторно виводити до DOM, встановивши ключ, - це золото!
Мартін Бейлі

2
Мені дуже сподобалась ця відповідь, оскільки - 1: використання forceupdate () не рекомендує, і 2: цей спосіб дуже корисний для сторонніх компонентів, які ви встановили через npm, тобто не для власних компонентів. Наприклад, React-Resizable-and-pokret - це сторонній компонент, який я використовую, але він не реагує на стан моїх компонентів. це чудове рішення.
Варан Пезешян

81
Це злом і зловживання key. По-перше, це наміри незрозумілі. Читачу вашого коду доведеться потрудитися, щоб зрозуміти, чому ви використовуєте keyцей спосіб. По-друге, це не більш чисте, ніж forceUpdate. "Чистий" Реакція означає, що візуальна презентація вашого компонента на 100% залежить від його стану, тому якщо ви зміните будь-який стан, він оновиться. Якщо у вас є деякі глибоко вкладені об'єкти (сценарій, який forceUpdateдокументи називають причиною його використання), то використання цього forceUpdateробить це зрозумілим. По-третє, Math.random()це… випадково. Теоретично це може генерувати те саме випадкове число.
atomkirk

8
@xtraSimplicity Я не можу погодитись, що це відповідає способу здійснення React. forceUpdateце Реактивний спосіб зробити це.
Роб Грант

3
@ Роберт Грант, озираючись назад, я згоден. Додаткова складність насправді не варта.
XtraSimplicity

80

Насправді, forceUpdate()це єдине правильне рішення, яке setState()може не викликати повторне відображення, якщо додаткова логіка буде впроваджена в shouldComponentUpdate()або коли вона просто повертається false.

примусове оновлення()

Виклик forceUpdate()викликає render()виклик компонента, пропуск shouldComponentUpdate(). більше ...

setState ()

setState()завжди викликає повторне відтворення, якщо тільки не буде реалізована логіка умовного візуалізації в shouldComponentUpdate(). більше ...


forceUpdate() Ви можете викликати всередині вашого компонента користувачем this.forceUpdate()


Гачки: Як я можу змусити компонент відновлювати гачки в React?


BTW: Ви мутуєте стан або ваші вкладені властивості не поширюються?


1
одне цікаве, що слід зазначити, це те, що визначення стану за межами setState, такого як this.state.foo = 'bar', не запустить метод життєвого циклу візуалізації
lfender6445,

4
@ lfender6445 Призначення стану безпосередньо за this.stateмежами конструктора також повинно призвести до помилки. Можливо, цього не зробили у 2016 році; але я майже впевнений, що це робить і зараз.
Тайлер

Я додав декілька посилань, щоб вирішити завдання щодо прямого стану.
Qwerty

36

Я уникаю forceUpdate, роблячи наступні дії

WRONG WAY : не використовуйте індекс як ключ

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

Для обґрунтування мого голосування, незважаючи на те, що цей підхід є доброю практикою, ця відповідь не пов'язана з питанням.
мікнік

34

Коли ви хочете спілкуватися з двома компонентами React, які не пов'язані відносинами (батько-дитина), доцільно використовувати Flux або подібні архітектури.

Що ви хочете зробити, це прослухати зміни в спостережуваному сховищі компонентів , що містить модель та її інтерфейс, та збереження даних, що спричиняє зміну візуалізації як stateу MyComponent. Коли магазин натискає нові дані, ви змінюєте стан свого компонента, що автоматично запускає візуалізацію.

Зазвичай вам слід намагатися уникати використання forceUpdate(). З документації:

Зазвичай ви повинні намагатися уникати будь-якого використання forceUpdate () і читати тільки з цього.props та this.state в render (). Це робить вашу програму набагато простішою та ефективнішою


2
Який стан пам'яті компонентного стану? Якщо у мене на сторінці п'ять компонентів, і всі вони слухають один магазин, чи маю я дані п'ять разів у пам'яті, чи це посилання? І чи не всі ці слухачі швидко додаються? Чому краще «простудити», ніж просто передавати дані до своєї цілі?
AJFarkas

5
Насправді рекомендації полягають у тому, щоб передавати дані магазину реквізитам компонентів і використовувати лише стан компонента для таких речей, як стан прокрутки та інші незначні речі, призначені для користувача інтерфейсу. Якщо ви використовуєте функцію redux (я рекомендую це), ви можете використовувати функцію підключення від react-reduxдля автоматичного відображення стану зберігання на реквізити компонентів, коли це необхідно, на основі функції відображення, яку ви надаєте.
Штійн де Вітт

1
@AJFarkas стан буде призначений для даних магазину, який все одно є лише вказівником на нього, тому пам'ять не є проблемою, якщо ви не клонуєте.
Джейсон Себрінг

4
"forceUpdate () слід завжди уникати" невірно. У документації чітко сказано: "Зазвичай ви повинні намагатися уникати будь-якого використання forceUpdate ()". Є дійсні випадки використанняforceUpdate
AJP

2
@AJP Ви абсолютно праві, це були особисті упередження :) Я відповідь відредагував.
gcedo

18

використовуйте гачки або HOC взяти свій вибір

Використовуючи гачки або шаблон HOC (компонент вищого порядку) , ви можете мати автоматичні оновлення, коли ваші магазини змінюються. Це дуже легкий підхід без рамок.

useStore Hooks спосіб обробляти оновлення магазину

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
Просто цікаво, що стосується ясності / читальності, наскільки coud this.setState (this.state) буде кращим, ніж this.forceUpdate ()?
Зоман

17

Тож я гадаю, що моє запитання таке: чи потрібні компоненти React, щоб мати стан, щоб відтворити? Чи є спосіб змусити компонент оновлюватись на вимогу, не змінюючи стан?

Інші відповіді намагалися проілюструвати, як ви могли, але справа в тому, що ви не повинні . Навіть хакітне рішення про зміну ключа пропускає суть. Сила React - це відмовитися від керування ручним керуванням, коли щось має відображатись, а замість того, щоб просто стосуватися себе того, як щось має відображатись на входах. Потім подайте потік входів.

Якщо вам потрібно вручну змусити повторно відтворювати, ви майже точно не робите щось правильно.


@ art-solopov, про яку проблему ти йдеш? Які документи їх представляють? І якщо припустити, що ви посилаєтесь на вкладені об’єкти у стані, відповідь полягає у використанні іншої структури для зберігання вашого стану. Це явно неоптимальний спосіб обробки стану в React. Ви також не повинні управляти станом окремо. Ви втрачаєте одну з найбільших переваг React, якщо не працюєте з системою управління державою. Управління оновленнями вручну - це анти-шаблон.
Кайл Бейкер

Будь ласка, можете перевірити це питання stackoverflow.com/questions/61277292/… ?
HuLu ViCa

4

Ви можете зробити це двома способами:

1. Використовуйте forceUpdate()метод:

Існують деякі збої, які можуть статися при використанні forceUpdate()методу. Одним із прикладів є те, що він ігнорує shouldComponentUpdate()метод і повторно подає перегляд незалежно від того, shouldComponentUpdate()повертає помилкове. Через це використання forceUpdate () слід уникати, коли це можливо.

2. Передання this.state setState()методу

Наступний рядок коду долає проблему з попереднього прикладу:

this.setState(this.state);

Дійсно, все це робиться - це перезапис поточного стану на поточний стан, що викликає повторне відображення. Це все ще не обов'язково найкращий спосіб робити речі, але він долає деякі проблеми, з якими ви можете зіткнутися за допомогою методу forceUpdate ().


2
Оскільки setState пакетно, це, мабуть, безпечніше зробити:this.setState(prevState => prevState);
Марк Галлоуей

1
Не дуже впевнений, чому це - глюк. Назва методу ('forceUpdate') не могла бути зрозумілішою: примушуйте оновлення завжди.
Зоман

4

Є кілька способів відновити ваш компонент:

Найпростіше рішення - використовувати метод forceUpdate ():

this.forceUpdate()

Ще одне рішення полягає у створенні не використовуваного ключа в стані (nonUsedKey) та виклику функції setState з оновленням цього невживаного ключа:

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

Або перепишіть увесь поточний стан:

this.setState(this.state);

Зміна реквізиту також забезпечує оновлення компонентів.


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 для повторного відтворення компонента.

Усі відповіді тут правильні, доповнюючи це питання для розуміння .. оскільки ми знаємо, що повторно виводити компонент з out за допомогою 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);

1

Ще одна відповідь на резервну копію прийнятої відповіді :-)

Реагує перешкоджає використанню, forceUpdate()оскільки вони, як правило, мають дуже "це єдиний спосіб зробити це" до функціонального програмування. У багатьох випадках це добре, але багато розробників React мають OO-фон, і при такому підході цілком нормально слухати спостережуваний об’єкт.

І якщо ви це зробите, ви, ймовірно, знаєте, що ОБОВ'ЯЗКОВО ретранслюватися, коли спостерігаються "пожежі", і, як так, ви повинні використовувати, forceUpdate()і це фактично плюс, щоshouldComponentUpdate() тут НЕ задіяний.

Такі інструменти, як MobX, який використовує підхід OO, насправді роблять це під поверхнею (насправді MobX дзвонить render()безпосередньо)


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>
      )
   }
}

Зателефонуйте до цього.forceChange (), коли вам потрібно примусити повторне надання.


0

ES6 - я включаю приклад, який був мені корисним:

У "короткому, якщо твердження" ви можете передати порожню функцію, як це:

isReady ? ()=>{} : onClick

Це, здається, є найкоротшим підходом.

()=>{}

0

forceUpdate(); метод буде працювати, але доцільно використовувати setState();



0

Ще один спосіб викликати setState, І зберегти стан:

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


0

forceUpdate (), але кожен раз, коли я коли-небудь чув, щоб хтось говорив про це, за ним слідкувати ніколи не слід цим користуватися.


-1

Ви можете використовувати forceUpdate () для більш детальної перевірки ( forceUpdate () ).


1
Навіщо цікаво, навіщо залишати цю відповідь, якщо протягом 4 років люди вже пропонували це як можливе рішення в цій темі?
Байрон Котзее
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.