Fiber架构的核心特性是可中断、可恢复的渲染过程,这直接导致某些生命周期方法可能被调用多次。想象一下,如果你正在搭建一座房子,但市政人员随时可能来检查而中断你的工作,那么有些准备工作就不能在"可能被中断"的阶段进行。
React团队将生命周期重新划分为三个阶段:
在这个阶段执行的生命周期方法可能被多次调用:
graph TD
A[废弃的生命周期方法] --> B[componentWillMount]
A --> C[componentWillReceiveProps]
A --> D[componentWillUpdate]
B --> E[原因:在Fiber下
可能被多次调用]
C --> E
D --> E
E --> F[可能导致副作用重复执行]
style A fill:#f96,stroke:#333,stroke-width:4px
想象React组件是一部电影制作过程:
关键区别:在传统拍摄中,如果你在"开拍前准备"阶段就订了餐厅庆功,但拍摄却一直被中断重来,你可能要重复预订多次!这就是为什么React不希望你在render前的生命周期做副作用操作。
// ❌ 旧代码(可能导致问题)
class OldComponent extends React.Component {
componentWillMount() {
// 发起数据请求
this.fetchData();
}
fetchData() {
fetch('/api/data').then(/*...*/);
}
render() { /*...*/ }
}
// ✅ 新代码(更安全)
class NewComponent extends React.Component {
constructor(props) {
super(props);
// 如果需要设置初始状态,放在这里
}
componentDidMount() {
// 副作用操作移到这里
this.fetchData();
}
fetchData() {
fetch('/api/data').then(/*...*/);
}
render() { /*...*/ }
}
// ❌ 旧代码
class OldSearchComponent extends React.Component {
componentWillReceiveProps(nextProps) {
if (nextProps.query !== this.props.query) {
this.setState({ loading: true });
this.fetchResults(nextProps.query);
}
}
fetchResults(query) {
fetch(`/api/search?q=${query}`).then(/*...*/);
}
render() { /*...*/ }
}
// ✅ 新代码
class NewSearchComponent extends React.Component {
// 静态方法,纯函数,不能访问this
static getDerivedStateFromProps(props, state) {
// 只负责返回新状态或null
if (props.query !== state.lastQuery) {
return {
loading: true,
lastQuery: props.query
};
}
return null;
}
// 状态变化后,在这里执行副作用
componentDidUpdate(prevProps, prevState) {
if (this.state.lastQuery !== prevState.lastQuery) {
this.fetchResults(this.state.lastQuery);
}
}
fetchResults(query) {
fetch(`/api/search?q=${query}`).then(/*...*/);
}
render() { /*...*/ }
}
class ChatThread extends React.Component {
listRef = React.createRef();
// 在DOM更新前捕获信息
getSnapshotBeforeUpdate(prevProps, prevState) {
// 保存滚动位置
if (prevProps.messages.length < this.props.messages.length) {
const list = this.listRef.current;
return list.scrollHeight - list.scrollTop;
}
return null;
}
// 使用snapshot参数获取之前保存的值
componentDidUpdate(prevProps, prevState, snapshot) {
if (snapshot !== null) {
const list = this.listRef.current;
// 维持滚动位置
list.scrollTop = list.scrollHeight - snapshot;
}
}
render() {
return (
{this.props.messages.map(msg => (
))}
);
}
}
Fiber使React团队重新思考生命周期方法应该如何工作,特别是对"纯函数"的强调:
在Fiber架构下,Render阶段的生命周期方法可能被调用多次,这是整个重构的核心原因:
// ❌ 危险代码:render阶段执行API调用
componentWillUpdate() {
// 这可能被执行多次!
this.updateExternalStore();
}
// ✅ 安全代码:commit阶段执行API调用
componentDidUpdate() {
// 只会在更新确实发生后执行一次
this.updateExternalStore();
}
Fiber架构不仅改变了类组件的生命周期,还催生了React Hooks这一全新范式:
// 使用Hooks管理副作用
function ProfilePage({ userId }) {
const [user, setUser] = useState(null);
// useEffect在commit阶段执行,类似componentDidMount/Update
useEffect(() => {
// 安全地执行副作用
fetchUser(userId).then(data => setUser(data));
// 清理函数类似componentWillUnmount
return () => {
cancelFetchUser();
};
}, [userId]); // 依赖数组控制执行时机
return (
{user ? : }
);
}
Hooks实际上更符合Fiber的设计理念,因为它们将逻辑清晰地分离为"渲染部分"和"副作用部分"。
// ✅ 优先使用新方法
static getDerivedStateFromProps()
getSnapshotBeforeUpdate()
// ❌ 避免在这些方法中执行副作用
// constructor()
// getDerivedStateFromProps()
// shouldComponentUpdate()
// render()
// ✅ 副作用应该放在这些方法中
// componentDidMount()
// componentDidUpdate()
// componentWillUnmount()
// ✅ 正确使用getDerivedStateFromProps
static getDerivedStateFromProps(props, state) {
if (props.selectedItem !== state.lastSelectedItem) {
return {
selectedIndex: props.items.indexOf(props.selectedItem),
lastSelectedItem: props.selectedItem
};
}
return null; // 没有变化返回null
}
// ✅ 使用Hooks简化状态和副作用管理
function ProfilePage({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetchUser(userId).then(setUser);
}, [userId]);
return user ? : ;
}
// ✅ 正确管理订阅
class StockTicker extends React.Component {
state = { price: 0 };
// 创建订阅
componentDidMount() {
this.subscription = subscribeToStock(
this.props.symbol,
price => this.setState({ price })
);
}
// 更新订阅
componentDidUpdate(prevProps) {
if (prevProps.symbol !== this.props.symbol) {
// 清理旧订阅
this.subscription.unsubscribe();
// 创建新订阅
this.subscription = subscribeToStock(
this.props.symbol,
price => this.setState({ price })
);
}
}
// 清理订阅
componentWillUnmount() {
this.subscription.unsubscribe();
}
render() {
return ${this.state.price};
}
}
// ✅ 使用getSnapshotBeforeUpdate保存DOM状态
class ScrollList extends React.Component {
listRef = React.createRef();
getSnapshotBeforeUpdate(prevProps) {
// 新增项目时保存滚动位置
if (prevProps.list.length < this.props.list.length) {
return this.listRef.current.scrollHeight;
}
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
// 如果有快照值,说明添加了新项目
if (snapshot !== null) {
const list = this.listRef.current;
list.scrollTop = list.scrollHeight - snapshot;
}
}
render() {
return (
{this.props.list.map(item => {item.text})}
);
}
}
核心记忆要点:
通过理解Fiber对生命周期的影响,你可以编写更加稳健的React应用,避免在异步渲染下可能出现的各种问题!