你可以强制React组件重新渲染而不调用setState吗?

Phi*_*ton 608 reactjs react-jsx

我有一个外部(对组件),可观察的对象,我想听取更改.当对象更新时,它会发出更改事件,然后我想在检测到任何更改时重新呈现组件.

使用顶级React.render这是可能的,但在一个组件中它不起作用(这有一定意义,因为该render方法只返回一个对象).

这是一个代码示例:

export default class MyComponent extends React.Component {

  handleButtonClick() {
    this.render();
  }

  render() {
    return (
      <div>
        {Math.random()}
        <button onClick={this.handleButtonClick.bind(this)}>
          Click me
        </button>
      </div>
    )
  }
}
Run Code Online (Sandbox Code Playgroud)

单击按钮会在内部调用this.render(),但这不是实际导致渲染发生的原因(您可以看到此操作因为创建的文本{Math.random()}不会更改).但是,如果我只是打电话this.setState()而不是this.render(),它可以正常工作.

所以我想我的问题是: React组件需要有状态才能重新渲染吗?有没有办法强制组件按需更新而不改变状态?

Cro*_*rob 696

在您的组件中,您可以调用this.forceUpdate()强制重新呈现.

文档:https://facebook.github.io/react/docs/component-api.html

  • 另一种方法是this.setState(this.state); (144认同)
  • 虽然`this.forceUpdate()`不是解决问题的一个很好的解决方案,但它是标题中提出的问题的正确答案.虽然通常应该避免,但有些情况下forceUpdate是一个很好的解决方案. (36认同)
  • 不鼓励使用forceupdate,请参阅最后一个答案. (20认同)
  • @kar实际上,可以在`shouldComponentUpdate()`中实现额外的逻辑,使您的解决方案徒劳无功. (7认同)
  • 超出最大调用堆栈大小 (4认同)

piz*_*r0b 301

forceUpdate应该避免,因为它偏离了React的心态.React文档引用了一个forceUpdate可能使用的示例:

默认情况下,当组件的状态或道具发生更改时,组件将重新呈现.但是,如果这些隐式更改(例如:对象内部的数据更改而不更改对象本身)或者如果render()方法依赖于其他一些数据,则可以告诉React需要通过调用重新运行render()强制性升级().

但是,我想提出一个想法,即使使用深层嵌套的对象,forceUpdate也是不必要的.通过使用不可变数据源,跟踪变化变得便宜; 更改将始终生成一个新对象,因此我们只需要检查对象的引用是否已更改.您可以使用库Immutable JS在您的应用程序中实现不可变数据对象.

通常你应该尽量避免使用forceUpdate(),只能在render()中读取this.props和this.state.这使您的组件"纯粹",您的应用程序更简单,更高效.https://facebook.github.io/react/docs/component-api.html#forceupdate

更改要重新渲染的元素的键将起作用.通过状态在元素上设置关键支柱,然后在您想要更新设置状态以获得新密钥时.

<Element key={this.state.key} /> 
Run Code Online (Sandbox Code Playgroud)

然后发生更改并重置密钥

this.setState({ key: Math.random() });
Run Code Online (Sandbox Code Playgroud)

我想要指出,这将取代密钥正在改变的元素.这可能有用的一个示例是当您有一个文件输入字段,您希望在图像上载后重置.

虽然OP问题的真正答案是forceUpdate()我发现这个解决方案在不同情况下有所帮助.我还要注意,如果您发现自己正在使用forceUpdate,可能需要查看您的代码并查看是否有其他方法可以执行操作.

说明1-9-2019:

以上(更改键)将完全取代元素.如果您发现自己更新密钥以进行更改,则可能在代码中的其他位置存在问题.使用Math.random()in键将为每个渲染重新创建元素.我不建议像这样更新密钥,因为react使用密钥来确定重新渲染事物的最佳方法.

  • 这是一个黑客和滥用"关键".首先,它的意图尚不清楚.你的代码的读者必须努力理解为什么你这样使用`key`.其次,这不比`forceUpdate`更纯粹."Pure"React意味着组件的视觉呈现完全取决于其状态,因此如果您更改任何状态,它将更新.但是,如果你有一些深层嵌套的对象(`forceUpdate` docs引用它的理由,那么使用`forceUpdate`就可以清楚了.)第三,`Math.random()`是......随机的.从理论上讲,它可以生成相同的随机数. (77认同)
  • 我有兴趣知道为什么这会有一个downvote?我一直在试图让屏幕阅读器响应咏叹调警报,这是我发现的唯一可行的技术.如果您的UI中有某些内容在每次单击时生成相同的警报,则默认情况下,react不会重新呈现DOM,因此屏幕阅读器不会宣布更改.设置唯一键使其工作.也许downvote是因为它仍然涉及设置状态.但是通过设置键强制重新渲染到DOM是金色的! (14认同)
  • @xtraSimplicity我不能同意这符合React的做事方式.`forceUpdate`是React的做法. (8认同)
  • @atomkirk 我和你有同样的看法,但是,令人惊讶的是,在某些情况下,反应文档批准甚至推荐这种方法:https://reactjs.org/blog/2018/06/07/you-probably-dont-need-派生状态.html (5认同)
  • @Robert Grant,回头看,我同意.增加的复杂性并不值得. (3认同)
  • 我非常喜欢这个答案,因为 - 1:不鼓励使用forceupdate(),2:这种方式对于你通过npm安装的第三方组件非常有用,这意味着不是你自己的组件.例如,React-resizable-and-movable是我使用的第三方组件,但它没有对我的组件setstate做出反应.这是很好的解决方案. (2认同)

Qwe*_*rty 63

实际上,forceUpdate()是唯一正确的解决方案,因为如果在其中简单地返回实现额外的逻辑,则setState()可能不会触发重新渲染.shouldComponentUpdate()false

强制性升级()

调用forceUpdate()将导致render()在组件上调用,跳过shouldComponentUpdate().更多...

的setState()

setState()除非实现条件渲染逻辑,否则将始终触发重新渲染shouldComponentUpdate().更多...


forceUpdate() 可以从您的组件中调用 this.forceUpdate()

  • @ lfender6445在构造函数外部直接使用`this.state`分配状态也应该抛出错误.2016年可能没有这样做; 但是,我很确定它现在就做到了. (4认同)

vij*_*jay 34

我避免forceUpdate做以下事情

错误的方法:不要使用索引作为关键

this.state.rows.map((item, index) =>
   <MyComponent cell={item} key={index} />
)
Run Code Online (Sandbox Code Playgroud)

正确的方法:使用数据id作为密钥,它可以是一些guid等

this.state.rows.map((item) =>
   <MyComponent item={item} key={item.id} />
)
Run Code Online (Sandbox Code Playgroud)

所以通过这样的代码改进,你的组件将是独一无二的并自然地呈现

  • 为了证明我的反对票是合理的,尽管使用这种方法是一种很好的做法,但这个答案与问题无关。 (2认同)

gce*_*edo 32

当您希望两个React组件进行通信时,它们不受关系(父子)的约束,建议使用Flux或类似的体系结构.

你想要做什么是监听的变化观察到的部分店面,其中包含模型及其接口,并保存引起渲染改变的数据stateMyComponent.当商店推送新数据时,您可以更改组件的状态,从而自动触发渲染.

通常你应该尽量避免使用forceUpdate().从文档:

通常你应该尽量避免使用forceUpdate(),只能在render()中读取this.props和this.state.这使您的应用程序更简单,更高效

  • 实际上,建议是将商店数据传递给组件道具,并仅将组件状态用于滚动状态和其他小的ui特定事物.如果您使用[redux](https://npmjs.org/package/redux)(我推荐它),您可以使用[connect](https://github.com/rackt/react-redux/blob/master/react-statetoprops-mapdispatchtoprops-mergeprops-options)函数从`react-redux`根据你提供的映射函数自动将存储状态映射到组件props. (4认同)
  • "应始终避免使用forceUpdate()"是不正确的.文档清楚地说:"通常你应该尽量避免使用forceUpdate()".有一些有效的`forceUpdate`用例 (4认同)
  • 组件状态的内存状态是什么?如果我在页面上有五个组件并且它们都在监听单个存储,那么我是否在内存中有五次数据,或者它们是否引用?并不是所有的听众都快速加起来?为什么"涓滴"比将数据传递给目标更好? (2认同)
  • @AJP you're absolutely right, that was personal bias speaking :) I have edited the answer. (2认同)

Tom*_*dil 29

2021 年,这是强制更新 React 功能组件的官方方式

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

  function handleClick() {
    forceUpdate();
  }
Run Code Online (Sandbox Code Playgroud)

我知道 OP 是用于类组件的。但是这个问题是在 2015 年提出的,现在钩子可用,许多人可能会forceUpdate在功能组件中搜索。这一点是给他们的。

  • 请记住,它仍然说:“如果可能,请尽量避免这种模式”。 (3认同)

Jas*_*ing 14

没有州担心重新渲染

当我开始使用反应时,我正在将我的数据存储在状态中我不得不这样做以进行渲染.那是非常错误的.事实证明,如果你推出一些简单的不可变商店,你甚至可以避免做像Flux和Redux这样复杂的事情.

用于继承和处理商店更新的BaseView

import React, { Component } from 'react'

export default function(/* store1, store2, store3... */) {
  const storeArgs = Array.prototype.slice.call(arguments)
  return function(WrappedComponent) {
    return class WithStore extends Component {
      constructor(props) {
        super(props)
        this.state = {
          lastUpdated: Date.now()
        }
        this.stores = storeArgs
      }

      _onChange = () => {
        this.setState({ lastUpdated: Date.now() })
      }

      componentWillMount = () => {
        this.stores && this.stores.forEach(store => {
          // each store has a common change event to subscribe to
          store.on('change', this._onChange)
        })
      }

      componentWillUnmount = () => {
        this.stores && this.stores.forEach(store => {
          store.off('change', this._onChange)
        })
      }

      render() {
        return <WrappedComponent 
                 lastUpdated={this.state.lastUpdated}
                 {...this.props}  />
      }
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

如何使用

import React, { Component } from 'react'
import { View, Text, Button } from 'react-native'
import withStores from './withStores'
import userStore from './stores/users'

class MyView {
  _increaseUserCount = () => {
    userStore.setState({ userCount: userStore.state.count + 1 })
  }

  render() {
    return (
      <View>
        <Text>User count: {userStore.state.count}</Text>
        <Button
          title="Increase User Count"
          onPress={this._increaseUserCount}
        />
      </View>
    )
  }
}

export default withStores(userStore)(MyView)
Run Code Online (Sandbox Code Playgroud)

Simple Store类示例

var ee = require('event-emitter')
export default class Store {
  constructor() {
    this._state = {}
    this._eventEmitter = ee({})
  }
  get state() {
    return this._state
  }
  setState(newState) {
    this._state = {...this._state, ...newState}
    this._eventEmitter.emit('change')
  }
  on(ev, fn) {
    this._eventEmitter.on(ev, fn)
  }
  off(ev, fn) {
    this._eventEmitter.off(ev, fn)
  }
}
Run Code Online (Sandbox Code Playgroud)

将实例存储为单例

import Store from './Store'

const myStore = new Store()
export default myStore
Run Code Online (Sandbox Code Playgroud)

我认为这会消除较小应用程序所需的所有苛刻框架.尽管与Immutable.js结合使用redux用于中到大的.

更新 您应该查看React中"钩子"的最新发展.这看起来非常简洁.

https://reactjs.org/docs/hooks-intro.html

  • 我认为在更新方法的Store类示例中有一个拼写错误,必须按如下方式编写方法的第一行:`this._data = {... this._data,... newData}`. (2认同)
  • React不鼓励继承以支持组合.https://reactjs.org/docs/composition-vs-inheritance.html (2认同)

Kyl*_*ker 13

所以我想我的问题是:React组件需要有状态才能重新渲染吗?有没有办法强制组件按需更新而不改变状态?

其他答案试图说明你怎么做,但重点是你不应该.即使是改变关键的黑客解决方案也忽略了这一点.React的力量放弃了控制手动管理什么东西应该渲染,而只是关于你自己应该如何映射输入.然后提供输入流.

如果你需要手动强制重新渲染,你几乎肯定不会做正确的事情.


Jac*_*bec 7

有几种方法可以重新渲染组件:

最简单的解决方案是使用 forceUpdate() 方法:

this.forceUpdate()
Run Code Online (Sandbox Code Playgroud)

另一种解决方案是在 state(nonUsedKey) 中创建未使用的密钥并调用 setState 函数并更新此 nonUsedKey:

this.setState({ nonUsedKey: Date.now() } );
Run Code Online (Sandbox Code Playgroud)

或者重写所有当前状态:

this.setState(this.state);
Run Code Online (Sandbox Code Playgroud)

道具更改还提供组件重新渲染。


koj*_*ow7 6

你可以通过几种方式做到这一点:

1、使用forceUpdate()方法:

使用该方法时可能会出现一些故障forceUpdate()。一个例子是它会忽略该shouldComponentUpdate()方法,并且无论是否shouldComponentUpdate()返回 false 都会重新渲染视图。因此,应尽可能避免使用forceUpdate()。

2.将this.state传递给setState()方法

以下代码行克服了上一个示例的问题:

this.setState(this.state);
Run Code Online (Sandbox Code Playgroud)

实际上,这一切都是用当前状态覆盖当前状态,从而触发重新渲染。这仍然不一定是最好的方法,但它确实克服了使用 forceUpdate() 方法可能遇到的一些问题。

  • 由于 setState 是批处理的,因此执行以下操作可能更安全:`this.setState(prevState =&gt; prevState);` (2认同)

Dha*_*ana 5

我们可以使用 this.forceUpdate() 如下。

       class MyComponent extends React.Component {



      handleButtonClick = ()=>{
          this.forceUpdate();
     }


 render() {

   return (
     <div>
      {Math.random()}
        <button  onClick={this.handleButtonClick}>
        Click me
        </button>
     </div>
    )
  }
}

 ReactDOM.render(<MyComponent /> , mountNode);
Run Code Online (Sandbox Code Playgroud)

即使您使用 setState 重新渲染组件,DOM 中的元素“Math.random”部分也只会更新。

这里的所有答案都是正确的,补充了理解问题。我们知道,在不使用 setState({}) 的情况下重新渲染组件是通过使用forceUpdate()。

上面的代码使用 setState 运行,如下所示。

 class MyComponent extends React.Component {



             handleButtonClick = ()=>{
                this.setState({ });
              }


        render() {
         return (
  <div>
    {Math.random()}
    <button  onClick={this.handleButtonClick}>
      Click me
    </button>
  </div>
)
  }
 }

ReactDOM.render(<MyComponent /> , mountNode);
Run Code Online (Sandbox Code Playgroud)


Luk*_*ach 5

为了完整起见,您还可以在功能组件中实现这一点:

const [, updateState] = useState();
const forceUpdate = useCallback(() => updateState({}), []);
// ...
forceUpdate();
Run Code Online (Sandbox Code Playgroud)

或者,作为可重复使用的钩子:

const useForceUpdate = () => {
  const [, updateState] = useState();
  return useCallback(() => updateState({}), []);
}
// const forceUpdate = useForceUpdate();
Run Code Online (Sandbox Code Playgroud)

参见:https : //stackoverflow.com/a/53215514/2692307

请注意,使用强制更新机制仍然是不好的做法,因为它违背了反应的心态,所以如果可能的话仍然应该避免。