您可以在不调用setState的情况下强制React组件重新渲染吗?


685

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

对于顶层,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>
    )
  }
}

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

所以我想我的问题是: React组件是否需要具有状态才能重新渲染?有没有一种方法可以在不更改状态的情况下强制组件按需更新?


7
接受的答案this.forceUpdate()是正确的解决方案,而所有的答案和一些评论的其余部分是反对使用forceUpdate()。可以说问题尚未得到适当的解决方案/答案吗?
ADI公司

6
那个例外的回答当时回答了我的问题。从技术上讲,这是我一直在寻找的答案,但我仍然认为正确的答案。我认为其他答案对于有相同问题的人们是很好的补充信息。
菲利普·沃尔顿

值得注意的是,除了将其初始化为普通对象之外,您根本不需要任何状态,然后调用this.setState({})只会触发一个新的渲染。React很棒,但有时也很奇怪。因此,当您触发更改时,您可以直接循环访问存储数据,而不必花费额外的精力或担心每个组件实例的数据。
Jason Sebring

总的来说,我会说“是”。如果您使用强制更新,则这是为了更新组件,其中组件可能取决于应用程序状态管理之外的更改。我想不出一个很好的例子。有用的虽然知道。
丹尼尔(Daniel)

Answers:


763

在组件中,您可以调用this.forceUpdate()强制重新渲染。

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


168
另一种方式是this.setState(this.state);
kar

25
不鼓励使用forceupdate,请参阅最后一个答案。
Varand Pezeshkian '16

42
虽然this.forceUpdate()不是很好地解决提问者的问题,但这是标题中提出的问题的正确答案。尽管通常应避免使用它,但在某些情况下,forceUpdate是一个很好的解决方案。
ArneHugo '16

8
@kar实际上,可以在shouldComponentUpdate()使解决方案徒劳的过程中实现其他逻辑。
Qwerty

4
超出了最大调用堆栈大小
IntoTheDeep

325

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

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

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

通常,您应该避免使用forceUpdate(),而只能从render()中的this.props和this.state中读取。这使您的组件“纯净”,您的应用程序变得更加简单和高效。强制性升级()

更改要重新渲染的元素的键即可。通过状态在元素上设置关键道具,然后在要更新设置状态以拥有新关键时进行设置。

<Element key={this.state.key} /> 

然后发生更改,您重置了密钥

this.setState({ key: Math.random() });

我想指出,这将替换密钥正在更改的元素。例如,当您有一个文件输入字段要在图像上传后重设时,此操作可能有用。

虽然对OP的问题的真正答案是forceUpdate()我发现此解决方案在不同情况下很有用。我还想指出,如果您发现自己正在使用forceUpdate,则可能需要查看代码并查看是否还有另一种处理方法。

注意1-9-2019:

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


16
我很想知道为什么这引起了反对?我一直在努力让屏幕阅读器响应aria警报,这是我发现的唯一可靠的技术。如果用户界面中的某些内容每次单击都会生成相同的警报,则默认情况下react不会重新呈现DOM,因此屏幕阅读器不会宣布更改。设置唯一的密钥即可使其工作。也许不满意的原因是因为它仍然涉及设置状态。但是通过设置密钥来强制将其重新渲染为DOM是黄金的!
Martin Bayly

2
我非常喜欢这个答案,因为-1:不鼓励使用forceupdate(),2:这种方式对您通过npm安装的第3方组件非常有帮助,这意味着不是您自己的组件。例如,React-resizable-and-movable是我使用的第3方组件,但它对我的组件setstate没有反应。这是很好的解决方案。
Varand Pezeshkian '16

80
这是一种hack和滥用key。首先,其意图尚不清楚。您的代码阅读者将必须努力理解为什么要使用key这种方式。其次,这并不比纯粹forceUpdate。“纯” React表示组件的视觉表示取决于其状态的100%,因此,如果您更改任何状态,它都会更新。但是,如果您有一些深层嵌套的对象(forceUpdate文档中引用了使用它的理由的情况),则使用forceUpdate可以使情况变得清晰。第三,Math.random()是……随机。从理论上讲,它可以生成相同的随机数。
atomkirk

8
@xtraSimplicity我不能同意这符合React的做事方式。forceUpdate是这样做的React方法。
罗布·格兰特

3
@罗伯特·格兰特,回望,我同意。增加的复杂性并不真正值得。
XtraSimplicity

80

实际上,这forceUpdate()是唯一正确的解决方案,因为如果在其中或仅返回时实施了附加逻辑,则setState()可能不会触发重新渲染。shouldComponentUpdate()false

强制性升级()

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

setState()

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


forceUpdate() 可以从您的组件内部通过调用 this.forceUpdate()


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


顺便说一句:您是在改变状态还是您的嵌套属性不传播?


1
需要注意的一件有趣的事是,setState之外的状态声明(例如this.state.foo ='bar')不会触发渲染生命周期方法
lfender6445,2016年

4
@ lfender6445直接this.state在构造函数外部分配状态也应该引发错误。2016年可能尚未这样做;但是,我很确定现在可以做到。
泰勒

我添加了一些链接来解决直接状态分配问题。
Qwerty

36

forceUpdate通过执行以下操作避免了

错误方式:请勿将索引用作键

this.state.rows.map((item, index) =>
   <MyComponent cell={item} key={index} />
)

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

this.state.rows.map((item) =>
   <MyComponent item={item} key={item.id} />
)

因此,通过进行此类代码改进,您的组件将是唯一的并自然呈现


this.state.rows.map((item) => <MyComponent item={item} key={item.id} /> )
塔尔

非常感谢您将我推向正确的方向。使用索引作为键确实是我的问题。
RoiEX

为了证明我的不赞成投票的理由,尽管使用此方法是一种好习惯,但此答案与问题无关。
micnic,

34

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

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

通常,您应尽量避免使用forceUpdate()。从文档中:

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


2
组件状态的内存状态是什么?如果我在一个页面上有五个组件,并且它们全部都在监听一个存储,那么我是否在内存中存储了五次数据,或者它们是引用?难道所有这些听众都不会很快加起来吗?为什么“滴流”比仅将数据传递给目标更好?
AJFarkas

5
实际上,建议是将存储数据传递给组件prop,并且仅将组件状态用于滚动状态和其他特定于ui的次要事情。如果您使用redux(我建议使用它),则可以根据需要使用连接函数from react-redux来根据提供的映射功能自动将商店状态映射到组件prop。
Stijn de Witt

1
@AJFarkas状态将被分配给商店的数据,而这仅仅是指向它的指针,因此除非您要克隆,否则内存不是问题。
Jason Sebring

4
“应始终避免使用forceUpdate()”是不正确的。该文档明确指出:“通常,您应尽量避免使用forceUpdate()”。forceUpdate
AJP

2
@AJP你说的对,这是个人偏见:)我已经编辑了答案。
gcedo

18

使用挂钩或HOC选择

使用挂钩HOC(高阶组件)模式,可以在商店更改时自动进行更新。如果没有框架,这是一种非常轻巧的方法。

useStore Hooks处理商店更新的方法

interface ISimpleStore {
  on: (ev: string, fn: () => void) => void;
  off: (ev: string, fn: () => void) => void;
}

export default function useStore<T extends ISimpleStore>(store: T) {
  const [storeState, setStoreState] = useState({store});
  useEffect(() => {
    const onChange = () => {
      setStoreState({store});
    }
    store.on('change', onChange);
    return () => {
      store.off('change', onChange);
    }
  }, []);
  return storeState.store;
}

withStores HOC处理商店更新

export default function (...stores: SimpleStore[]) {
  return function (WrappedComponent: React.ComponentType<any>) {
    return class WithStore extends PureComponent<{}, {lastUpdated: number}> {
      constructor(props: React.ComponentProps<any>) {
        super(props);
        this.state = {
          lastUpdated: Date.now(),
        };
        this.stores = stores;
      }

      private stores?: SimpleStore[];

      private onChange = () => {
        this.setState({lastUpdated: Date.now()});
      };

      componentDidMount = () => {
        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}
          />
        );
      }
    };
  };
}

SimpleStore类

import AsyncStorage from '@react-native-community/async-storage';
import ee, {Emitter} from 'event-emitter';

interface SimpleStoreArgs {
  key?: string;
  defaultState?: {[key: string]: any};
}

export default class SimpleStore {
  constructor({key, defaultState}: SimpleStoreArgs) {
    if (key) {
      this.key = key;
      // hydrate here if you want w/ localState or AsyncStorage
    }
    if (defaultState) {
      this._state = {...defaultState, loaded: false};
    } else {
      this._state = {loaded: true};
    }
  }
  protected key: string = '';
  protected _state: {[key: string]: any} = {};
  protected eventEmitter: Emitter = ee({});
  public setState(newState: {[key: string]: any}) {
    this._state = {...this._state, ...newState};
    this.eventEmitter.emit('change');
    if (this.key) {
      // store on client w/ localState or AsyncStorage
    }
  }
  public get state() {
    return this._state;
  }
  public on(ev: string, fn:() => void) {
    this.eventEmitter.on(ev, fn);
  }
  public off(ev: string, fn:() => void) {
    this.eventEmitter.off(ev, fn);
  }
  public get loaded(): boolean {
    return !!this._state.loaded;
  }
}

如何使用

对于钩子:

// use inside function like so
const someState = useStore(myStore);
someState.myProp = 'something';

对于HOC:

// inside your code get/set your store and stuff just updates
const val = myStore.myProp;
myOtherStore.myProp = 'something';
// return your wrapped component like so
export default withStores(myStore)(MyComponent);

请确认 为您的商店导出为一个单独得到像这样的全球变化的好处:

class MyStore extends SimpleStore {
  public get someProp() {
    return this._state.someProp || '';
  }
  public set someProp(value: string) {
    this.setState({...this._state, someProp: value});
  }
}
// this is a singleton
const myStore = new MyStore();
export {myStore};

这种方法非常简单,对我有用。我还在大型团队中工作,并使用Redux和MobX,发现它们也不错,但只是很多样板。我只是个人喜欢自己的方法,因为我总是讨厌很多代码,因为某些事情在您需要的时候可能很简单。


2
我认为您的Store类示例中的update方法中有一个错字,必须将方法的第一行编写如下:this._data = {...this._data, ...newData}
诺伯特'18

2
React不鼓励继承,而支持组合。reactjs.org/docs/composition-vs-inheritance.html
弗雷德(Fred),

1
只是想知道其清晰度/可读性,如何使this.setState(this.state)比this.forceUpdate()更好?
Zoman

17

所以我想我的问题是:React组件是否需要具有状态才能重新渲染?有没有一种方法可以在不更改状态的情况下强制组件按需更新?

其他答案试图说明您如何做到,但重点是您不应该这样做。即使是更改密钥的骇人听闻的解决方案也没有抓住重点。React的功能是放弃手动管理何时应呈现某些内容的控制权,而仅仅是考虑如何在输入上映射某些内容。然后提供输入流。

如果您需要手动强制重新渲染,几乎可以肯定您做的事情不正确。


@ art-solopov,您指的是什么问题?哪些文档向他们展示?并假设您是指状态中的嵌套对象,答案是使用不同的结构来存储状态。显然,这是在React中处理状态的次佳方式。您也不应该单独管理状态。如果不使用状态管理系统,您将失去React最大的好处之一。手动管理更新是一种反模式。
凯尔·贝克

请,您可以检查这个问题stackoverflow.com/questions/61277292/…吗?
HuLu ViCa

4

您可以通过以下两种方法进行操作:

1.使用forceUpdate()方法:

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

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

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

this.setState(this.state);

实际上,所有要做的就是用触发重新渲染的当前状态覆盖当前状态。这仍然不一定是最好的处理方法,但是它确实克服了使用forceUpdate()方法可能遇到的一些故障。


2
由于setState已批处理,因此可能更安全:this.setState(prevState => prevState);
Mark Galloway

1
不太确定为什么这是一个“小故障”。方法的名称('forceUpdate')不能更清楚:始终强制更新。
Zoman

4

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

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

this.forceUpdate()

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

this.setState({ nonUsedKey: Date.now() } );

或重写所有当前状态:

this.setState(this.state);

更改道具还可以重新渲染组件。


3

为了完整起见,您还可以在功能组件中实现此目的:

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

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

const useForceUpdate = () => {
  const [, updateState] = useState();
  return useCallback(() => updateState({}), []);
}
// const forceUpdate = useForceUpdate();

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

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


2

我们可以如下使用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);

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

此处的所有答案都是对问题理解的正确补充。.我们知道通过使用forceUpdate()无需使用setState({})即可重新渲染组件。

上面的代码与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);

1

只是另一个回复,以备份已接受的答案:-)

React不鼓励使用,forceUpdate()因为它们通常在函数式编程中使用非常“这是唯一的方法”。在许多情况下这很好,但是许多React开发人员都带有面向对象的背景,通过这种方法,完全可以监听可观察对象。

如果这样做,您可能知道您必须在可观察到的“火灾”发生时重新渲染,因此,您应该使用forceUpdate()它,而实际上这shouldComponentUpdate()是这里不涉及的优点。

MobX之类的工具需要面向对象的方法,实际上是在表面下进行此操作(实际上是MobX render()直接调用)


0

我发现最好避免forceUpdate()。强制重新渲染的一种方法是添加render()对临时外部变量的依赖性,并在需要时更改该变量的值。

这是一个代码示例:

class Example extends Component{
   constructor(props){
      this.state = {temp:0};

      this.forceChange = this.forceChange.bind(this);
   }

   forceChange(){
      this.setState(prevState => ({
          temp: prevState.temp++
      })); 
   }

   render(){
      return(
         <div>{this.state.temp &&
             <div>
                  ... add code here ...
             </div>}
         </div>
      )
   }
}

需要强制重新渲染时,请调用this.forceChange()。


0

ES6-我提供了一个示例,这对我有帮助:

在“ short if语句”中,您可以传递空函数,如下所示:

isReady ? ()=>{} : onClick

这似乎是最短的方法。

()=>{}



0

另一种方法是调用setState保留状态:

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


0

forceUpdate(),但是每次我听到有人谈论它时,都应该跟进它,但绝对不要使用它。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.