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

Рассматривая ниже пример крючков

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

Но мой вопрос: как я могу заставить вышеуказанный функциональный компонент немедленно перерисовать с помощью хуков?


person Hemadri Dasari    schedule 08.11.2018    source источник
comment
Можете ли вы опубликовать версию вашего исходного компонента, в которой используется this.forceUpdate()? Может быть, есть способ добиться того же и без этого.   -  person Jacob    schedule 08.11.2018
comment
Последняя строка в setCount усечена. Непонятно, какова цель setCount в его текущем состоянии.   -  person Estus Flask    schedule 08.11.2018
comment
Это просто действие после this.forceUpdate (); Я добавил это, чтобы объяснить this.forceUpdate () в моем вопросе   -  person Hemadri Dasari    schedule 09.11.2018
comment
Как бы то ни было: я боролся с этим, потому что думал, что мне нужен ручной повторный рендеринг, и, наконец, понял, что мне просто нужно переместить внешнюю переменную в ловушку состояния и использовать функцию настройки, которая исправила все мои проблемы без повторный рендер. Нельзя сказать, что это никогда не требуется, но стоит взглянуть в третий и четвертый раз, чтобы увидеть, на самом деле это нужно в вашем конкретном случае использования.   -  person Alexander Nied    schedule 04.04.2020


Ответы (17)


Это возможно с useState или useReducer, поскольку _ 3_ использует 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 }));
   }, []);
person Estus Flask    schedule 08.11.2018
comment
Как const forceUpdate = useCallback(() => updateState({}), []); работает? Это вообще принудительное обновление? - person Dávid Molnár; 07.11.2019
comment
@ DávidMolnár useCallback запоминает forceUpdate, поэтому он остается постоянным в течение срока службы компонента и может безопасно передаваться в качестве опоры. updateState({}) обновляет состояние новым объектом при каждом forceUpdate вызове, это приводит к повторному рендерингу. Так что да, он вызывает обновление при вызове. - person Estus Flask; 07.11.2019
comment
Таким образом, часть useCallback на самом деле не нужна. Он должен нормально работать и без него. - person Dávid Molnár; 07.11.2019
comment
И это не сработает с updateState(0), потому что 0 - примитивный тип данных? Это должен быть объект или updateState([]) (т.е. использование с массивом) тоже подойдет? - person Andru; 22.10.2020
comment
@Andru Да, состояние обновляется один раз, потому что 0 === 0. Да, массив будет работать, потому что это тоже объект. Можно использовать все, что не проходит проверку на равенство, например updateState(Math.random()) или счетчик. - person Estus Flask; 22.10.2020
comment
@EstusFlask На самом деле при сравнении используется Object.is - который отличается, поскольку Number.NaN равен самому себе для сравнения идентичности (и аналогично +0 и -0 различны). - person paul23; 06.02.2021
comment
Одно незначительное различие между setState и forceUpdate состоит в том, что forceUpdate пропускает shouldComponentUpdate вызов. Но с хуками нет возможности пропустить React.memo. - person Easwar; 18.02.2021

Как правило, можно использовать любой подход к обработке состояния, при котором требуется запускать обновление.

С 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() требует параметра. Мы избавляемся от него, привязывая новый объект {}.
() => ({}) в useReducer - это фиктивный редуктор, который возвращает новый объект каждый раз при отправке действия.
{} (свежий объект) требуется, чтобы запускать обновление путем изменения ссылки в состоянии.

PS: useState просто внутренняя оболочка useReducer. источник

ПРИМЕЧАНИЕ.. Использование .bind с useState приводит к изменению ссылки на функцию между отрисовками. Его можно обернуть внутри useCallback как уже объяснено здесь, но тогда это не будет сексуальным однострочником ™. Версия Reducer уже сохраняет равенство ссылок между отрисовками. Это важно, если вы хотите передать функцию forceUpdate в props.

простой JS

const forceUpdate = React.useState()[1].bind(null, {})  // see NOTE above
const forceUpdate = React.useReducer(() => ({}))[1]
person Qwerty    schedule 29.10.2019
comment
Разве это не приведет к тому, что хуки будут вызываться разное количество раз между рендерами, если forceUpdate вызывается условно, что нарушит правила хуков и потенциально оставит хуки доступ к неправильным данным? - person user56reinstatemonica8; 02.03.2021
comment
@ user56reinstatemonica8 По сути, это просто назначение состояния, которое запускает рендеринг, ничего необычного. - person Qwerty; 02.03.2021

Как уже упоминали другие, useState работает - вот как mobx-react-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

person Brian Burns    schedule 26.04.2019
comment
Насколько я понимаю из хуков, это может не сработать, поскольку useForceUpdate будет возвращать новую функцию каждый раз, когда функция перерисовывается. Чтобы forceUpdate работал при использовании в useEffect, он должен возвращать useCallback(update) См. kentcdodds.com/blog/usememo -and-usecallback - person Martin Ratinaud; 12.09.2019
comment
Спасибо, @MartinRatinaud - да, это могло вызвать утечку памяти без использования useCallback (?) - исправлено. - person Brian Burns; 13.09.2019

Официальное решение React Hooks для forceUpdate:

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

Рабочий пример

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

  return (
    <div>
      <button onClick={forceUpdate}>Force update</button>
      <p>Forced update {_} times</p>
    </div>
  );
};

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.1/umd/react.production.min.js" integrity="sha256-vMEjoeSlzpWvres5mDlxmSKxx6jAmDNY4zCt712YCI0=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.1/umd/react-dom.production.min.js" integrity="sha256-QQt6MpTdAD0DiPLhqhzVyPs1flIdstR4/R7x4GqCvZ4=" crossorigin="anonymous"></script>
<script>var useReducer = React.useReducer</script>
<div id="root"></div>

person ford04    schedule 05.01.2020

Альтернатива ответу @MinhKha:

Это может быть намного чище с useReducer:

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

Использование: forceUpdate() - очиститель без параметров

person nullhook    schedule 13.10.2019

Вы можете просто определить useState следующим образом:

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

И использование: forceUpdate(n => !n)

Надеюсь на эту помощь!

person Minh Kha    schedule 03.07.2019
comment
Не удастся, если forceUpdate вызывается четное количество раз за рендеринг. - person Izhaki; 08.11.2019
comment
Просто продолжайте увеличивать значение. - person Gary; 10.03.2020
comment
Это подвержено ошибкам и должно быть удалено или отредактировано. - person slikts; 02.04.2020

Желательно, чтобы ваш компонент зависел только от состояния и свойств, и он будет работать так, как ожидалось, но если вам действительно нужна функция для принудительной повторной визуализации компонента, вы можете использовать хук 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/[email protected]/umd/react.production.min.js"></script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.production.min.js"></script>

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

person Tholle    schedule 08.11.2018
comment
Хорошо, но почему React представил this.forceUpdate (); в первую очередь, когда компонент повторно отрисовывается с помощью setState в более ранних версиях? - person Hemadri Dasari; 08.11.2018
comment
@ Think-Twice Я лично никогда не использовал его, и сейчас я не могу придумать для него хороший вариант использования, но я думаю, что это спасательный люк для тех действительно особых случаев использования. Обычно вам следует избегать любого использования forceUpdate() и читать только this.props и this.state в render(). - person Tholle; 08.11.2018
comment
Согласовано. Я даже никогда не использовал это по своему опыту, но я знаю, как это работает, поэтому просто пытаюсь понять, как то же самое можно сделать в хуках. - person Hemadri Dasari; 09.11.2018
comment
@Tholle and I can't think of a good use case for it right now У меня есть один, как насчет того, чтобы состояние не контролировалось React. Я не использую Redux, но предполагаю, что он должен выполнить какое-то принудительное обновление. Я лично использую прокси для поддержания состояния, затем компонент может проверять наличие изменений свойств, а затем обновлять. Кажется, тоже работает очень эффективно. Например, все мои компоненты затем контролируются прокси, этот прокси поддерживается SessionStorage, поэтому даже если пользователь обновляет свою веб-страницу, состояние даже выпадающих списков и т. Д. Сохраняется. IOW: Я вообще не использую состояние, все управляется с помощью props. - person Keith; 11.12.2018

Простой код

const forceUpdate = React.useReducer(bool => !bool)[1];

Использовать:

forceUpdate();
person GilCarvalhoDev    schedule 06.06.2020

Возможный вариант - принудительное обновление только определенного компонента с помощью key. Обновление ключа запускает рендеринг компонента (который раньше не обновлялся)

Например:

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

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

...
<DataTable
    key={tableKey}
    data={tableData}/>
person Idan    schedule 14.07.2019
comment
Часто это самый чистый способ, если существует соотношение 1: 1 между значением состояния и требованием повторной визуализации. - person jaimefps; 03.03.2020
comment
это простое решение - person Priyanka V; 29.08.2020

Вы можете (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>
)

person Fergie    schedule 11.07.2019
comment
В этом случае, когда setBoolean изменяет дважды, дочерние элементы React.useEffect могут не распознавать обновление. - person alma; 11.09.2019
comment
Насколько я понимаю, React рассматривает каждое логическое обновление как причину для повторного рендеринга страницы, даже если логическое значение снова быстро переключается. Тем не менее, React, конечно, не является стандартом, и то, как он работает в этом случае, не определено и может быть изменено. - person Fergie; 11.09.2019
comment
Я не знаю. Почему-то меня привлекает эта версия. Меня это щекочет :-). Кроме того, он чистый. Что-то изменилось, от чего зависит мой JSX, поэтому я перерисовываю. Невидимость не умаляет ИМО. - person Adrian Bartholomew; 04.08.2020

Мой вариант forceUpdate не через counter, а через объект:

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

Потому что {} !== {} каждый раз.

person catamphetamine    schedule 21.11.2019
comment
Что такое useCallback()? Откуда это пришло? ой. Теперь я вижу ... - person zipzit; 28.03.2020

Решение в одной строке:

const [,forceRender] = useReducer((s) => s+1, 0)

Вы можете узнать об useReducer здесь. https://reactjs.org/docs/hooks-reference.html#usereducer

person think-serious    schedule 11.05.2020

Однострочное решение:

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

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

person arielhad    schedule 22.06.2020

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

Подробнее об этом хуке

Заявление об ограничении ответственности. Я автор этой библиотеки.

person webNeat    schedule 08.10.2020

Это будет отображать зависимые компоненты 3 раза (массивы с одинаковыми элементами не равны):

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

setMsg(["test"])
setMsg(["test"])
setMsg(["test"])
person Janek Olszak    schedule 23.01.2020
comment
Я считаю, что вам даже не нужно помещать элемент в массив. Пустой массив не может быть строго равен другому пустому массиву просто по другой ссылке, как и объекты. - person Qwerty; 05.02.2020
comment
да, просто хотел показать это как способ передачи данных - person Janek Olszak; 06.02.2020

Для обычных компонентов на основе React Class см. React Docs для forceUpdate api по адресу this URL. В документах упоминается, что:

Обычно вам следует избегать любого использования forceUpdate () и читать только из this.props и this.state в render ()

Однако в документации также упоминается, что:

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

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

Таким образом, эквивалентную функциональность для функциональных компонентов см. В React Docs for 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);

ПРИМЕЧАНИЕ. Альтернативный подход с использованием ловушки useState (вместо useReducer) также доступен по адресу по этому URL-адресу. .

person Alan C. S.    schedule 01.10.2019

Есть много способов принудительного повторного рендеринга в Hook.

Для меня простой способ с useState() и подсказкой значений эталонного объекта.

const [, forceRender] = useState({});

// Anywhre
forceRender({});

Пример Codesandbox

person nghiepit    schedule 16.04.2021