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

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

下面是一个代码示例:

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组件需要有状态才能渲染吗?是否有一种方法可以在不改变状态的情况下强制组件按需更新?


在类组件中,可以调用this.forceUpdate()来强制渲染。

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

在函数组件中,没有等效的forceUpdate,但您可以通过useState钩子设计一种强制更新的方法。


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

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

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

通常情况下,你应该尽量避免使用forceUpdate(),只从这个中读取。道具和这个。render()中的状态。这使您的应用程序更加简单和高效


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

默认情况下,当组件的状态或道具改变时,组件将重新呈现。然而,如果这些隐式变化(例如:对象内部的深层数据变化而不改变对象本身),或者如果你的render()方法依赖于一些其他数据,你可以通过调用forceUpdate()告诉React它需要重新运行render()。

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

通常情况下,你应该尽量避免使用forceUpdate(),只从这个中读取。道具和这个。render()中的状态。这使得你的组件“纯粹”,你的应用程序更简单、更高效。

改变你想要重新渲染的元素的键也可以。通过state在元素上设置键道具,然后当你想更新Set state时有一个新的键。

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

然后发生了变化,您重置了密钥

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

我要注意的是,这将取代键改变的元素。这可能有用的一个例子是,当您有一个文件输入字段,您希望在图像上传后重置。

虽然OP问题的真正答案是forceUpdate(),但我发现这个解决方案在不同的情况下都很有用。我还想指出的是,如果你发现自己使用了forceUpdate,你可能需要检查你的代码,看看是否有另一种方法来做事情。

注意1-9-2019:

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


实际上,forceUpdate()是唯一正确的解决方案,因为如果在shouldComponentUpdate()中实现了额外的逻辑,或者当它只是返回false时,setState()可能不会触发重新呈现。

强制更新()

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

设置状态()

setState()总是会触发重新呈现,除非在shouldComponentUpdate()中实现了条件呈现逻辑。更多…

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

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

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

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


所以我想我的问题是:React组件需要有状态吗 要重新渲染?是否有一种方法强制组件更新 不改变状态的需求?

其他答案试图说明你可以这样做,但关键是你不应该这样做。即使是改变密钥这种俗气的解决方案也没有抓住重点。React的强大之处在于放弃手动管理内容何时应该呈现的控制,而只是关注内容应该如何映射到输入上。然后供给输入流。

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


我通过执行以下操作避免了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} />
)

所以通过这样的代码改进,你的组件将是唯一的,呈现自然


你可以有几种方法:

1. 使用forceUpdate()方法:

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

2. 通过这个。状态转换为setState()方法

下面这行代码克服了前面例子中的问题:

this.setState(this.state);

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


使用钩子或HOC选择

使用钩子或HOC(高阶组件)模式,可以在存储发生变化时进行自动更新。这是一种没有框架的轻量级方法。

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


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


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

在一个“简短的if语句”中,你可以像这样传递一个空函数:

isReady ? ()=>{} : onClick

这似乎是最短的方法。

()=>{}

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

另一种方法是调用setState和preserve state:

this.setState(prevState=>({...上一页状态}));


forceUpdate ();方法将工作,但建议使用setState();


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


只是另一个回复来支持接受的答案:-)

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

如果你这样做,你可能知道你必须重新渲染时,可观察对象“火灾”,因此,你应该使用forceUpdate(),它实际上是一个加,shouldComponentUpdate()是不涉及在这里。

像MobX这样的工具,采用oo方法,实际上是在表面之下做这件事(实际上MobX直接调用render())


有几种方法来渲染你的组件:

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

this.forceUpdate()

另一个解决方案是在状态下创建未使用的键(nonUsedKey) 然后调用setState函数更新这个nonUsedKey:

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

或者重写所有当前状态:

this.setState(this.state);

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


你可以使用forceUpdate()来了解更多细节。


forceUpdate(),但每次我听到有人谈论它,它的后续是你不应该使用这个。


为了完整性,你也可以在功能组件中实现:

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

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


在2021年和2022年,这将是强制更新React功能组件的官方方式。

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

  function handleClick() {
    forceUpdate();
  }

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

编辑2022年4月18日

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

有几个原因可能导致需要使用强制更新。

Not using state variables where you have to - local, redux, context. The field from the state object you are trying to access and expecting to update/change is too deeply nested in objects or arrays. Even Redux advises to maintain flat objects or arrays. If only one field value changes in a complex object, React may not figure out that the state object has changed, thus it does not update the component. Keep your state flat and simple. The key on your list items, as mentioned in another answer. In fact, this can cause other unexpected behaviors as well. I've seen lists where items are repeatedly rendered (duplicates) because the keys aren't identical or the keys are just missing altogether. Always request the backend team to send unique ids everywhere possible! Avoid using array indexes for keys. Do not try to create unique ids on the front-end by using nanoid, uuid or random. Because ids created using above methods change each time the component updates (keys provided to a list need to be static and the same on each render). Creating unique ids is usually a backend concern. Try your best to not bring that requirement to the front-end. The front-end's responsibility is only to paint what data the backend returns and not create data on the fly. If your useEffect, useCallback dependency arrays do not have the proper values set. Use ESLint to help you with this one! Also, this is one of the biggest causes for memory leaks in React. Clean up your state and event listeners in the return callback to avoid memory leaks. Because such memory leaks are awfully difficult to debug. Always keep an eye on the console. It's your best friend at work. Solving warning and errors that show up in the console can fix a whole lot of nasty things - bugs and issues that you aren't even aware off.

我记得我做错的几件事。万一有用的话…


使用useEffect作为componentDidMount, componentDidUpdate和componentWillUnmount的组合,如React文档中所述。

要像componentDidMount一样工作,你需要像这样设置你的useEffect:

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

第一个参数是一个回调,它将根据第二个参数(一个值数组)触发。如果第二个参数中的任何值发生了变化,您在useEffect中定义的回调函数将被触发。

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

这是对useEffect的总结。如果不是空值,而是有一个参数,比如:

 useEffect(() => {

  }, [props.lang]);

这意味着每次道具。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>
  );
}