我有一个外部(组件)的可观察对象,我想监听它的变化。当对象更新时,它会发出更改事件,然后我想在检测到任何更改时重新渲染组件。
使用顶级 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 组件是否需要有状态才能重新渲染?有没有办法强制组件按需更新而不改变状态?
this.forceUpdate()
是正确的解决方案,而其余所有答案和一些评论都反对使用 forceUpdate()
。那么可以说这个问题还没有得到正确的解决方案/答案吗?
在类组件中,您可以调用 this.forceUpdate()
来强制重新呈现。
文档:https://facebook.github.io/react/docs/component-api.html
在函数组件中,没有 forceUpdate
的等价物,但您可以 contrive a way to force updates with the useState
hook。
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 使用密钥来确定重新渲染事物的最佳方式。
key
的滥用。首先,其意图不明确。阅读您的代码的人必须努力理解您为什么要以这种方式使用 key
。其次,这并不比 forceUpdate
更纯粹。 “纯”React 意味着你的组件的视觉呈现 100% 依赖于它的状态,所以如果你改变任何状态,它就会更新。但是,如果您有一些深度嵌套的对象(forceUpdate
文档引用了使用它的理由的场景),那么使用 forceUpdate
可以清楚地说明这一点。第三,Math.random()
是……随机的。理论上,它可以生成相同的随机数。
forceUpdate
是执行此操作的 React 方式。
在 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 内存泄漏的最大原因之一。在返回回调中清理您的状态和事件侦听器以避免内存泄漏。因为这样的内存泄漏非常难以调试。
始终关注控制台。它是你工作中最好的朋友。解决控制台中出现的警告和错误可以解决很多令人讨厌的事情——你甚至没有意识到的错误和问题。
有几件事我记得我做错了。万一有帮助..
forceUpdate
。如果你发现自己使用了任何强制更新的实现,这通常意味着你正在做一些不是 React 方式的事情。尽可能避免 forceUpdate ,除非你必须这样做!
实际上,forceUpdate()
是唯一正确的解决方案,因为如果在 shouldComponentUpdate()
中实现了其他逻辑或仅返回 false
,setState()
可能不会触发重新渲染。
强制性升级()
调用 forceUpdate() 将导致在组件上调用 render(),跳过 shouldComponentUpdate()。更多的...
设置状态()
除非在 shouldComponentUpdate() 中实现了条件渲染逻辑,否则 setState() 将始终触发重新渲染。更多的...
强制性升级()
this.forceUpdate()
Hooks:如何强制组件在 React 中使用 hooks 重新渲染?
顺便说一句:你是在改变状态还是你的嵌套属性不传播?
如何在 React 中更新嵌套状态属性
沙盒
this.state
分配状态也应该引发错误。 2016 年可能不会这样做;但是,我很确定它现在可以做到。
我通过执行以下操作避免了 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} /> )
当您希望两个不受关系(父子关系)约束的 React 组件进行通信时,建议使用 Flux 或类似的架构。
您要做的是监听 可观察组件 存储的更改,该存储保存模型及其接口,并将导致渲染更改的数据保存为 MyComponent
中的 state
。当商店推送新数据时,您会更改组件的状态,这会自动触发渲染。
通常你应该尽量避免使用 forceUpdate()
。从文档中:
通常你应该尽量避免使用 forceUpdate() 并且只在 render() 中读取 this.props 和 this.state。这使您的应用程序更加简单和高效
react-redux
中的 connect 函数根据您提供的映射函数在需要时自动将商店状态映射到组件道具。
forceUpdate
的有效用例
使用钩子或 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,发现它们也很好,但只是很多样板。我个人只是喜欢我自己的方法,因为我总是讨厌很多代码,因为你需要它时可以很简单。
this._data = {...this._data, ...newData}
。
所以我想我的问题是:React 组件是否需要有状态才能重新渲染?有没有办法强制组件按需更新而不改变状态?
其他答案试图说明你是如何做到的,但关键是你不应该这样做。即使是更改密钥的骇人听闻的解决方案也没有抓住重点。 React 的强大之处在于放弃了手动管理什么时候应该渲染的控制,而只关心自己应该如何映射输入。然后提供输入流。
如果您需要手动强制重新渲染,则几乎可以肯定您做的不对。
有几种方法可以重新渲染您的组件:
最简单的解决方案是使用 forceUpdate() 方法:
this.forceUpdate()
另一种解决方案是在状态(nonUsedKey)中创建未使用的密钥并调用 setState 函数更新此 nonUsedKey:
this.setState({ nonUsedKey: Date.now() } );
或者重写所有当前状态:
this.setState(this.state);
道具更改还提供组件重新渲染。
为了完整起见,您还可以在功能组件中实现这一点:
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
请注意,使用强制更新机制仍然是不好的做法,因为它违背了反应的心态,所以如果可能的话仍然应该避免。
你可以通过以下几种方式做到这一点:
<强> 1。使用 forceUpdate()
方法:
使用 forceUpdate()
方法时可能会出现一些故障。一个示例是它忽略 shouldComponentUpdate()
方法,并且无论 shouldComponentUpdate()
是否返回 false 都会重新渲染视图。因此,应尽可能避免使用 forceUpdate()。
<强> 2。将 this.state 传递给 setState()
方法
以下代码行克服了前面示例的问题:
this.setState(this.state);
实际上,所有这一切都是用触发重新渲染的当前状态覆盖当前状态。这仍然不一定是最好的方法,但它确实克服了使用 forceUpdate() 方法可能遇到的一些故障。
this.setState(prevState => prevState);
我们可以使用 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);
只是另一个回复以备份已接受的答案:-)
React 不鼓励使用 forceUpdate()
,因为它们通常对函数式编程有一种非常“这是唯一的方法”的方法。这在很多情况下都很好,但是许多 React 开发人员都有 OO 背景,使用这种方法,监听可观察对象是完全可以的。
如果你这样做了,你可能知道你必须在 observable “触发”时重新渲染,因此,你应该使用 forceUpdate()
并且这里不涉及 shouldComponentUpdate()
实际上是一个优点。
像 MobX 这样采用 OO 方法的工具实际上是在表面下执行此操作(实际上 MobX 直接调用 render()
)
forceUpdate(),但每次我听到有人谈论它时,都会跟进,你永远不应该使用它。
forceUpdate();
方法可行,但建议使用 setState();
为了完成您所描述的内容,请尝试 this.forceUpdate()。
另一种方法是调用 setState
,AND 保留状态:
this.setState(prevState=>({...prevState}));
您可以使用 this.forceUpdate() 强制重新渲染。你可以像这样使用它。
class App extends React.Component {
handleChange = () => {
this.forceUpdate();
};
render() {
return (
<>
<button onClick={this.handleChange}>Hello World</button>
</>
);
}
}
我发现最好避免 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
这似乎是最短的方法。
()=>{}
如 React 文档中所述,将 useEffect
用作 componentDidMount
、componentDidUpdate
和 componentWillUnmount
的组合。
要表现得像 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>
);
}
bruh如果我不关心持久化本地状态或任何数据,我只是到location.reload()
,这重新加载页面,也显然重新渲染组件
不定期副业成功案例分享
this.forceUpdate()
不是解决提问者问题的好方法,但它是对标题中提出的问题的正确答案。虽然通常应该避免这种情况,但在某些情况下 forceUpdate 是一个很好的解决方案。shouldComponentUpdate()
中实现额外的逻辑,使您的解决方案无效。