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

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

飞云路易2020/03/18 17:13:50

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

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

setMsg(["test"])
setMsg(["test"])
setMsg(["test"])
Sam神乐番长2020/03/18 17:13:50

我的变化forceUpdate不是通过a counter而是通过对象:

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

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

APro2020/03/18 17:13:50

现在还有一个小的npm软件包use-force-update由于功能的命名和封装清晰,我认为它比此处介绍的解决方法更好。

猪猪阿飞2020/03/18 17:13:50

潜在的选择是使用强制仅在特定组件上进行更新key更新密钥会触发组件渲染(之前无法更新)

例如:

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

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

...
<DataTable
    key={tableKey}
    data={tableData}/>
西门樱Eva2020/03/18 17:13:50

对于常规的基于React Class的组件,请forceUpdate URL上参考api的React Docs 该文档提到:

通常,您应该避免使用forceUpdate(),而只能从render()中的this.props和this.state中读取

但是,在文档中也提到:

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

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

因此,对于功能组件的等效功能,请参考 URL 上的《 React Docs for HOOKS》根据上述URL,可以使用“ useReducer”钩子forceUpdate为功能组件提供功能。

that does not use state or props下面提供了一个有效的代码示例,也可以在 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);

注意: URL上还提供了一种使用useState挂钩(而不是useReducer)的替代方法

Tom村村2020/03/18 17:13:50

这是React Hooks FAQ中的官方解决方案(forceUpdate+功能组件)

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

  return (
    <div>
      <h3 onClick={forceUpdate}>Click me!</h3>
      <p>Rendered {_count} times</p>
    </div>
  );
};

ReactDOM.render(<Test />, 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>

Near小哥西门2020/03/18 17:13:50

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

NearHarrySam2020/03/18 17:13:50

替代@MinhKha的答案:

使用以下命令可以更加清洁useReducer

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

用法: forceUpdate()-无参数的清洁剂

猪猪GO2020/03/18 17:13:50

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

使用TypeScript

codeandbox示例

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
}

如何运作?

"To trigger an update" means to tell React engine that some value has changed and that it should rerender your component.

[, setState] from useState() requires a parameter. We get rid of it by binding a fresh object {}.
() => ({}) in useReducer is a dummy reducer that returns a fresh object each time an action is dispatched.
{} (fresh object) is required so that it triggers an update by changing a reference in the state.

PS: useState just wraps useReducer internally. source

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

纯JS

const forceUpdate = React.useState()[1].bind(null, {})  // see NOTE above
const forceUpdate = React.useReducer(() => ({}))[1]
樱小小小小2020/03/18 17:13:50

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

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

和用法: forceUpdate(n => !n)

希望对您有所帮助!

JinJin神奇宝儿2020/03/18 17:13:49

您最好只让您的组件依赖状态和道具,并且可以按预期工作,但是如果您确实需要一个函数来强制重新渲染组件,则可以使用useStatehook并在需要时调用该函数。

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>

Eva梅2020/03/18 17:13:49

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

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

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

setCount是不当使用的一个例子forceUpdatesetState出于性能原因是异步的,不应仅由于状态更新未正确执行而被迫同步。如果状态依赖于先前设置的状态,则应使用updater函数来完成

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

<...>

更新器功能接收到的状态和道具都保证是最新的。更新器的输出与state进行浅层合并。

setCount 可能不是说明性的示例,因为其用途尚不清楚,但updater函数就是这种情况:

setCount(){
  this.setState(({count}) => ({ count: count + 1 }));
  this.setState(({count2}) => ({ count2: count + 1 }));
  this.setState(({count}) => ({ count2: count + 1 }));
}