ChatGPT解决这个技术问题 Extra ChatGPT

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

我有一个外部(组件)的可观察对象,我想监听它的变化。当对象更新时,它会发出更改事件,然后我想在检测到任何更改时重新渲染组件。

使用顶级 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 组件是否需要有状态才能重新渲染?有没有办法强制组件按需更新而不改变状态?

accepted answerthis.forceUpdate() 是正确的解决方案,而其余所有答案和一些评论都反对使用 forceUpdate()。那么可以说这个问题还没有得到正确的解决方案/答案吗?
例外的答案回答了我当时的问题。从技术上讲,是我一直在寻找的答案,我仍然认为是正确的答案。我认为其他答案对于有相同问题的人来说是很好的补充信息。
值得注意的是,除了将其初始化为普通对象之外,您根本不需要任何状态,然后调用 this.setState({}) 只会触发新的渲染。 React 很棒,但有时也很奇怪。因此,您可以在触发更改时直接遍历存储数据,而无需额外的管道或担心每个组件实例的数据。
总的来说,我会说是的。如果您使用强制更新,这是用于更新可能依赖于应用程序状态管理之外的更改的组件。我想不出一个很好的例子。不过知道有用。

A
Andy

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

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

在函数组件中,没有 forceUpdate 的等价物,但您可以 contrive a way to force updates with the useState hook


另一种方式是 this.setState(this.state);
不鼓励使用 forceupdate ,请参阅最后一个答案。
虽然 this.forceUpdate() 不是解决提问者问题的好方法,但它是对标题中提出的问题的正确答案。虽然通常应该避免这种情况,但在某些情况下 forceUpdate 是一个很好的解决方案。
@kar 实际上,可以在 shouldComponentUpdate() 中实现额外的逻辑,使您的解决方案无效。
超出最大调用堆栈大小
S
Silicum Silium

forceUpdate 应该避免,因为它偏离了 React 的思维方式。 React 文档引用了一个何时可以使用 forceUpdate 的示例:

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

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

通常你应该尽量避免使用 forceUpdate() 并且只在 render() 中读取 this.props 和 this.state。这使您的组件“纯粹”并且您的应用程序更加简单和高效。forceUpdate()

更改要重新渲染的元素的键将起作用。通过 state 设置元素上的 key prop,然后当你想要更新 set state 以获得新的 key 时。

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

然后发生变化并重置密钥

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

我想指出,这将替换键正在更改的元素。这可能有用的一个示例是,当您有一个文件输入字段希望在图像上传后重置时。

虽然对 OP 问题的真正答案是forceUpdate(),但我发现此解决方案在不同情况下都有帮助。我还想指出,如果您发现自己在使用 forceUpdate,您可能需要查看您的代码,看看是否有其他方法可以做事。

注意 1-9-2019:

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


我很想知道为什么这会被否决?我一直在努力让屏幕阅读器响应咏叹调警报,这是我发现的唯一可靠的技术。如果您的 UI 中的某些内容在每次单击时都会生成相同的警报,则默认情况下,react 不会重新渲染 DOM,因此屏幕阅读器不会宣布更改。设置唯一键使其工作。也许反对票是因为它仍然涉及设置状态。但是通过设置 key 强制重新渲染到 DOM 是黄金!
这是一个 hack 和 key 的滥用。首先,其意图不明确。阅读您的代码的人必须努力理解您为什么要以这种方式使用 key。其次,这并不比 forceUpdate 更纯粹。 “纯”React 意味着你的组件的视觉呈现 100% 依赖于它的状态,所以如果你改变任何状态,它就会更新。但是,如果您有一些深度嵌套的对象(forceUpdate 文档引用了使用它的理由的场景),那么使用 forceUpdate 可以清楚地说明这一点。第三,Math.random() 是……随机的。理论上,它可以生成相同的随机数。
@xtraSimplicity 我不同意这符合 React 的做事方式。 forceUpdate 是执行此操作的 React 方式。
@Robert Grant,回头看,我同意。增加的复杂性并不值得。
@atomkirk 我和你的意见相同,但是,令人惊讶的是,在某些情况下,反应文档批准甚至推荐这种方法:reactjs.org/blog/2018/06/07/…
T
Tom Bombadil

在 2021 年和 2022 年,这是 the official way 强制更新 React 功能组件。

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

  function handleClick() {
    forceUpdate();
  }

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

编辑 2022 年 4 月 18 日

强制更新组件通常是不好的做法。

一些可能导致需要使用强制更新的事情。

不使用必须使用的状态变量 - 本地、redux、上下文。

您尝试访问并期望更新/更改的状态对象中的字段在对象或数组中嵌套得太深。甚至 Redux 也建议维护平面对象或数组。如果复杂对象中只有一个字段值发生变化,React 可能无法判断状态对象发生了变化,因此它不会更新组件。保持你的状态平坦和简单。

如另一个答案中所述,列表项上的键。事实上,这也可能导致其他意外行为。我已经看到列表中的项目被重复呈现(重复),因为键不相同或键完全丢失。始终要求后端团队尽可能发送唯一的 ID!避免对键使用数组索引。不要尝试使用 nanoid、uuid 或随机在前端创建唯一 id。因为使用上述方法创建的 id 在每次组件更新时都会发生变化(提供给列表的键需要是静态的并且在每次渲染时都相同)。创建唯一 ID 通常是后端问题。尽量不要把这个要求带到前端。前端的职责只是绘制后端返回的数据,而不是动态创建数据。

如果您的 useEffect、useCallback 依赖数组没有设置正确的值。使用 ESLint 来帮助你解决这个问题!此外,这也是 React 内存泄漏的最大原因之一。在返回回调中清理您的状态和事件侦听器以避免内存泄漏。因为这样的内存泄漏非常难以调试。

始终关注控制台。它是你工作中最好的朋友。解决控制台中出现的警告和错误可以解决很多令人讨厌的事情——你甚至没有意识到的错误和问题。

有几件事我记得我做错了。万一有帮助..


请记住,它仍然说:“尽量避免这种模式”。
@John 是的,这个问题的所有答案都是如此,因为您不应该forceUpdate。如果你发现自己使用了任何强制更新的实现,这通常意味着你正在做一些不是 React 方式的事情。尽可能避免 forceUpdate ,除非你必须这样做!
Q
Qwerty

实际上,forceUpdate() 是唯一正确的解决方案,因为如果在 shouldComponentUpdate() 中实现了其他逻辑或仅返回 falsesetState() 可能不会触发重新渲染。

强制性升级()

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

设置状态()

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

强制性升级()

this.forceUpdate()

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

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

如何在 React 中更新嵌套状态属性

沙盒


需要注意的一件有趣的事情是 setState 之外的状态分配,例如 this.state.foo = 'bar' 不会触发渲染生命周期方法
@lfender6445 在构造函数之外直接使用 this.state 分配状态也应该引发错误。 2016 年可能不会这样做;但是,我很确定它现在可以做到。
我添加了一些链接来解决直接状态分配问题。
v
vijay

我通过执行以下操作避免了 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} /> )
为了证明我的反对票是合理的,尽管使用这种方法是一种很好的做法,但这个答案与问题无关。
g
gcedo

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

您要做的是监听 可观察组件 存储的更改,该存储保存模型及其接口,并将导致渲染更改的数据保存为 MyComponent 中的 state。当商店推送新数据时,您会更改组件的状态,这会自动触发渲染。

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

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


组件状态的内存状态是什么?如果我在一个页面上有五个组件并且它们都在听一个存储,那么我是否在内存中有五次数据,或者它们是引用的?不是所有的听众加起来很快吗?为什么“涓涓细流”比仅仅将数据传递给您的目标更好?
实际上,建议是将存储数据传递给组件道具,并且仅将组件状态用于滚动状态和其他次要的特定于 ui 的事物。如果您使用 redux(我推荐它),您可以使用 react-redux 中的 connect 函数根据您提供的映射函数在需要时自动将商店状态映射到组件道具。
@AJFarkas 状态将分配给商店的数据,无论如何这只是指向它的指针,因此除非您正在克隆,否则内存不是问题。
“应始终避免 forceUpdate()”是不正确的。文档清楚地说:“通常你应该尽量避免使用 forceUpdate()”。有 forceUpdate 的有效用例
@AJP你是绝对正确的,那是个人偏见:)我已经编辑了答案。
K
King Friday

使用钩子或 HOC 选择

使用钩子或 HOC(高阶组件)模式,您可以在商店更改时自动更新。这是一种没有框架的非常轻量级的方法。

使用Store 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,发现它们也很好,但只是很多样板。我个人只是喜欢我自己的方法,因为我总是讨厌很多代码,因为你需要它时可以很简单。


我认为更新方法中的 Store 类示例中有一个错字,必须将方法的第一行编写如下:this._data = {...this._data, ...newData}
React 不鼓励继承而支持组合。 reactjs.org/docs/composition-vs-inheritance.html
只是想知道清晰度/可读性,this.setState(this.state) 如何比 this.forceUpdate() 更好?
K
Kyle Baker

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

其他答案试图说明你是如何做到的,但关键是你不应该这样做。即使是更改密钥的骇人听闻的解决方案也没有抓住重点。 React 的强大之处在于放弃了手动管理什么时候应该渲染的控制,而只关心自己应该如何映射输入。然后提供输入流。

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


p
piet.t

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

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

this.forceUpdate()

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

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

或者重写所有当前状态:

this.setState(this.state);

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


L
Lukas Bach

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

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

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


k
kojow7

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

<强> 1。使用 forceUpdate() 方法:

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

<强> 2。将 this.state 传递给 setState() 方法

以下代码行克服了前面示例的问题:

this.setState(this.state);

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


由于 setState 是批处理的,因此这样做可能更安全:this.setState(prevState => prevState);
不太清楚为什么这是一个“故障”。方法的名称('forceUpdate')再清楚不过了:总是强制更新。
D
Dhana

我们可以使用 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 中的 Element '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);

P
Plaul

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

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

如果你这样做了,你可能知道你必须在 observable “触发”时重新渲染,因此,你应该使用 forceUpdate() 并且这里不涉及 shouldComponentUpdate() 实际上是一个优点。

像 MobX 这样采用 OO 方法的工具实际上是在表面下执行此操作(实际上 MobX 直接调用 render()


I
Ian

forceUpdate(),但每次我听到有人谈论它时,都会跟进,你永远不应该使用它。


C
Chiamaka Ikeanyi

forceUpdate(); 方法可行,但建议使用 setState();


A
API-Andy

为了完成您所描述的内容,请尝试 this.forceUpdate()。


这个动作有什么作用?
z
zumalifeguard

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

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


I
Iram Faram

您可以使用 this.forceUpdate() 强制重新渲染。你可以像这样使用它。

class App extends React.Component {

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

render() {
    return (
      <>
        <button onClick={this.handleChange}>Hello World</button>
      </>
    );
  }

}


r
raksheetbhat

我发现最好避免 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()。


M
MrDoda

ES6 - 我包括一个例子,这对我很有帮助:

在“短 if 语句”中,您可以像这样传递空函数:

isReady ? ()=>{} : onClick

这似乎是最短的方法。

()=>{}

R
Raskul

如 React 文档中所述,将 useEffect 用作 componentDidMountcomponentDidUpdatecomponentWillUnmount 的组合。

要表现得像 componentDidMount,您需要像这样设置您的 useEffect:

  useEffect(() => console.log('mounted'), []);

第一个参数是一个回调,它将基于第二个参数触发,第二个参数是一个值数组。如果第二个参数中的任何值发生更改,您在 useEffect 中定义的回调函数将被触发。

然而,在我展示的示例中,我将一个空数组作为我的第二个参数传递,并且永远不会改变,所以当组件挂载时回调函数将被调用一次。

那种总结useEffect。如果你有一个参数而不是一个空值,例如:

 useEffect(() => {

  }, [props.lang]);

这意味着每次 props.lang 更改时,都会调用您的回调函数。 useEffect 不会真正重新渲染您的组件,除非您在该回调函数中管理一些可能触发重新渲染的状态。

如果您想触发重新渲染,您的渲染函数需要有一个您正在 useEffect 中更新的状态。

例如,在这里,渲染函数首先将英语显示为默认语言,在我的使用效果中,我在 3 秒后更改了该语言,因此渲染被重新渲染并开始显示“西班牙语”。

function App() {
  const [lang, setLang] = useState("english");

  useEffect(() => {
    setTimeout(() => {
      setLang("spanish");
    }, 3000);
  }, []);

  return (
    <div className="App">
      <h1>Lang:</h1>
      <p>{lang}</p>
    </div>
  );
}

S
Silicum Silium

您可以使用 forceUpdate() 进行更多详细信息检查 (forceUpdate())。


出于好奇,如果 4 年来人们已经在这个线程中提供了这个可能的解决方案,为什么还要留下这个答案?
S
Sarthak Saklecha

bruh如果我不关心持久化本地状态或任何数据,我只是到location.reload(),这重新加载页面,也显然重新渲染组件


location.reload() 刷新整个页面,这不是所需的行为。
它应该是黑客