如何在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();之后的动作。我添加该信息只是为了解释我的问题
Hemadri Dasari '18

物有所值:我一直在努力,因为我认为我需要手动重新渲染,最后意识到我只需要将外部保存的变量移到状态钩子中并利用设置函数即可解决所有问题,而无需重新渲染。并不是说它永远都不需要,但是值得在第三和第四张外观中查看在特定用例中是否真正需要它。
亚历山大·尼德

Answers:


77

可以使用useState或进行useReducer,因为在内部useState使用useReducer

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

forceUpdate不打算在正常情况下使用,仅在测试或其他未解决的情况下使用。可以以更常规的方式解决这种情况。

setCount是使用不当的例子forceUpdatesetState是异步的性能,而且不应该被强迫只是因为状态更新没有正确执行是同步的。如果状态依赖于先前设置的状态,则应使用updater函数来完成,

如果您需要基于先前的状态来设置状态,请阅读以下有关updater参数的信息。

<...>

更新器功能接收到的状态和道具都保证是最新的。更新器的输出与state合并在一起。

setCount 可能不是说明性的示例,因为其用途尚不清楚,但updater函数就是这种情况:

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-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.tshttps://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(?),可能会导致内存泄漏-已修复。
Brian Burns

27

通常,您可以使用要触发更新的任何状态处理方法。

使用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]fromuseState()需要一个参数。我们通过绑定一个新对象来摆脱它{}
() => ({})inuseReducer是一个虚拟的reducer,它在每次调度动作时都返回一个新对象。
{} (新对象)是必需的,以便它通过更改状态中的引用来触发更新。

PS:useState只是useReducer内部包装。资源

注意:将.bind与useState一起使用会导致渲染之间的函数引用发生更改。可以将其包装在useCallback内,如此处已解释的那样,但是它不是一个性感的单线。Reducer版本已经在渲染之间保持引用相等。如果要在props中传递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偶数次,则将失败。
伊扎基

只是不断增加值。
加里

2
这容易出错,应将其删除或编辑。
slikts

11

React Hooks FAQ官方解决方案forceUpdate

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

工作实例


10

您最好只让您的组件依赖状态和道具,并且可以按预期工作,但是如果您确实需要一个函数来强制重新渲染组件,则可以使用useStatehook并在需要时调用该函数。

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重新渲染时?
Hemadri Dasari

1
@ Think-Twice我个人从来没有使用过它,现在我还没有想到一个好的用例,但是我想对于那些真正特殊的用例来说,这是一个逃生之门。“通常情况下,你应该尽量避免的所有使用forceUpdate()和只读this.props,并this.staterender()”。
Tholle

1
同意 我什至从未用过它,但是我知道它是如何工作的,所以只是想了解如何用钩子做同样的事情
Hemadri Dasari

1
@Tholle and I can't think of a good use case for it right now 我有一个,如果状态不是由React控制的话。我不使用Redux,但我假设它必须进行某种forceupdate。我个人使用代理服务器来维护状态,该组件随后可以检查道具更改,然后进行更新。似乎也非常有效地工作。例如,然后我的所有组件都由一个代理控制,该代理由SessionStorage支持,因此即使用户刷新其网页,也可以保持下拉菜单状态。爱奥华:我根本不使用状态,一切都由道具控制。
基思(Keith)'18


5

通过利用React不在JSX代码中打印布尔值的事实,您可以(ab)使用普通的钩子来强制重新渲染

// 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当然不是一个标准,在这种情况下它的确切工作方式是不确定的,可能会发生变化。
Fergie

1
我不知道。由于某种原因,我被这个版本所吸引。它让我挠痒痒:-)。除此之外,它感觉纯净。我的JSX依赖的东西发生了变化,因此我重新渲染。它的隐蔽性并不影响该IMO。
阿德里安·巴塞洛缪

4

潜在的选择是使用强制仅在特定组件上进行更新key。更新密钥会触发组件渲染(之前无法更新)

例如:

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

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

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

1
如果状态值与重新渲染的要求之间存在1:1的关系,通常这是最干净的方法。
jaimefps

1
这是一个简单的解决方案
Priyanka V


3

一线解决方案:

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

useState返回一对值:当前的状态和功能更新它-状态setter方法,在这里我们只使用setter方法,以武力重新渲染。


2

我的变化forceUpdate不是通过acounter而是通过对象:

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

是的,只是想表明这一点作为传递数据的方式
Janek Olszak

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 Class的组件,请forceUpdateURL上参考api的React Docs 。该文档提到:

通常,您应该避免使用forceUpdate(),而只能从render()中的this.props和this.state中读取

但是,在文档中也提到:

如果您的render()方法依赖于其他数据,您可以通过调用forceUpdate()来告诉React该组件需要重新渲染。

因此,尽管使用的用例forceUpdate可能很少见,而且我从未使用过,但是我看到其他开发人员在我从事过的一些遗留公司项目中使用过。

因此,对于功能组件的等效功能,请参考URL上的《 React Docs for HOOKS》。根据上述URL,可以使用“ useReducer”钩子forceUpdate为功能组件提供功能。

that does not use state or props下面提供了一个有效的代码示例,也可以在URL的CodeSandbox上找到它。

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)的替代方法。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.