我不知道为什么我的 React 组件渲染了两次。因此,我从 params 中提取电话号码并将其保存到 state 中,以便我可以通过 Firestore 进行搜索。一切似乎都工作正常,除了渲染两次......第一次渲染电话号码和零点。第二次渲染时所有数据都正确显示。有人可以指导我解决方案吗?
class Update extends Component {
constructor(props) {
super(props);
const { match } = this.props;
this.state = {
phoneNumber: match.params.phoneNumber,
points: 0,
error: ''
}
}
getPoints = () => {
firebase.auth().onAuthStateChanged((user) => {
if(user) {
const docRef = database.collection('users').doc(user.uid).collection('customers').doc(this.state.phoneNumber);
docRef.get().then((doc) => {
if (doc.exists) {
const points = doc.data().points;
this.setState(() => ({ points }));
console.log(points);
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
const error = 'This phone number is not registered yet...'
this.setState(() => ({ error }));
}
}).catch(function(error) {
console.log("Error getting document:", error);
});
} else {
history.push('/')
}
});
}
componentDidMount() {
if(this.state.phoneNumber) {
this.getPoints();
} else {
return null;
}
}
render() {
return (
<div>
<div>
<p>{this.state.phoneNumber} has {this.state.points} points...</p>
<p>Would you like to redeem or add points?</p>
</div>
<div>
<button>Redeem Points</button>
<button>Add Points</button>
</div>
</div>
);
}
}
export default Update;
您正在严格模式下运行您的应用程序。转到index.js并注释严格模式标记。您会发现单个渲染。
发生这种情况是 React.StrictMode 的有意功能。它只发生在开发模式下,应该有助于在渲染阶段发现意外的副作用。
来自文档:
严格模式无法自动为您检测副作用,但它可以通过使副作用更具确定性来帮助您发现它们。这是通过有意双重调用以下函数来完成的:...
^ 在本例中为
render
函数。
有关使用 React.StrictMode 时可能导致重新渲染的原因的官方文档:
https://reactjs.org/docs/strict-mode.html#detecting-unexpected-side-effects
这是因为 React 严格模式代码。
从 ReactDOM.render 代码中删除 -> React.StrictMode。
每次重新渲染时都会渲染 2 次:
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
将渲染1次:
ReactDOM.render(
<>
<App />
</>,
document.getElementById('root')
);
React 在
getPoints
完成异步操作之前渲染组件。
因此第一个
render
显示了 points
的初始状态,即 0
,然后 componentDidMount
被调用并触发异步操作。render
。
如果需要,您可以显示加载器或指示器,表明正在获取数据,但尚未准备好使用条件渲染进行显示。
只需添加另一个布尔键,如
isFetching
,在调用服务器时将其设置为true,在接收到数据时将其设置为false。
你的渲染可能看起来像这样:
render() {
const { isFetching } = this.state;
return (
<div>
{isFetching ? (
<div>Loading...</div>
) : (
<div>
<p>
{this.state.phoneNumber} has {this.state.points} points...
</p>
<p>Would you like to redeem or add points?</p>
<div>
<button>Redeem Points</button>
<button>Add Points</button>
</div>
</div>
)}
</div>
);
}
React.StrictMode,使其渲染两次,这样我们就不会在以下位置放置副作用
constructor
componentWillMount (or UNSAFE_componentWillMount)
componentWillReceiveProps (or UNSAFE_componentWillReceiveProps)
componentWillUpdate (or UNSAFE_componentWillUpdate)
getDerivedStateFromProps
shouldComponentUpdate
render
setState updater functions (the first argument)
所有这些方法都会被多次调用,因此避免其中产生副作用非常重要。如果我们忽略这个原则,很可能会导致不一致的状态问题和内存泄漏。
React.StrictMode 无法立即发现副作用,但它可以通过有意调用两次某些关键函数来帮助我们找到副作用。
这些功能是:
Class component constructor, render, and shouldComponentUpdate methods
Class component static getDerivedStateFromProps method
Function component bodies
State updater functions (the first argument to setState)
Functions passed to useState, useMemo, or useReducer
这种行为肯定会对性能产生一些影响,但我们不应该担心,因为它只发生在开发中而不是生产中。
信用:https://mariosfakiolas.com/blog/my-react-components-render-twice-and-drive-me-crazy/
这是故意通过反应来避免这种情况 删除
<React.StrictMode> </React.StrictMode>
来自index.js
我通过提供自定义钩子解决了这个问题。将下面的钩子放入您的代码中,然后:
// instead of this:
useEffect( ()=> {
console.log('my effect is running');
return () => console.log('my effect is destroying');
}, []);
// do this:
useEffectOnce( ()=> {
console.log('my effect is running');
return () => console.log('my effect is destroying');
});
这是钩子的代码:
export const useEffectOnce = ( effect => {
const destroyFunc = useRef();
const calledOnce = useRef(false);
const renderAfterCalled = useRef(false);
if (calledOnce.current) {
renderAfterCalled.current = true;
}
useEffect( () => {
if (calledOnce.current) {
return;
}
calledOnce.current = true;
destroyFunc.current = effect();
return ()=> {
if (!renderAfterCalled.current) {
return;
}
if (destroyFunc.current) {
destroyFunc.current();
}
};
}, []);
};
请参阅此博客了解解释。
嗯,我为此创建了一个解决方法挂钩。检查一下,如果有帮助的话:
import { useEffect } from "react";
const useDevEffect = (cb, deps) => {
let ran = false;
useEffect(() => {
if (ran) return;
cb();
return () => (ran = true);
}, deps);
};
const isDev = !process.env.NODE_ENV || process.env.NODE_ENV === "development";
export const useOnceEffect = isDev ? useDevEffect : useEffect;
CodeSandbox 演示:https://github.com/akulsr0/react-18-useeffect-twice-fix
除了 React StrictMode,另一个潜在原因是使用 next.js 重写,这是“预期行为”
React 使用其虚拟 dom 及其比较算法在内部监视和管理其渲染周期,因此您无需担心重新渲染的数量。让重新渲染由 React 来管理。即使渲染函数被调用,如果其中没有 props 或状态更改,有些子组件不会在 ui 上刷新。每个 setstate 函数调用都会通知 React 检查 diffing 算法,并调用 render 函数。
因此,在您的情况下,由于您在 getPoints 函数内定义了 setstate,因此它告诉 React 通过渲染函数重新运行比较过程。
在重写我的一些组件以使用 props 后,我注意到了这种行为。 我这样做破坏了页面的功能。 我正在修复道具的使用,但如果我将它们从子组件中删除,组件的双倍就会消失,实际的副本将适用于我正在使用的新流程。
对我来说,在 next.js 版本 14 中:
// next.config.mjs
const nextConfig = {
reactStrictMode: false,
}
export default nextConfig