简体   繁体   English

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

[英]How can I force a component to re-render with hooks in React?

Considering below hooks example考虑下面的钩子示例

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

Basically we use this.forceUpdate() method to force the component to re-render immediately in React class components like below example基本上我们使用 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>
        }
 }

But my query is How can I force above functional component to re-render immediately with hooks?但是我的问题是如何强制上面的功能组件立即用钩子重新渲染?

This is possible with useState or useReducer , since useState uses useReducer internally :这可以通过useStateuseReducer ,因为useState useReducer内部使用useReducer

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

forceUpdate isn't intended to be used under normal circumstances, only in testing or other outstanding cases. forceUpdate不打算在正常情况下使用,仅在测试或其他突出情况下使用。 This situation may be addressed in a more conventional way.这种情况可以以更传统的方式解决。

setCount is an example of improperly used forceUpdate , setState is asynchronous for performance reasons and shouldn't be forced to be synchronous just because state updates weren't performed correctly. setCount是使用不当的例子forceUpdatesetState是异步的性能,而且不应该被强迫只是因为状态更新没有正确执行是同步的。 If a state relies on previously set state, this should be done with 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. updater 函数接收到的 state 和 props 都保证是最新的。 The output of the updater is shallowly merged with state.更新器的输出与状态浅合并。

setCount may not be an illustrative example because its purpose is unclear but this is the case for updater function: setCount可能不是一个说明性的例子,因为它的目的不明确,但更新程序函数就是这种情况:

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

This is translated 1:1 to hooks, with the exception that functions that are used as callbacks should better be memoized:这被转换为 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 }));
   }, []);

Generally, you can use any state handling approach you want to trigger an update.通常,您可以使用任何想要触发更新的状态处理方法。

With TypeScript使用打字稿

codesandbox example代码沙盒示例

useState使用状态

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

useReducer使用减速器

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

as custom hook作为自定义钩子

Just wrap whatever approach you prefer like this只需像这样包装您喜欢的任何方法

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

How this works?这是如何工作的?

" To trigger an update " means to tell React engine that some value has changed and that it should rerender your component. 触发更新”的意思是告诉 React 引擎某些值已经改变,它应该重新渲染你的组件。

[, setState] from useState() requires a parameter. [, setState]来自useState()需要一个参数。 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. useReducer中的() => ({})是一个虚拟的 reducer,每次调度 action 时都会返回一个新对象。
{} (fresh object) is required so that it triggers an update by changing a reference in the state. {} (新鲜对象)是必需的,以便它通过更改状态中的引用来触发更新。

PS: useState just wraps useReducer internally. PS: useState只是useReducer内部包装了useReducer source 来源

NOTE: Using .bind with useState causes a change in function reference between renders.注意:将 .bind 与 useState 一起使用会导致渲染之间的函数引用发生变化。 It is possible to wrap it inside useCallback as already explained here , but then it wouldn't be a sexy one-liner™ .可以将它包装在 useCallback 中, 正如这里已经解释的那样,但它不会是一个性感的 one-liner™ The Reducer version already keeps reference equality between renders. Reducer 版本已经在渲染之间保持引用相等。 This is important if you want to pass the forceUpdate function in props.如果你想在 props 中传递 forceUpdate 函数,这很重要。

plain JS纯JS

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

React Hooks FAQ official solution for forceUpdate :针对forceUpdate React Hooks FAQ官方解决方案:

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

Working example工作示例

 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>

As the others have mentioned, useState works - here is how mobx-react-lite implements updates - you could do something similar.正如其他人提到的, useState有效 - 这是mobx-react-lite实现更新的方式 - 你可以做类似的事情。

Define a new hook, useForceUpdate -定义一个新的钩子, useForceUpdate -

import { useState, useCallback } from 'react'

export function useForceUpdate() {
  const [, setTick] = useState(0);
  const update = useCallback(() => {
    setTick(tick => tick + 1);
  }, [])
  return update;
}

and use it in a component -并在组件中使用它 -

const forceUpdate = useForceUpdate();
if (...) {
  forceUpdate(); // force re-render
}

See https://github.com/mobxjs/mobx-react-lite/blob/master/src/utils.ts and https://github.com/mobxjs/mobx-react-lite/blob/master/src/useObserver.tshttps://github.com/mobxjs/mobx-react-lite/blob/master/src/utils.tshttps://github.com/mobxjs/mobx-react-lite/blob/master/src/useObserver .ts

Alternative to @MinhKha's answer:替代@MinhKha 的回答:

It can be much cleaner with useReducer :使用useReducer可以更干净:

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

Usage: forceUpdate() - cleaner without params用法: forceUpdate() - 没有参数的更清洁

You can simply define the useState like that:您可以像这样简单地定义 useState:

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

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

Hope this help !希望这有帮助!

You should preferably only have your component depend on state and props and it will work as expected, but if you really need a function to force the component to re-render, you could use the useState hook and call the function when needed.你最好只让你的组件依赖 state 和 props,它会按预期工作,但如果你真的需要一个函数来强制组件重新渲染,你可以使用useState钩子并在需要时调用该函数。

Example例子

 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>

Simple code简单的代码

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

Use:用:

forceUpdate();

Potential option is to force update only on specific component using key .潜在的选择是使用key仅在特定组件上强制更新。 Updating the key trigger a rendering of the component (which failed to update before)更新密钥会触发组件的渲染(之前未能更新)

For example:例如:

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

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

...
<DataTable
    key={tableKey}
    data={tableData}/>

You can (ab)use normal hooks to force a rerender by taking advantage of the fact that React doesn't print booleans in JSX code您可以(ab)使用普通钩子通过利用React 不会在 JSX 代码中打印布尔值这一事实来强制重新渲染

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

My variation of forceUpdate is not via a counter but rather via an object:我的forceUpdate变体不是通过counter而是通过对象:

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

Because {} !== {} every time.因为每次都{} !== {}

Solution in one single line:单行解决方案:

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

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

One line solution:一行解决方案:

const useForceUpdate = () => useState()[1];

useState returns a pair of values: the current state and a function that updates it - state and setter , here we are using only the setter in order to force re-render. useState返回一对值:当前状态和更新它的函数 - statesetter ,这里我们仅使用 setter 来强制重新渲染。

react-tidy has a custom hook just for doing that called useRefresh : react-tidy有一个自定义钩子, useRefresh用于执行名为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 了解有关此挂钩的更多信息

Disclaimer I am the writer of this library.免责声明我是这个图书馆的作者。

This will render depending components 3 times (arrays with equal elements aren't equal):这将渲染依赖组件 3 次(具有相等元素的数组不相等):

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

setMsg(["test"])
setMsg(["test"])
setMsg(["test"])

For regular React Class based components, refer to React Docs for the forceUpdate api at this URL.对于常规的基于 React 类的组件,请参阅URL 上的forceUpdate api 的 React Docs。 The docs mention that:文档提到:

Normally you should try to avoid all uses of forceUpdate() and only read from this.props and this.state in render()通常你应该尽量避免使用 forceUpdate() 并且只在 render() 中从 this.props 和 this.state 中读取

However, it is also mentioned in the docs that:但是,文档中还提到:

If your render() method depends on some other data, you can tell React that the component needs re-rendering by calling forceUpdate().如果你的 render() 方法依赖于其他一些数据,你可以通过调用 forceUpdate() 告诉 React 该组件需要重新渲染。

So, although use cases for using forceUpdate might be rare, and I have not used it ever, however I have seen it used by other developers in some legacy corporate projects that I have worked on.因此,尽管使用forceUpdate用例可能很少见,而且我从未使用过它,但是我已经看到其他开发人员在我参与的一些遗留公司项目中使用它。

So, for the equivalent functionality for Functional Components, refer to the React Docs for HOOKS at this URL.因此,对于 Functional Components 的等效功能,请参阅URL 上的 HOOKS 的 React Docs。 Per the above URL, one can use the "useReducer" hook to provide a forceUpdate functionality for Functional Components.根据上面的 URL,可以使用“useReducer”钩子为功能组件提供forceUpdate功能。

A working code sample that does not use state or props is provided below, which is also available on CodeSandbox at this URL下面提供了一个that does not use state or props的工作代码示例,它也可以在 CodeSandbox 上的这个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);

NOTE: An alternate approach using the useState hook (instead of useReducer) is also available at this URL.注意:URL 还提供了使用 useState 挂钩(而不是 useReducer)的替代方法。

There are many ways to force re-render in Hook.有很多方法可以在 Hook 中强制重新渲染。

For me simple way with useState() and tip of reference object values.对我来说,使用useState()和参考对象值提示的简单方法。

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

// Anywhre
forceRender({});

Codesandbox Example 代码沙盒示例

const useForceRender = () => {
  const [, forceRender] = useReducer(x => (x + 1) % 2, 0)
  return forceRender
}

Usage用法

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

A bit late to the party but I notice that most (all) of the answers have missed the part where you can pass a callback to forceUpdate lifecycle method.聚会有点晚了,但我注意到大多数(所有)答案都错过了可以将回调传递给 forceUpdate 生命周期方法的部分。

As per the react source code , this callback has the same behavior as the one in the setState method - it is executed after the update.根据反应源代码,此回调与 setState 方法中的行为相同 - 它在更新后执行。

Hence, the most correct implementation would be like this:因此,最正确的实现是这样的:

 /** * Increments the state which causes a rerender and executes a callback * @param {function} callback - callback to execute after state update * @returns {function} */ export const useForceUpdate = (callback) => { const [state, updater] = useReducer((x) => x + 1, 0); useEffect(() => { callback && callback(); }, [state]); return useCallback(() => { updater(); }, []); };

I was working with an array and spotted this issue.我正在使用一个数组并发现了这个问题。 However, instead of explicit forceUpdate I found another approach - to deconstruct an array and set a new value for it using this code:但是,我找到了另一种方法,而不是显式forceUpdate - 解构数组并使用以下代码为其设置新值:

    setRoutes(arr => [...arr, newRoute]); // add new elements to the array
    setRouteErrors(routeErrs => [...routeErrs]); // the array elements were changed

I found it very interesting that setting even a copy of an array will not trigger the hook.我发现非常有趣的是,即使设置数组的副本也不会触发钩子。 I assume React does the shallow comparison我假设 React 进行了浅层比较

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM