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

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

Ответы

Ответ 1

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

const [, updateState] = React.useState();
const forceUpdate = 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 }));
}

Ответ 2

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

Ответ 3

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

Ответ 4

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

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

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

Надеюсь, это поможет!

Ответ 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>
)

Ответ 6

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

Например:

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

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

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

Ответ 7

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

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

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

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

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

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

Ответ 8

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

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

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

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