Як я можу змусити компонент повторно відтворити за допомогою хуків у React?


116

Беручи до уваги нижчий приклад гачків

   import { useState } from 'react';

   function Example() {
       const [count, setCount] = useState(0);

       return (
           <div>
               <p>You clicked {count} times</p>
               <button onClick={() => setCount(count + 1)}>
                  Click me
               </button>
          </div>
        );
     }

В основному ми використовуємо метод this.forceUpdate (), щоб змусити компонент негайно повторно відтворити компоненти класу React, як нижче

    class Test extends Component{
        constructor(props){
             super(props);
             this.state = {
                 count:0,
                 count2: 100
             }
             this.setCount = this.setCount.bind(this);//how can I do this with hooks in functional component 
        }
        setCount(){
              let count = this.state.count;
                   count = count+1;
              let count2 = this.state.count2;
                   count2 = count2+1;
              this.setState({count});
              this.forceUpdate();
              //before below setState the component will re-render immediately when this.forceUpdate() is called
              this.setState({count2: count
        }

        render(){
              return (<div>
                   <span>Count: {this.state.count}></span>. 
                   <button onClick={this.setCount}></button>
                 </div>
        }
 }

Але мій запит полягає в тому, як я можу змусити функціональний компонент негайно відтворити негайно за допомогою хуків?


1
Чи можете ви опублікувати версію оригінального компонента, яка використовує this.forceUpdate()? Можливо, є спосіб здійснити те саме без цього.
Яків

Останній рядок у setCount усічений. Незрозуміло, яка мета setCount у його поточному стані.
Estus Flask,

Це просто дія після this.forceUpdate (); Я додав це, щоб просто пояснити про це. ForceUpdate () у своєму питанні
Хемадрі Дасарі

Для чого це варте: я боровся з цим, тому що вважав, що мені потрібен ручний рендерінг, і нарешті зрозумів, що мені просто потрібно перенести зовнішню змінну в гачок стану та використати функцію налаштування, яка вирішила всі мої проблеми без рендеринг. Не кажучи вже про те, що це ніколи не потрібно, але варто поглянути на третій та четвертий перевірки, чи справді це потрібно у вашому конкретному випадку використання.
Олександр Нієд,

Відповіді:


77

Це можливо за допомогою useStateабо useReducer, оскільки useStateвикористовує useReducerвнутрішньо :

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

forceUpdateне призначений для використання за звичайних обставин, лише для тестування чи інших нестандартних випадків. Цю ситуацію можна вирішити більш звичним способом.

setCountє прикладом неправильного використання forceUpdate, setStateє асинхронним з міркувань продуктивності, і його не слід примушувати бути синхронним лише через те, що оновлення стану було виконано неправильно. Якщо стан покладається на встановлений раніше стан, це слід зробити за допомогою функції оновлення ,

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

<...>

Як стан, так і реквізит, отримані функцією оновлення, гарантовано будуть актуальними. Вихід програми оновлення неглибоко зливається із станом.

setCount може бути не наочним прикладом, оскільки його мета незрозуміла, але це стосується функції оновлення:

setCount(){
  this.setState(({count}) => ({ count: count + 1 }));
  this.setState(({count2}) => ({ count2: count + 1 }));
  this.setState(({count}) => ({ count2: count + 1 }));
}

Це перекладено як 1: 1 для хуків, за винятком того, що функції, які використовуються як зворотні виклики, краще запам'ятовувати:

   const [state, setState] = useState({ count: 0, count2: 100 });

   const setCount = useCallback(() => {
     setState(({count}) => ({ count: count + 1 }));
     setState(({count2}) => ({ count2: count + 1 }));
     setState(({count}) => ({ count2: count + 1 }));
   }, []);

Як const forceUpdate = useCallback(() => updateState({}), []);працює? Це навіть змушує оновлення?
Давид Молнар,

2
@ DávidMolnár useCallbackзапам'ятовує forceUpdate, тому він залишається незмінним протягом тривалості життя компонента і може безпечно передаватися як опора. updateState({})оновлює стан новим об’єктом при кожному forceUpdateвиклику, це призводить до повторного відтворення. Так що так, це вимагає оновлення при виклику.
Estus Flask

3
Отже, useCallbackчастина насправді не потрібна. Він повинен просто нормально працювати без цього.
Давід Молнар,

І це не могло б працювати, updateState(0)оскільки 0це примітивний тип даних? Це повинен бути об'єкт, чи це updateState([])(тобто використання з масивом) також може працювати?
Андру,

1
@Andru Так, стан оновлюється один раз, оскільки 0 === 0. Так, масив буде працювати, оскільки це теж об’єкт. Все, що не проходить перевірку рівності, можна використовувати, наприклад updateState(Math.random()), або лічильник.
Estus Flask

29

Як зазначали інші, useStateпрацює - ось як mobx-response-lite реалізує оновлення - ви можете зробити щось подібне.

Визначте новий гачок, useForceUpdate-

import { useState, useCallback } from 'react'

export function useForceUpdate() {
  const [, setTick] = useState(0);
  const update = useCallback(() => {
    setTick(tick => tick + 1);
  }, [])
  return update;
}

і використовувати його в компоненті -

const forceUpdate = useForceUpdate();
if (...) {
  forceUpdate(); // force re-render
}

Див. Https://github.com/mobxjs/mobx-react-lite/blob/master/src/utils.ts та https://github.com/mobxjs/mobx-react-lite/blob/master/src/useObserver .ts


2
З того, що я розумію з хуків, це може не спрацювати, оскільки useForceUpdateбуде повертати нову функцію кожного разу, коли функція повторно відображається. Для forceUpdateпрацювати при використанні в useEffect, він повинен повернути useCallback(update)Подивитися kentcdodds.com/blog/usememo-and-usecallback
Martin Ratinaud

Дякую, @MartinRatinaud - так, це може спричинити витік пам'яті без useCallback (?) - виправлено.
Брайан Бернс,

27

Як правило, ви можете використовувати будь-який підхід до обробки стану, який потрібно активувати оновлення.

За допомогою TypeScript

приклад коду і коробки

useState

const forceUpdate: () => void = React.useState()[1].bind(null, {})  // see NOTE below

useReducer

const forceUpdate = React.useReducer(() => ({}), {})[1] as () => void

як спеціальний гачок

Просто оберніть будь-який підхід, який вам більше подобається, таким чином

function useForceUpdate(): () => void {
  return React.useReducer(() => ({}), {})[1] as () => void // <- paste here
}

Як це працює?

" Щоб запустити оновлення " Викликати означає повідомити двигуну React про те, що якесь значення змінилося і що воно повинно повторно відобразити ваш компонент.

[, setState]від useState()вимагає параметра. Позбавляємось, пов’язуючи свіжий предмет {}.
() => ({})in useReducer- фіктивний редуктор, який повертає свіжий об'єкт кожного разу, коли відправляється дія.
{} (свіжий об’єкт) потрібен, щоб він ініціював оновлення, змінивши посилання у стані.

PS: useStateпросто useReducerвнутрішнє обгортання . джерело

ПРИМІТКА. Використання .bind з useState спричиняє зміну посилання на функції між рендерами. Можна загорнути його всередину useCallback, як уже було пояснено тут , але тоді це не був би сексуальний однокласник . Версія Редуктора вже зберігається еталонну рівність між рендерами. Це важливо, якщо ви хочете передати функцію forceUpdate в реквізиті.

рівнина JS

const forceUpdate = React.useState()[1].bind(null, {})  // see NOTE above
const forceUpdate = React.useReducer(() => ({}))[1]

17

Альтернатива відповіді @ MinhKha:

Це може бути набагато чистіше за допомогою useReducer:

const [, forceUpdate] = useReducer(x => x + 1, 0);

Застосування: forceUpdate()- миючий засіб без параметрів


13

Ви можете просто визначити useState так:

const [, forceUpdate] = React.useState(0);

І використання: forceUpdate(n => !n)

Сподіваюся, це допоможе!


7
Не вдасться, якщо forceUpdate буде викликано парну кількість разів на рендер.
Іжакі

Просто продовжуйте збільшувати значення.
Gary

2
Це схильне до помилок, тому його слід видалити або відредагувати.
slikts

11

Офіційне рішення для React Hooks FAQ для forceUpdate:

const [_, forceUpdate] = useReducer((x) => x + 1, 0);
// usage
<button onClick={forceUpdate}>Force update</button>

Робочий приклад


10

Вам бажано, щоб ваш компонент залежав лише від стану та реквізиту, і він буде працювати, як очікувалося, але якщо вам дійсно потрібна функція, щоб змусити компонент повторно відтворити, ви можете скористатися useStateхуком і викликати функцію, коли це потрібно.

Приклад

const { useState, useEffect } = React;

function Foo() {
  const [, forceUpdate] = useState();

  useEffect(() => {
    setTimeout(forceUpdate, 2000);
  }, []);

  return <div>{Date.now()}</div>;
}

ReactDOM.render(<Foo />, document.getElementById("root"));
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.production.min.js"></script>

<div id="root"></div>


Гаразд, але чому React представив this.forceUpdate (); в першу чергу, коли компонент повторно відтворює за допомогою setState у попередніх версіях?
Хемадрі Дасарі

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

1
Домовились. Я навіть ніколи не використовував цього на своєму досвіді, але я знаю, як це працює, тому просто намагаюся зрозуміти, як те саме можна зробити в гачках
Hemadri Dasari

1
@Tholle У and I can't think of a good use case for it right now мене є такий, як щодо того, якщо стан не контролюється React. Я не використовую Redux, але я припускаю, що він повинен виконати певне примусове оновлення. Я особисто використовую проксі-сервери для підтримання стану, потім компонент може перевіряти зміни в властивостях, а потім оновлювати. Здається, це теж працює дуже ефективно. Наприклад, усі мої компоненти потім контролюються проксі-сервером, цей проксі підтримується SessionStorage, тому навіть якщо користувач оновлює свою веб-сторінку, зберігається стан навіть випадаючих меню тощо. IOW: Я взагалі не використовую стан, все контролюється за допомогою реквізиту.
Кіт,


5

Ви можете (ab) використовувати звичайні хуки, щоб примусити рендерінг, скориставшись тим, що React не друкує логічні значення в коді JSX

// create a hook
const [forceRerender, setForceRerender] = React.useState(true);

// ...put this line where you want to force a rerender
setForceRerender(!forceRerender);

// ...make sure that {forceRerender} is "visible" in your js code
// ({forceRerender} will not actually be visible since booleans are
// not printed, but updating its value will nonetheless force a
// rerender)
return (
  <div>{forceRerender}</div>
)


1
У цьому випадку, коли setBoolean двічі змінюється, дочірній React.useEffect може не відновити оновлення.
alma

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

1
Не знаю. Чомусь ця версія мене приваблює. Мене лоскоче :-). Крім того, він відчуває себе чистим. Щось змінилося, від чого залежить мій JSX, тому я відтворюю. Це невидимість не зменшує цю ІМО.
Адріан Варфоломій,

4

Потенційний варіант - примусове оновлення лише для певного компонента key. Оновлення ключа запускає візуалізацію компонента (який раніше не вдалося оновити)

Наприклад:

const [tableKey, setTableKey] = useState(1);
...

useEffect(() => {
    ...
    setTableKey(tableKey + 1);
}, [tableData]);

...
<DataTable
    key={tableKey}
    data={tableData}/>

1
Це найчастіше найчистіший спосіб, якщо існує співвідношення 1: 1 між значенням стану та вимогою повторно відтворити.
jaimefps 02.03.20

1
це просте рішення
Приянка V


3

Рішення в один рядок:

const useForceUpdate = () => useState()[1];

useState повертає пару значень: поточний стан та функцію, яка його оновлює - стан та сеттер , тут ми використовуємо лише сетер для примусового повторного відтворення.


2

Моя варіація forceUpdateне через a, counterа через об'єкт:

// Emulates `forceUpdate()`
const [unusedState, setUnusedState] = useState()
const forceUpdate = useCallback(() => setUnusedState({}), [])

Тому що {} !== {}кожного разу.


Що це таке useCallback()? Звідки це взялося? ой. Я бачу це зараз ...
zipzit

2

Це зробить залежні компоненти 3 рази (масиви з однаковими елементами не рівні):

const [msg, setMsg] = useState([""])

setMsg(["test"])
setMsg(["test"])
setMsg(["test"])

1
Я вважаю, що вам навіть не потрібно поміщати елемент у масив. Порожній масив не суворо дорівнює іншому порожньому масиву просто за різними посиланнями, як і об'єкти.
Qwerty

так, просто хотів показати це як спосіб передачі даних
Янек Ольшак

2

react-tidyмає спеціальний хук лише для того, щоб зробити так зване useRefresh:

import React from 'react'
import {useRefresh} from 'react-tidy'

function App() {
  const refresh = useRefresh()
  return (
    <p>
      The time is {new Date()} <button onClick={refresh}>Refresh</button>
    </p>
  )
}

Дізнайтеся більше про цей гачок

Застереження. Я є автором цієї бібліотеки.


1

Щодо звичайних компонентів на основі класу React, зверніться до React Docs для forceUpdateapi за цією URL-адресою. У документах згадується, що:

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

Однак у документах також згадується, що:

Якщо ваш метод render () залежить від деяких інших даних, ви можете сказати React, що компонент потребує повторного відтворення, викликавши forceUpdate ().

Отже, хоча випадки використання для використання forceUpdate можуть бути рідкістю, і я не використовував їх ніколи, проте я бачив, як їх використовували інші розробники в деяких застарілих корпоративних проектах, над якими я працював.

Отже, щодо еквівалентної функціональності для функціональних компонентів зверніться до React Docs для HOOKS за цією URL-адресою. За вказаною вище URL-адресою можна скористатися гачком "useReducer", щоб забезпечити forceUpdateфункціональність функціональних компонентів.

that does not use state or propsНижче наведено зразок робочого коду , який також доступний у CodeSandbox за цією URL-адресою

import React, { useReducer, useRef } from "react";
import ReactDOM from "react-dom";

import "./styles.css";

function App() {
  // Use the useRef hook to store a mutable value inside a functional component for the counter
  let countref = useRef(0);

  const [, forceUpdate] = useReducer(x => x + 1, 0);

  function handleClick() {
    countref.current++;
    console.log("Count = ", countref.current);
    forceUpdate(); // If you comment this out, the date and count in the screen will not be updated
  }

  return (
    <div className="App">
      <h1> {new Date().toLocaleString()} </h1>
      <h2>You clicked {countref.current} times</h2>
      <button
        onClick={() => {
          handleClick();
        }}
      >
        ClickToUpdateDateAndCount
      </button>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

ПРИМІТКА. За цією URL-адресою також доступний альтернативний підхід із використанням гачка useState (замість useReducer) .

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