【问题标题】:How can I force a component to re-render with hooks in React?如何强制组件使用 React 中的钩子重新渲染?
【发布时间】:2019-04-12 09:46:01
【问题描述】:

考虑下面的钩子示例

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

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

【问题讨论】:

  • 您能否发布使用this.forceUpdate() 的原始组件版本?也许有一种方法可以在没有它的情况下完成同样的事情。
  • setCount 中的最后一行被截断。目前尚不清楚 setCount 在当前状态下的目的是什么。
  • 这只是this.forceUpdate()之后的一个动作;我补充说只是为了在我的问题中解释 this.forceUpdate()
  • 为了它的价值:我正在为此苦苦挣扎,因为我认为我需要手动重新渲染,最后意识到我只需将外部持有的变量移动到状态挂钩并利用设置功能,无需重新渲染即可解决我的所有问题。并不是说它从不需要,但值得第三次和第四次查看它是否在您的特定用例中实际上需要。

标签: javascript reactjs react-native react-hooks


【解决方案1】:

这对于useStateuseReducer 是可能的,因为useState uses useReducer internally

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

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

setCount 是不正确使用forceUpdate 的示例,setState 出于性能原因是异步的,不应仅仅因为未正确执行状态更新而强制同步。如果一个状态依赖于之前设置的状态,这应该使用updater function

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

<...>

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(() =&gt; updateState({}), []); 是如何工作的?它甚至会强制更新吗?
  • @DávidMolnár useCallback memoizes forceUpdate,因此它在组件生命周期内保持不变,并且可以作为 prop 安全地传递。 updateState({}) 在每次 forceUpdate 调用时使用新对象更新状态,这会导致重新渲染。所以是的,它在被调用时强制更新。
  • 因此,useCallback 部分并不是必需的。没有它它应该可以正常工作。
  • @Andru 是的,一个状态会更新一次,因为 0===0。是的,数组会起作用,因为它也是一个对象。可以使用任何未通过相等检查的内容,例如 updateState(Math.random()) 或计数器。
  • setStateforceUpdate 之间的一个可以忽略不计的区别是,forceUpdate 跳过了 shouldComponentUpdate 调用。但是有了钩子,就没有选择跳过React.memo
【解决方案2】:

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

使用 TypeScript

codesandbox example

使用状态

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 引擎某些值已经改变并且它应该重新渲染你的组件。

来自useState()[, setState] 需要一个参数。我们通过绑定一个新对象 {} 来摆脱它。
useReducer 中的() =&gt; ({}) 是一个虚拟化简器,每次调度操作时都会返回一个新对象。
{} (新鲜对象) 是必需的,以便它通过更改状态中的引用来触发更新。

PS:useState 只是在内部包装useReducersource

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

纯JS

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

【讨论】:

  • 如果有条件地调用forceUpdate,这会不会导致在渲染之间调用不同次数的钩子,这会破坏钩子的规则并可能让钩子访问错误的数据?跨度>
  • @user56reinstatemonica8 这基本上只是一个触发渲染的状态分配,没什么奇怪的。
  • 我不得不将以下内容用于 typescript useState 解决方案const forceUpdate: () =&gt; void = React.useState({})[1].bind(null, {}); 否则我收到一个类型错误,即 useState 类型错误
【解决方案3】:

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>

【讨论】:

    【解决方案4】:

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

    【讨论】:

    • 根据我对钩子的理解,这可能不起作用,因为 useForceUpdate 将在每次函数重新渲染时返回一个新函数。要让forceUpdateuseEffect 中使用,它应该返回useCallback(update) 请参阅kentcdodds.com/blog/usememo-and-usecallback
    • 谢谢,@MartinRatinaud - 是的,如果没有 useCallback (?),它可能会导致内存泄漏 - 已修复。
    【解决方案5】:

    @MinhKha 的答案的替代方案:

    useReducer 会更干净:

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

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

    【讨论】:

      【解决方案6】:

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

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

      及用法:forceUpdate(n =&gt; !n)

      希望有帮助!

      【讨论】:

      • 如果每次渲染调用 forceUpdate 的次数为偶数次,则会失败。
      • 只要不断增加值。
      • 这很容易出错,应该删除或编辑。
      【解决方案7】:

      你最好只让你的组件依赖于 state 和 props,它会按预期工作,但是如果你真的需要一个函数来强制组件重新渲染,你可以使用 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>

      【讨论】:

      • 好的,但是为什么 React 引入了 this.forceUpdate();当组件在早期版本中使用 setState 重新渲染时排在首位?
      • @Think-Twice 我个人从未使用过它,我现在也想不出一个好的用例,但我想它是那些真正特殊用例的逃生舱。 "Normally you should try to avoid all uses of forceUpdate() and only read from this.props and this.state in render()."
      • 同意。在我的经验中我什至从未使用过它,但我知道它是如何工作的,所以只是想了解如何在钩子中完成同样的事情
      • @Tholle and I can't think of a good use case for it right now 我有一个,如果状态不受 React 控制怎么办。我不使用 Redux,但我假设它必须执行某种强制更新。我个人使用代理来维护状态,然后组件可以检查道具更改然后更新。似乎也非常有效地工作。例如,然后我的所有组件都由代理控制,该代理由 SessionStorage 支持,因此即使用户刷新他的网页,甚至下拉列表等的状态也会得到维护。 IOW:我根本不使用 state,一切都是用 props 控制的。
      【解决方案8】:

      简单代码

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

      用途:

      forceUpdate();
      

      【讨论】:

        【解决方案9】:

        可能的选项是使用key 仅在特定组件上强制更新。更新密钥会触发组件的渲染(之前更新失败)

        例如:

        const [tableKey, setTableKey] = useState(1);
        ...
        
        useEffect(() => {
            ...
            setTableKey(tableKey + 1);
        }, [tableData]);
        
        ...
        <DataTable
            key={tableKey}
            data={tableData}/>
        

        【讨论】:

        • 如果状态值和重新渲染要求之间存在 1:1 的关系,这通常是最简洁的方法。
        • 这是一个简单的解决方案
        【解决方案10】:

        利用 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>
        )
        
        

        【讨论】:

        • 在这种情况下,当 setBoolean 更改两次时,子 React.useEffect 可能无法识别更新。
        • 据我了解,React 将每个布尔值更新视为重新呈现页面的原因,即使布尔值再次快速切换回来。也就是说,React 当然不是一个标准,它在这种情况下的具体工作方式是未定义的,可能会发生变化。
        • 我不知道。出于某种原因,我被这个版本所吸引。它让我发痒:-)。除此之外,感觉很纯粹。我的 JSX 依赖的东西发生了变化,所以我重新渲染。它的隐身性并没有减损 IMO。
        【解决方案11】:

        一线解决方案:

        const useForceUpdate = () =&gt; useState()[1];

        useState 返回一对值:当前状态和更新它的函数 - statesetter,这里我们只使用setter 以强制重新渲染。

        【讨论】:

          【解决方案12】:

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

          Learn more about this hook

          免责声明我是这个库的作者。

          【讨论】:

            【解决方案13】:

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

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

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

            【讨论】:

            • 什么是useCallback()?那个是从哪里来的?哎呀。 I see it now...
            【解决方案14】:

            单行解决方案:

            const [,forceRender] = useReducer((s) => s+1, 0)
            

            您可以在此处了解 useReducer。 https://reactjs.org/docs/hooks-reference.html#usereducer

            【讨论】:

              【解决方案15】:

              这将渲染依赖组件 3 次(元素相等的数组不相等):

              const [msg, setMsg] = useState([""])
              
              setMsg(["test"])
              setMsg(["test"])
              setMsg(["test"])
              

              【讨论】:

              • 我相信你甚至不需要在数组中放置一个项目。一个空数组并不严格等于另一个空数组,只是通过不同的引用,就像对象一样。
              • 是的,只是想表明这是一种传递数据的方式
              【解决方案16】:

              有很多方法可以在 Hook 中强制重新渲染。

              对我来说,useState() 和引用对象值的提示很简单。

              const [, forceRender] = useState({});
              
              // Anywhre
              forceRender({});
              

              Codesandbox Example

              【讨论】:

                【解决方案17】:

                对于基于 React 类的常规组件,请参阅 forceUpdate api 的 React 文档this URL。文档提到:

                通常你应该尽量避免使用 forceUpdate() 并且只 从 render() 中的 this.props 和 this.state 读取

                但是,文档中也提到:

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

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

                因此,对于功能组件的等效功能,请参阅位于this URL 的 HOOKS 的 React 文档。根据上述 URL,可以使用“useReducer”钩子为功能组件提供forceUpdate 功能。

                下面提供了一个工作代码示例that does not use state or props,它也可以在 CodeSandbox 上的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)的替代方法。

                【讨论】:

                  【解决方案18】:
                  const useForceRender = () => {
                    const [, forceRender] = useReducer(x => !x, true)
                    return forceRender
                  }
                  

                  用法

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

                  【讨论】:

                    猜你喜欢
                    • 2018-12-23
                    • 2016-12-31
                    • 1970-01-01
                    • 2020-10-29
                    • 1970-01-01
                    • 2020-01-26
                    • 2019-11-12
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多