如何强制组件在 React 中使用钩子重新渲染?

IT技术 javascript reactjs react-native react-hooks
2021-01-26 05:31:15

考虑下面的钩子示例

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

但我的查询是如何强制上述功能组件立即使用钩子重新渲染?

6个回答

这可以使用useStateuseReducer,因为在内部useState使用useReducer

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

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

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

如果您需要根据之前的状态设置状态,请阅读下面的更新程序参数。

<...>

updater 函数接收到的 state 和 props 都保证是最新的。更新器的输出与状态浅合并。

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 }));
   }, []);
如何const forceUpdate = useCallback(() => updateState({}), []);工作?它甚至会强制更新吗?
2021-03-18 05:31:15
@DávidMolnár useCallbackmemoizes forceUpdate,因此它在组件生命周期内保持不变,并且可以安全地作为道具传递。updateState({})在每次forceUpdate调用时用新对象更新状态,这会导致重新渲染。所以是的,它在被调用时强制更新。
2021-04-09 05:31:15
@Andru 是的,状态更新一次,因为 0===0。是的,数组会起作用,因为它也是一个对象。任何未通过相等性检查的都可以使用,例如updateState(Math.random()),或计数器。
2021-04-09 05:31:15
setState之间的一个可以忽略不计的区别forceUpdate是,forceUpdate跳过shouldComponentUpdate调用。但是对于钩子,没有选项可以跳过React.memo
2021-04-10 05:31:15
所以,这useCallback部分并不是真正必要的。没有它它应该可以正常工作。
2021-04-13 05:31:15

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

使用typescript

代码沙盒示例

使用状态

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

使用减速机

const forceUpdate = React.useReducer(() => ({}), {})[1] as () => void

作为自定义钩子

只需像这样包装您喜欢的任何方法

function useForceUpdate(): () => void {
  return React.useReducer(() => ({}), {})[1] as () => void // <- paste here
}

这是如何工作的?

触发更新”的意思是告诉 React 引擎某些值已经改变,它应该重新渲染你的组件。

[, setState]fromuseState()需要一个参数。我们通过绑定一个新对象来摆脱它{}
() => ({})inuseReducer是一个虚拟的reducer,每次分派动作时都会返回一个新对象。
{} (fresh object)是必需的,以便它通过更改状态中的引用来触发更新。

PS:useState只是在useReducer内部包装来源

注意:将 .bind 与 useState 一起使用会导致渲染之间的函数引用发生变化。可以将它包装在 useCallback 中,正如这里已经解释的那样,但它不会是一个性感的 one-liner™Reducer 版本已经在渲染之间保持引用相等。如果你想在 props 中传递 forceUpdate 函数,这很重要。

纯JS

const forceUpdate = React.useState()[1].bind(null, {})  // see NOTE above
const forceUpdate = React.useReducer(() => ({}))[1]
@ user56reinstatemonica8 这基本上只是一个触发渲染的状态分配,没什么奇怪的。
2021-03-27 05:31:15
如果forceUpdate有条件地调用,这会不会导致钩子在渲染之间被调用的次数不同,这会破坏钩子的规则并可能让钩子访问错误的数据?
2021-04-07 05:31:15

React Hooks FAQ官方解决方案forceUpdate

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

工作示例

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

谢谢,@MartinRatinaud - 是的,如果没有 useCallback (?),它可能会导致内存泄漏 - 已修复。
2021-04-04 05:31:15
根据我对钩子的理解,这可能不起作用,因为useForceUpdate每次重新渲染函数时都会返回一个新函数。为了forceUpdate在 a 中使用时工作useEffect,它应该返回useCallback(update)参见kentcdodds.com/blog/usememo-and-usecallback
2021-04-14 05:31:15

替代@MinhKha 的回答:

它可以更清洁useReducer

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

用法: forceUpdate()- 没有参数的清洁器