如何在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>
}
}

但我的问题是,我如何才能强制上述功能组件重新渲染立即与挂钩?

353787 次浏览

你最好只让你的组件依赖于状态和道具,它会像预期的那样工作,但如果你真的需要一个函数来强制组件重新呈现,你可以使用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>

这可以通过useStateuseReducer实现,因为__ABC0在内部使用useReducer:

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

forceUpdate不打算在正常情况下使用,只在测试或其他突出的情况下使用。这种情况可以用更常规的方法来解决。

setCount是不正确使用forceUpdate的一个例子,由于性能原因,setState是异步的,不应该仅仅因为状态更新没有正确执行而强制为同步的。如果一个状态依赖于之前设置的状态,这应该用更新功能来完成,

如果需要基于前面的状态设置状态,请阅读下面的updater参数。

& lt;……比;

updater函数接收到的状态和道具都是保证的 与时俱进。与更新程序的输出进行浅合并 状态。< / p >

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 }));
}, []);

正如其他人所提到的,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

你可以像这样简单地定义useState:

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

用法:forceUpdate(n => !n)

希望这对你有所帮助!

你可以(ab)利用JSX代码中的React不打印布尔值这一事实,使用普通钩子强制渲染

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


潜在的选项是使用key仅在特定组件上强制更新。更新键会触发组件的呈现(之前更新失败)

例如:

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


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


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

对于常规的基于React类的组件,请参考React Docs中的forceUpdate api ( URL)。文件中提到:

通常情况下,你应该尽量避免使用forceUpdate() 读一读。道具和这个。状态在render()

然而,在文档中也提到:

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

所以,虽然使用forceUpdate的用例可能很少,而且我从来没有使用过它,但是我在我工作过的一些遗留公司项目中看到过其他开发人员使用它。

因此,对于功能组件的等效功能,请参考React Docs中的 URL中的HOOKS。根据上面的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中使用。

@MinhKha的回答:

使用useReducer可以更简洁:

const [, forceUpdate] = useReducer(x => x + 1, 0);
< p >用法: forceUpdate() -没有参数的清洁器

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

与打印稿

< a href = " https://codesandbox。Io /s/useforceupdate-hook-sd8xi" rel="noreferrer">code andbox example .

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] from useState()需要一个参数。我们通过绑定一个新对象{}来摆脱它。
useReducer中的() => ({})是一个虚拟减速器,每次调度操作时返回一个新对象。
{} (新鲜的对象)是必需的,以便它通过改变状态中的引用来触发更新

PS: useState只是在内部包装useReducer,所以使用reducer来降低复杂性。

注:参考不稳定性

.binduseState一起使用会导致呈现之间的函数引用发生变化。
可以像在这里的答案中解释了一样将它包装在useCallback中,但这样它就不是性感的小笑话™了。渲染之间的减速器版本已经保持参考相等(稳定性)。如果你想将props中的forceUpdate函数传递给另一个组件,这很重要

平原JS

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

我对forceUpdate的变化不是通过counter,而是通过一个对象:

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

因为每次都{} !== {}

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"])

单行解决方案:

const [,forceRender] = useReducer((s) => s+1, 0)
你可以在这里了解useReducer。 https://reactjs.org/docs/hooks-reference.html#usereducer < / p >

简单的代码

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

使用:

forceUpdate();

一句话解决方案:

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

useState返回一对值:当前状态和更新状态的函数——状态setter,这里我们只使用setter来强制重新呈现。

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({});

< a href = " https://codesandbox.io/s/force-re-render-hook-3rlm3?file=/src/App。tsx" rel="nofollow noreferrer">代码框示例 . tsx" rel="nofollow noreferrer">

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

使用

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

有点晚了,但我注意到大多数(所有)的答案都错过了可以传递回调到forceUpdate生命周期方法的部分。

根据React源代码,此回调具有与setState方法中的行为相同的行为-它在更新后执行。

因此,最正确的实现应该是这样的:

    /**
* Increments the state which causes a rerender and executes a callback
* @param {function} callback - callback to execute after state update
* @returns {function}
*/
export const useForceUpdate = (callback) => {
const [state, updater] = useReducer((x) => x + 1, 0);


useEffect(() => {
callback && callback();
}, [state]);


return useCallback(() => {
updater();
}, []);
};

我在使用一个数组时发现了这个问题。然而,我发现了另一种方法,而不是显式的forceUpdate——解构一个数组,并使用下面的代码为它设置一个新值:

    setRoutes(arr => [...arr, newRoute]); // add new elements to the array
setRouteErrors(routeErrs => [...routeErrs]); // the array elements were changed

我发现非常有趣的是,即使设置一个数组的副本也不会触发钩子。我认为React只做了浅层的比较