Как я могу заставить компонент повторно визуализироваться с помощью хуков в 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>
        }
 }

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

Можете ли вы опубликовать версию вашего оригинального компонента, использующего this.forceUpdate()? Может быть, есть способ добиться того же и без этого.

Jacob 08.11.2018 21:04

Последняя строка в setCount усечена. Непонятно, какова цель setCount в его текущем состоянии.

Estus Flask 08.11.2018 21:42

Это просто действие после this.forceUpdate (); Я добавил это просто, чтобы объяснить this.forceUpdate () в моем вопросе

Hemadri Dasari 09.11.2018 03:26

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

Alexander Nied 04.04.2020 18:51
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
175
4
265 268
18
Перейти к ответу Данный вопрос помечен как решенный

Ответы 18

Желательно, чтобы ваш компонент зависел только от состояния и свойств, и он будет работать так, как ожидалось, но если вам действительно нужна функция для принудительного повторного рендеринга компонента, вы можете использовать ловушку 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>

Хорошо, но почему React представил this.forceUpdate (); в первую очередь, когда компонент повторно отрисовывается с помощью setState в более ранних версиях?

Hemadri Dasari 08.11.2018 21:19

@ Think-Twice Я лично никогда не использовал его, и сейчас я не могу придумать для него хороший вариант использования, но я думаю, что это спасательный люк для тех действительно особых случаев использования. «Обычно вам следует избегать любого использования forceUpdate() и читать только с this.props и this.state в render()».

Tholle 08.11.2018 21:21

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

Hemadri Dasari 09.11.2018 03:29

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

Keith 11.12.2018 11:39
Ответ принят как подходящий

Это возможно с useState или useReducer, поскольку useState использует useReducer внутри:

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

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

setCount является примером неправильно используемого forceUpdate, setState является асинхронным по соображениям производительности и не должен быть синхронным только потому, что обновления состояния не были выполнены правильно. Если состояние зависит от ранее установленного состояния, это должно быть выполнено с помощью функция обновления,

If you need to set the state based on the previous state, read about the updater argument below.

<...>

Both state and props received by the updater function are guaranteed to be up-to-date. The output of the updater is shallowly merged with state.

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({}), []);? Это вообще принудительное обновление?

Dávid Molnár 07.11.2019 11:44

@ DávidMolnár useCallback запоминает forceUpdate, поэтому он остается неизменным в течение срока службы компонентов и может безопасно передаваться в качестве опоры. updateState({}) обновляет состояние новым объектом при каждом вызове forceUpdate, это приводит к повторному рендерингу. Так что да, он вызывает обновление при вызове.

Estus Flask 07.11.2019 12:02

Так что деталь useCallback на самом деле не нужна. Он должен нормально работать и без него.

Dávid Molnár 07.11.2019 12:03

А с updateState(0) это не сработает, потому что 0 - это примитивный тип данных? Должен ли это быть объект или updateState([]) (то есть использование с массивом) тоже подойдет?

Andru 22.10.2020 11:47

@Andru Да, состояние обновляется один раз, потому что 0 === 0. Да, массив будет работать, потому что это тоже объект. Можно использовать все, что не проходит проверку на равенство, например updateState(Math.random()) или счетчик.

Estus Flask 22.10.2020 12:17

@EstusFlask На самом деле сравнение выполняется с использованием Object.is - который отличается, поскольку Number.NaN равен самому себе для сравнения идентичности (и аналогично +0 и -0 различны).

paul23 06.02.2021 02:03

Одно незначительное различие между setState и forceUpdate состоит в том, что forceUpdate пропускает вызов shouldComponentUpdate. А вот с хуками нет возможности пропустить React.memo.

Easwar 18.02.2021 14:26

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

Насколько я понимаю из хуков, это может не сработать, поскольку useForceUpdate будет возвращать новую функцию каждый раз, когда функция перерисовывается. Чтобы forceUpdate работал при использовании в useEffect, он должен возвращать useCallback(update) См. kentcdodds.com/blog/usememo-and-usecallback

Martin Ratinaud 12.09.2019 06:54

Спасибо, @MartinRatinaud - да, это могло вызвать утечку памяти без использования useCallback (?) - исправлено.

Brian Burns 13.09.2019 09:20

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

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

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

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

Не удастся, если forceUpdate вызывается четное количество раз за рендеринг.

Izhaki 08.11.2019 00:17

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

Gary 10.03.2020 03:58

Это подвержено ошибкам и должно быть удалено или отредактировано.

slikts 02.04.2020 11:58

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

В этом случае, когда setBoolean изменяет дважды, дочерние элементы React.useEffect могут не распознать обновление.

alma 11.09.2019 04:43

Насколько я понимаю, React рассматривает каждое логическое обновление как причину для повторного рендеринга страницы, даже если логическое значение быстро переключается обратно. Тем не менее, React, конечно, не является стандартом, и то, как он работает в этом случае, не определено и может быть изменено.

Fergie 11.09.2019 10:00

Я не знаю. Почему-то меня привлекает эта версия. Меня это щекочет :-). Кроме того, он чистый. Что-то изменилось, от чего зависит мой JSX, поэтому я перерисовываю. Его невидимость не умаляет ИМО.

Adrian Bartholomew 04.08.2020 17:17

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

Например:

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

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

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

Часто это самый чистый способ, если существует соотношение 1: 1 между значением состояния и требованием повторной визуализации.

jaimefps 02.03.2020 23:45

это простое решение

Priyanka V 29.08.2020 19:07

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

Normally you should try to avoid all uses of forceUpdate() and only read from this.props and this.state in render()

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

If your render() method depends on some other data, you can tell React that the component needs re-rendering by calling 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.info("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-адресу это.

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

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

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

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

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

С TypeScript

codeandbox пример

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]

Разве это не приведет к тому, что хуки будут вызываться разное количество раз между рендерами, если forceUpdate вызывается условно, что нарушит правила хуков и потенциально оставит хуки для доступа к неправильным данным?

user56reinstatemonica8 02.03.2021 19:49

@ user56reinstatemonica8 По сути, это просто назначение состояния, которое запускает рендеринг, ничего необычного.

Qwerty 02.03.2021 20:27

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

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

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

Что такое useCallback()? Откуда это пришло? ой. Теперь я это вижу ...

zipzit 28.03.2020 00:17

Официальное решение Часто задаваемые вопросы по 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>

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

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

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

Я считаю, что вам даже не нужно помещать элемент в массив. Пустой массив не может быть строго равен другому пустому массиву просто по другой ссылке, как и объекты.

Qwerty 05.02.2020 14:37

да, просто хотел показать это как способ передачи данных

Janek Olszak 06.02.2020 15:20

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

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

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

Простой код

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

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

forceUpdate();

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

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

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

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

Узнать больше об этом крючке

Отказ от ответственности Я автор этой библиотеки.

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

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

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

// Anywhre
forceRender({});

Codesandbox Пример

const useForceRender = () => {
  const [, forceRender] = useReducer(x => !x, true)
  return forceRender
}

использование

function Component () {
  const forceRender = useForceRender() 
  useEffect(() => {
    // ...
    forceRender()
  }, [])

Другие вопросы по теме