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

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

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

1386758 次浏览

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

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

在函数组件中,没有forceUpdate的等价物,但您可以设计一种用#1钩子强制更新的方法

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

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

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

通常情况下,您应该尽量避免使用forceUpdate(),并且只从this.props和this.state中读取渲染()。这使得您的应用程序更加简单和高效

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

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

然而,我想提出这样的想法,即使对于深度嵌套的对象,forceUpdate也是不必要的。通过使用不可变数据源跟踪更改变得便宜;更改总是会导致一个新对象,所以我们只需要检查对对象的引用是否发生了变化。你可以使用库不可变js在你的应用程序中实现不可变数据对象。

通常情况下,你应该尽量避免使用forceUpdate(),只从this.props和this.state中读取。这使得你的组件“纯粹”,你的应用程序更简单,更高效。

更改要重新渲染的元素的键将起作用。通过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()更多…

设置状态

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


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


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


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

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

其他答案试图说明你如何做到这一点,但关键是你不应该。即使是更改键的黑客解决方案也没有抓住重点。React的强大之处在于放弃了手动管理何时应该渲染的控制,而是只关心一些东西应该如何映射到输入上。然后提供输入流。

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

我通过以下操作避免了forceUpdate

走错路了:不要使用索引作为键

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

正确的方式:使用data id作为key,可以是guid等

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

因此,通过这样的代码改进,您的组件将独特并自然呈现

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

1.使用forceUpdate()方法:

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

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

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

this.setState(this.state);

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

用钩子还是HOC随你挑

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

使用商店挂钩处理商店更新的方式

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

随着商店 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 tostore.on('change', this.onChange);});};
componentWillUnmount = () => {this.stores &&this.stores.forEach((store) => {store.off('change', this.onChange);});};
render() {return (<WrappedComponentlastUpdated={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 soconst someState = useStore(myStore);someState.myProp = 'something';

在HOC的情况下:

// inside your code get/set your store and stuff just updatesconst val = myStore.myProp;myOtherStore.myProp = 'something';
// return your wrapped component like soexport 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 singletonconst myStore = new MyStore();export {myStore};

这种方法非常简单,对我很有效。我也在大型团队中工作,使用Redux和MobX,发现它们也很好,但只是有很多样板。我个人只是喜欢我自己的方法,因为我总是讨厌在需要的时候编写大量可以简单的代码。

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

下面是一个代码示例:

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

DOM中的元素“Math.random”部分仅在使用setState重新呈现组件时才会更新。

这里的所有答案都是正确的,补充了理解问题…正如我们所知,使用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保存状态:

this.setState(prevState=>({...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日

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

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

  • 不使用您必须使用的状态变量-本地、redux、上下文。
  • 您尝试访问并期望更新/更改的state对象中的字段嵌套在对象或数组中太深。即使是Redux也建议维护扁平化对象或数组。如果复杂对象中只有一个字段值更改,React可能不会发现state对象已更改,因此它不会更新组件。保持您的状态扁平化和简单。
  • 正如另一个答案中提到的,列表项上的键。事实上,这也可能导致其他意想不到的行为。我见过重复渲染项目的列表(重复),因为键不相同或键完全丢失。始终要求后端团队尽可能向任何地方发送唯一ID!避免为键使用数组索引。不要尝试通过使用纳米级、uuid或随机在前端创建唯一ID。因为每次组件更新时,使用上述方法创建的ID都会发生变化(提供给列表的键需要是静态的,并且在每次渲染时都相同)。创建唯一ID通常是后端的问题。尽量不要把那个需求带到前端,前端的职责只是画出后端返回什么数据,而不是动态创建数据。
  • 如果您的useEffects,useCallback依赖数组没有设置正确的值。使用ESLint来帮助您解决这个问题!此外,这是React中内存泄漏的最大原因之一。在返回回调中清理您的状态和事件侦听器以避免内存泄漏。因为这种内存泄漏非常难以调试。
  • 始终关注控制台。它是您在工作中最好的朋友。解决控制台中显示的警告和错误可以修复许多令人讨厌的事情-您甚至没有意识到的错误和问题。

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

使用useEffect作为componentDidMountcomponentDidUpdatecomponentWillUnmount的混合,如React留档中所述。

要表现得像componentDidMount,你需要像这样设置你的useEffects:

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