我如何强制组件在 React 中使用钩子重新渲染?
How can I force a component to re-render with hooks in 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 组件中立即重新渲染,如下例
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
钩子并在需要时调用函数。
例子
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>
使用 useState
或 useReducer
是可能的,因为 useState
uses useReducer
internally:
const [, updateState] = React.useState();
const forceUpdate = React.useCallback(() => updateState({}), []);
forceUpdate
不打算在正常情况下使用,仅在测试或其他未决情况下使用。这种情况可以用更传统的方式解决。
setCount
是不正确使用的示例 forceUpdate
,setState
出于性能原因是异步的,不应仅仅因为状态更新未正确执行而强制同步.如果状态依赖于先前设置的状态,则应使用 updater function、
来完成
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 }));
}, []);
正如其他人所提到的,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 and https://github.com/mobxjs/mobx-react-lite/blob/master/src/useObserver.ts
您可以像这样简单地定义 useState:
const [, forceUpdate] = React.useState(0);
和用法:forceUpdate(n => !n)
希望对您有所帮助!
您可以利用 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>
)
可能的选项是使用 key
仅在特定组件上强制更新。更新key触发组件渲染(之前更新失败)
例如:
const [tableKey, setTableKey] = useState(1);
...
useEffect(() => {
...
setTableKey(tableKey + 1);
}, [tableData]);
...
<DataTable
key={tableKey}
data={tableData}/>
对于常规的基于 React Class 的组件,请参阅 forceUpdate
api this URL 中的 React 文档。文档提到:
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
的用例可能很少见,而且我也从未使用过它,但是我看到其他开发人员在我从事的一些遗留公司项目中使用了它。
因此,对于功能组件的等效功能,请参阅位于 this URL 的 HOOKS 的 React 文档。根据上述 URL,可以使用 "useReducer" 挂钩为功能组件提供 forceUpdate
功能。
下面提供了一个工作代码示例 that does not use state or props
,也可以在 this 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);
注意:this URL.
也提供了使用 useState 挂钩(而不是 useReducer)的替代方法
替代@MinhKha 的回答:
使用 useReducer
可以更干净:
const [, forceUpdate] = useReducer(x => x + 1, 0);
用法:
forceUpdate()
- 没有参数的清洁器
通常,您可以使用任何您想要触发更新的状态处理方法。
使用 TypeScript
使用状态
const forceUpdate: () => void = React.useState()[1].bind(null, {}) // see NOTE below
使用Reducer
const forceUpdate = React.useReducer(() => ({}), {})[1] as () => void
作为自定义挂钩
像这样包装您喜欢的任何方法
function useForceUpdate(): () => void {
return React.useReducer(() => ({}), {})[1] as () => void // <- paste here
}
这是如何工作的?
"触发更新" 意味着告诉 React 引擎一些值已经改变并且它应该重新渲染你的组件。
[, setState]
来自 useState()
需要一个参数。我们通过绑定一个新对象来摆脱它 {}
.
useReducer
中的 () => ({})
是一个虚拟 reducer,每次调度一个动作时 returns 一个新对象。
{}
(新对象) 是必需的,以便它通过更改状态中的引用来触发更新。
PS:useState
只是在内部包装了 useReducer
。 source
注意: 将 .bind 与 useState 一起使用会导致渲染之间的函数引用发生变化。可以像 一样将它包装在 useCallback 中,但那样它就不会是一个 性感的 one-liner™。 Reducer 版本 已经在渲染之间保持 引用相等。如果你想在 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({}), [])
因为{} !== {}
每次都是。
React Hooks FAQforceUpdate
的官方解决方案:
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
简单代码
const forceUpdate = React.useReducer(bool => !bool)[1];
使用:
forceUpdate();
一行解决方案:
const useForceUpdate = () => useState()[1];
useState returns 一对值:当前状态和更新它的函数 - state 和 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({});
const useForceRender = () => {
const [, forceRender] = useReducer(x => !x, true)
return forceRender
}
用法
function Component () {
const forceRender = useForceRender()
useEffect(() => {
// ...
forceRender()
}, [])
考虑以下钩子示例
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 组件中立即重新渲染,如下例
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
钩子并在需要时调用函数。
例子
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>
使用 useState
或 useReducer
是可能的,因为 useState
uses useReducer
internally:
const [, updateState] = React.useState();
const forceUpdate = React.useCallback(() => updateState({}), []);
forceUpdate
不打算在正常情况下使用,仅在测试或其他未决情况下使用。这种情况可以用更传统的方式解决。
setCount
是不正确使用的示例 forceUpdate
,setState
出于性能原因是异步的,不应仅仅因为状态更新未正确执行而强制同步.如果状态依赖于先前设置的状态,则应使用 updater function、
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 }));
}, []);
正如其他人所提到的,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 and https://github.com/mobxjs/mobx-react-lite/blob/master/src/useObserver.ts
您可以像这样简单地定义 useState:
const [, forceUpdate] = React.useState(0);
和用法:forceUpdate(n => !n)
希望对您有所帮助!
您可以利用 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>
)
可能的选项是使用 key
仅在特定组件上强制更新。更新key触发组件渲染(之前更新失败)
例如:
const [tableKey, setTableKey] = useState(1);
...
useEffect(() => {
...
setTableKey(tableKey + 1);
}, [tableData]);
...
<DataTable
key={tableKey}
data={tableData}/>
对于常规的基于 React Class 的组件,请参阅 forceUpdate
api this URL 中的 React 文档。文档提到:
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
的用例可能很少见,而且我也从未使用过它,但是我看到其他开发人员在我从事的一些遗留公司项目中使用了它。
因此,对于功能组件的等效功能,请参阅位于 this URL 的 HOOKS 的 React 文档。根据上述 URL,可以使用 "useReducer" 挂钩为功能组件提供 forceUpdate
功能。
下面提供了一个工作代码示例 that does not use state or props
,也可以在 this 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);
注意:this URL.
也提供了使用 useState 挂钩(而不是 useReducer)的替代方法替代@MinhKha 的回答:
使用 useReducer
可以更干净:
const [, forceUpdate] = useReducer(x => x + 1, 0);
用法:
forceUpdate()
- 没有参数的清洁器
通常,您可以使用任何您想要触发更新的状态处理方法。
使用 TypeScript
使用状态
const forceUpdate: () => void = React.useState()[1].bind(null, {}) // see NOTE below
使用Reducer
const forceUpdate = React.useReducer(() => ({}), {})[1] as () => void
作为自定义挂钩
像这样包装您喜欢的任何方法
function useForceUpdate(): () => void {
return React.useReducer(() => ({}), {})[1] as () => void // <- paste here
}
这是如何工作的?
"触发更新" 意味着告诉 React 引擎一些值已经改变并且它应该重新渲染你的组件。
[, setState]
来自 useState()
需要一个参数。我们通过绑定一个新对象来摆脱它 {}
.
useReducer
中的 () => ({})
是一个虚拟 reducer,每次调度一个动作时 returns 一个新对象。
{}
(新对象) 是必需的,以便它通过更改状态中的引用来触发更新。
PS:useState
只是在内部包装了 useReducer
。 source
注意: 将 .bind 与 useState 一起使用会导致渲染之间的函数引用发生变化。可以像
纯 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({}), [])
因为{} !== {}
每次都是。
React Hooks FAQforceUpdate
的官方解决方案:
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
简单代码
const forceUpdate = React.useReducer(bool => !bool)[1];
使用:
forceUpdate();
一行解决方案:
const useForceUpdate = () => useState()[1];
useState returns 一对值:当前状态和更新它的函数 - state 和 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({});
const useForceRender = () => {
const [, forceRender] = useReducer(x => !x, true)
return forceRender
}
用法
function Component () {
const forceRender = useForceRender()
useEffect(() => {
// ...
forceRender()
}, [])