考虑下面的钩子示例
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>
}
}
但我的问题是,我如何才能强制上述功能组件重新渲染立即与挂钩?
这可以通过useState或useReducer实现,因为useState在内部使用useReducer:
const [, updateState] = React.useState();
const forceUpdate = React.useCallback(() => updateState({}), []);
forceUpdate不打算在正常情况下使用,只能在测试或其他突出的情况下使用。这种情况可以用更常规的方法来解决。
setCount是一个使用不当的forceUpdate的例子,setState是异步的,因为性能原因,不应该仅仅因为状态更新没有正确执行而被强制为同步的。如果一个状态依赖于之前设置的状态,这应该通过updater函数来完成,
如果需要基于前面的状态设置状态,请阅读下面的updater参数。
<…>
updater函数接收的状态和道具都是有保证的
与时俱进。与更新程序的输出进行浅合并
状态。
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.ts和https://github.com/mobxjs/mobx-react-lite/blob/master/src/useObserver.ts
对于常规的基于React类的组件,请参考React Docs中的forceUpdate api。文件中提到:
通常你应该尽量避免使用forceUpdate()
读一读。道具和这个。render()中的状态
然而,在文档中也提到:
如果你的render()方法依赖于其他数据,你可以告诉React
组件需要通过调用forceUpdate()重新渲染。
因此,尽管使用forceUpdate的用例可能很少,而且我从未使用过它,但是我在我所从事的一些遗留企业项目中看到过其他开发人员使用它。
因此,对于功能组件的等效功能,请参考这个URL中的React Docs for HOOKS。根据上面的URL,可以使用“useReducer”钩子为功能组件提供forceUpdate功能。
下面提供了一个不使用状态或道具的工作代码示例,也可以在这个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);
注意:使用useState钩子(而不是useReducer)的另一种方法也可以在这个URL中使用。
通常,您可以使用任何想要触发更新的状态处理方法。
与打印稿
codesandbox例子
使用状态
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引擎某个值发生了变化,它应该重新渲染你的组件。
来自useState()的[,setState]需要一个参数。我们通过绑定一个新对象{}来摆脱它。
() =>({})在useReducer是一个虚拟的减速器,每次调度一个动作时返回一个新的对象。
{} (fresh对象)是必需的,这样它就可以通过改变状态中的引用来触发更新。
PS: useState只是在内部包装了useReducer,所以使用reducer来降低复杂性。源
注:参考不稳定性
使用.bind与useState一起使用会导致呈现之间的函数引用发生变化。
可以将它包装在useCallback中,就像在这里的回答中已经解释的那样,但这样它就不是一个性感的单行程序™了。Reducer版本已经在渲染之间保持了引用相等(稳定性)。如果你想把props中的forceUpdate函数传递给另一个组件,这很重要。
平原JS
const forceUpdate = React.useState({})[1].bind(null, {}) // see NOTE above
const forceUpdate = React.useReducer(() => ({}))[1]
React Hooks FAQ官方解决方案的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>
有点晚了,但我注意到大多数(所有)的答案都错过了可以传递回调到forceUpdate生命周期方法的部分。
根据react源代码,此回调具有与setState方法中的行为相同的行为-它在更新后执行。
因此,最正确的实现应该是这样的:
/**
*增加导致重传并执行回调的状态
* @param {function}回调-状态更新后执行的回调
* @返回{函数}
* /
export const useForceUpdate = (callback) => {
const [state, updater] = useReducer((x) => x + 1,0);
useEffect(() => {
Callback && Callback ();
},[状态]);
返回useCallback(() => {
更新();
},[]);
};