【问题标题】:你能在不调用 setState 的情况下强制 React 组件重新渲染吗?
【发布时间】:2015-08-18 00:19:21
【问题描述】:

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

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

标签: reactjs react-jsx


【解决方案1】:

在类组件中,您可以调用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() 中实现额外的逻辑,从而使您的解决方案无效。
  • 超出最大调用堆栈大小
【解决方案2】:

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:

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

【讨论】:

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

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

forceUpdate()

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

setState()

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


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


钩子:How can I force component to re-render with hooks in React?


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

【讨论】:

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

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

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

  function handleClick() {
    forceUpdate();
  }

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

【讨论】:

  • 这应该在 2021 年以后发送到顶部。
  • 请记住,它仍然说:“尽量避免这种模式”。
  • @John 是的,这个问题的所有答案都是这种情况,因为你不应该forceUpdate。如果你发现自己使用了任何强制更新的实现,这通常意味着你正在做一些不是 React 方式的事情。尽量避免 forceUpdate,除非你必须这样做!
【解决方案5】:

我通过以下操作避开了forceUpdate

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

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

CORRECT WAY : 使用数据 id 作为键,可以是一些 guid 等

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

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

【讨论】:

  • this.state.rows.map((item) =&gt; &lt;MyComponent item={item} key={item.id} /&gt; )
  • 非常感谢您将我推向正确的方向。使用索引作为键确实是我的问题。
  • 为了证明我的反对意见,尽管使用这种方法是一种很好的做法,但这个答案与问题无关。
  • 这是完美的。比使用随机数要好得多。
【解决方案6】:

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

您要做的是监听 observable 组件 store 的变化,该 store 保存模型及其接口,并将导致渲染更改的数据保存为 state in @ 987654325@。当商店推送新数据时,您会更改组件的状态,这会自动触发渲染。

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

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

【讨论】:

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

使用钩子或 HOC 选择

使用 hooksHOC(高阶组件)模式,您可以在商店发生变化时自动更新。这是一种没有框架的非常轻量级的方法。

useStore 处理商店更新的挂钩方式

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() 更好?
【解决方案8】:

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

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

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

【讨论】:

  • @art-solopov,你指的是什么问题?哪些文档介绍了它们?假设您指的是状态中的嵌套对象,答案是使用不同的结构来存储您的状态。这显然是在 React 中处理状态的次优方式。您也不应该单独管理状态。如果您不使用状态管理系统,您将失去 React 的最大好处之一。手动管理更新是一种反模式。
  • 拜托,你能检查一下这个问题stackoverflow.com/questions/61277292/…吗?
【解决方案9】:

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

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

this.forceUpdate()

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

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

或者重写所有当前状态:

this.setState(this.state);

道具改变也提供组件重新渲染。

【讨论】:

    【解决方案10】:

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

    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

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

    【讨论】:

      【解决方案11】:

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

      1.使用forceUpdate() 方法:

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

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

      以下代码行解决了上一个示例的问题:

      this.setState(this.state);
      

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

      【讨论】:

      • 由于 setState 是批处理的,这样做可能更安全:this.setState(prevState =&gt; prevState);
      • 不太清楚为什么这是一个“故障”。方法的名称('forceUpdate')再清楚不过了:总是强制更新。
      【解决方案12】:

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

      【讨论】:

        【解决方案13】:

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

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

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

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

        【讨论】:

          【解决方案14】:

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

          【讨论】:

            【解决方案15】:

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

            【讨论】:

            • 这个动作有什么作用?
            【解决方案16】:

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

            this.setState(prevState=&gt;({...prevState}));

            【讨论】:

              【解决方案17】:

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

              【讨论】:

                【解决方案18】:

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

                【讨论】:

                  【解决方案19】:

                  ES6 - 我包含一个对我很有帮助的示例:

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

                  isReady ? ()=>{} : onClick
                  

                  这似乎是最短的方法。

                  ()=>{}
                  

                  【讨论】:

                    【解决方案20】:

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

                    【讨论】:

                      【解决方案21】:

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

                      【讨论】:

                      • 出于好奇,如果 4 年来人们已经在此线程中提供了一个可能的解决方案,为什么还要留下这个答案?
                      猜你喜欢
                      • 2018-03-27
                      • 2020-08-18
                      • 2021-04-28
                      • 1970-01-01
                      • 2015-11-13
                      • 2016-07-14
                      • 2018-12-23
                      • 2019-08-10
                      • 1970-01-01
                      相关资源
                      最近更新 更多