为何this.setState()触发已卸载组件警告?请求排查代码问题
Let's break down exactly what's happening here and fix this warning step by step.
Why You're Seeing This Warning
That warning pops up because when you navigate back from your roles2 page to home, your Empty component gets unmounted from the screen. But the Firestore async request you started earlier (with db.collection("roles").get()) is still running in the background. When it finishes, it tries to call setState on a component that no longer exists—hence the error.
Step 1: Remove the Unnecessary componentWillUpdate Logic
First off, your componentWillUpdate method is causing way more problems than it solves. It fires every time the component updates, which means you're spawning duplicate Firestore requests constantly. Delete this entire block:
componentWillUpdate(nextProps, nextState) { this.roles = db.collection("roles").get(); this.setItemsFromFirestore(this.roles); }
You already fetch data in componentDidMount—this extra fetch is redundant and creates more opportunities for memory leaks.
Step 2: Add a Mounted Flag to Block Post-Unmount setState
We'll add a simple flag to track if the component is still mounted, so we only call setState when it's safe. Update your component code like this:
constructor(props) { super(props); this.ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2}) this.state = { dataSource: this.ds.cloneWithRows(list), }; this._isMounted = false; // Initialize our mounted flag this.renderItem = this.renderItem.bind(this) this.setItemsFromFirestore = this.setItemsFromFirestore.bind(this); } componentDidMount() { this._isMounted = true; // Mark component as mounted when it loads const roles = db.collection("roles").get(); this.setItemsFromFirestore(roles); } componentWillUnmount() { this._isMounted = false; // Mark as unmounted when navigating away } setItemsFromFirestore(roles) { roles.then((querySnapshot) => { var items = []; querySnapshot.forEach((child) => { items.push({ label: `${child.data().label}`, description: `${child.data().description}`, id: `${child.id}` }); }); // Only update state if the component is still mounted! if (this._isMounted) { this.setState({ dataSource: this.ds.cloneWithRows(items), }); } }); }
Step 3: (Optional but Highly Recommended) Switch to Real-Time Firestore Listener
If your roles data might update while the component is active, using onSnapshot instead of get() will give you real-time updates and let you clean up the listener properly when the component unmounts. Here's how to refactor that:
constructor(props) { super(props); this.ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2}) this.state = { dataSource: this.ds.cloneWithRows(list), }; this._isMounted = false; this._firestoreUnsubscribe = null; // Store the unsubscribe function this.renderItem = this.renderItem.bind(this); } componentDidMount() { this._isMounted = true; // Set up real-time listener this._firestoreUnsubscribe = db.collection("roles").onSnapshot((querySnapshot) => { var items = []; querySnapshot.forEach((child) => { items.push({ label: `${child.data().label}`, description: `${child.data().description}`, id: `${child.id}` }); }); if (this._isMounted) { this.setState({ dataSource: this.ds.cloneWithRows(items), }); } }); } componentWillUnmount() { this._isMounted = false; // Clean up the Firestore listener to stop updates if (this._firestoreUnsubscribe) { this._firestoreUnsubscribe(); } }
Quick Bonus Fixes
- Stop using
ListView: React Native has deprecatedListViewin favor ofFlatList/SectionList—they're more performant and have better built-in features. - Fix list item keys: Using
Uuid()inrenderItemcreates a new key every render, which hurts performance. Use the Firestore document ID instead:<Item key={item.id} ... />
内容的提问来源于stack exchange,提问作者Matthias Wegner

