React复习:数据管理+生命周期

67 阅读28分钟

声明:GLM4.5生成,辅助复习

简洁简洁

  • 使用简洁的大白话同时结合案例,帮我复习React知识之数据管理 简洁简洁

复习React数据管理,咱们用“过日子”的比喻来串,保证简单易懂!

一、组件自己的“小本本”:State(状态)

大白话

每个组件就像一个独立的人,有自己的“小本本”(state),记着属于自己的事(比如计数器点了多少次、输入框打了什么字)。这个小本本只有自己能改,改了之后自己会“重新打扮”(重新渲染),展示新样子。

核心工具

useState:给组件开个“小本本”,格式是const [数据, 改数据的函数] = useState(初始值)

案例:计数器
import { useState } from 'react';

function Counter() {
  // 开个小本本,记“count”,初始值0
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>你点了 {count} 次</p>
      {/* 点按钮就改小本本里的count,加1 */}
      <button onClick={() => setCount(count + 1)}>点我+1</button>
    </div>
  );
}

关键点

  • count是“小本本”里的数据,setCount是改数据的笔(只有用这笔改,组件才会重新渲染)。
  • 直接改count(比如count++)没用!必须用setCount,React才会知道“数据变了,你该更新了”。

二、父母给孩子的“零花钱”:Props(属性)

大白话

父组件像父母,子组件像孩子。父母可以把“零花钱”(数据)通过props给孩子,孩子只能花(用),不能改(props是只读的)。如果孩子想改零花钱,得让父母用自己的“小本本”(state)改,再重新给孩子。

案例:父组件传名字给子组件
// 子组件(孩子):接收父母给的“name”零花钱
function Child({ name }) {
  return <p>我叫 {name}</p>;
}

// 父组件(父母):有自己的“小本本”记名字,传给孩子
function Parent() {
  const [childName, setChildName] = useState("小明");

  return (
    <div>
      {/* 把childName通过props(name)传给Child */}
      <Child name={childName} />
      {/* 父母改自己小本本,孩子的“零花钱”自动更新 */}
      <button onClick={() => setChildName("小红")}>改孩子名字</button>
    </div>
  );
}

关键点

  • 子组件通过props(这里是{ name }解构)收数据,只能读,不能直接改(比如name = "小红"会报错)。
  • 数据流向:父组件state → props → 子组件。子组件想改数据,得“通知”父组件改state(比如父组件传个函数给子组件,子组件调用这个函数)。

三、家族的“传家宝”:Context(跨组件共享数据)

大白话

如果家族很大(比如爷爷→爸爸→孙子→重孙),爷爷想把“传家宝”(数据)直接给重孙,不用一层层传(props drilling),就用Context。Context就像“家族公告栏”,爷爷把传家宝放上去,所有后代都能直接拿。

核心工具

createContext(创建公告栏)、Provider(放数据的人)、useContext(拿数据的人)。

案例:主题切换(爷爷直接给重孙传主题)
import { createContext, useContext, useState } from 'react';

// 1. 创建“家族公告栏”(ThemeContext),默认值是“light”
const ThemeContext = createContext("light");

// 重孙组件:直接从公告栏拿主题
function GreatGrandchild() {
  const theme = useContext(ThemeContext); // 拿公告栏的数据
  return <p>当前主题:{theme}</p>;
}

// 爷爷组件:用Provider把主题放公告栏,所有后代都能拿
function Grandpa() {
  const [theme, setTheme] = useState("light");

  return (
    // ThemeContext.Provider:给公告栏放数据,value是“传家宝”
    <ThemeContext.Provider value={theme}>
      <div>
        <GreatGrandchild />
        <button onClick={() => setTheme("dark")}>切换暗黑主题</button>
      </div>
    </ThemeContext.Provider>
  );
}

关键点

  • Context解决“props层层传”的麻烦,适合全局共享的数据(比如主题、用户登录状态)。
  • Provider包裹组件,通过value传数据;后代用useContext直接拿,不用中间组件传props。

四、公司的“总部”:Redux(复杂状态管理)

大白话

如果应用很大(比如电商网站),有很多组件需要共享数据(购物车、用户信息等),用Context会很乱(比如多个公告栏,不知道听谁的)。Redux就像“公司总部”,统一管理所有数据(store),组件要改数据,得“打报告”(dispatch action),总部根据报告(action)找对应部门(reducer)改数据,改完通知所有组件“数据更新了”。

核心概念
  • Store:总部仓库,存所有数据。
  • Action:报告(比如“添加商品到购物车”),格式是{ type: "ADD_CART", payload: 商品信息 }
  • Reducer:部门(比如“购物车部门”),根据action报告改store里的数据(纯函数,输入旧数据+action,输出新数据)。
  • Dispatch:打报告的按钮,组件用dispatch(action)告诉总部“我要改数据”。
案例:购物车(Redux管理)
1. 安装Redux:npm install @reduxjs/toolkit react-redux
2. 创建“购物车部门”(reducer)和总部(store)
// cartSlice.js(用Redux Toolkit简化写法)
import { createSlice } from '@reduxjs/toolkit';

// 创建购物车“部门”
const cartSlice = createSlice({
  name: 'cart',
  initialState: { items: [] }, // 初始数据:空购物车
  reducers: {
    // 添加商品到购物车的“报告处理方式”
    addItem: (state, action) => {
      state.items.push(action.payload); // 直接改state(Toolkit内部会处理成不可变更新)
    },
    // 删除商品的“报告处理方式”
    removeItem: (state, action) => {
      state.items = state.items.filter(item => item.id !== action.payload);
    }
  }
});

// 导出“打报告”的函数(addItem, removeItem)
export const { addItem, removeItem } = cartSlice.actions;
// 导出“部门”(reducer),给总部用
export default cartSlice.reducer;
3. 创建“总部”(store)
// store.js
import { configureStore } from '@reduxjs/toolkit';
import cartReducer from './cartSlice';

// 创建总部,把“购物车部门”挂进去
const store = configureStore({
  reducer: {
    cart: cartReducer // 购物车数据归cartReducer管
  }
});

export default store;
4. 组件用“总部数据”和“打报告”
// App.js(用Provider包裹整个应用,让所有组件能访问总部)
import { Provider } from 'react-redux';
import store from './store';
import Cart from './Cart';

function App() {
  return (
    <Provider store={store}>
      <Cart />
    </Provider>
  );
}

// Cart.js(购物车组件)
import { useSelector, useDispatch } from 'react-redux';
import { addItem, removeItem } from './cartSlice';

function Cart() {
  // 从总部拿购物车数据(useSelector相当于“去总部查数据”)
  const cartItems = useSelector(state => state.cart.items);
  // 拿“打报告”的按钮(dispatch)
  const dispatch = useDispatch();

  const itemToAdd = { id: 1, name: "苹果", price: 5 };

  return (
    <div>
      <h2>购物车</h2>
      <ul>
        {cartItems.map(item => (
          <li key={item.id}>
            {item.name} - {item.price}元
            <button onClick={() => dispatch(removeItem(item.id))}>删除</button>
          </li>
        ))}
      </ul>
      {/* 点按钮就打“添加商品”的报告 */}
      <button onClick={() => dispatch(addItem(itemToAdd))}>添加苹果</button>
    </div>
  );
}

关键点

  • Redux适合“多组件共享复杂数据”的场景(比如购物车、用户权限、全局配置)。
  • 数据流向:组件dispatch(action)store调用reducerreducerstore数据 → 组件通过useSelector自动更新。

五、从“服务器”拿数据:React Query(服务端状态管理)

大白话

前面说的state、props、Context、Redux都是管“客户端数据”(比如用户输入、购物车)。但如果数据来自服务器(比如API接口),用它们会很麻烦(比如手动处理加载中、错误、缓存)。React Query就像“快递员”,专门管“服务器数据”:帮你取数据、存缓存、自动更新(比如数据过期了重新取),还能显示“快递在路上”(加载中)、“快递丢了”(错误)。

核心工具

useQuery:取数据(查询),useMutation:改数据(增删改)。

案例:获取用户列表(显示加载中、错误、数据)
1. 安装:npm install @tanstack/react-query
2. 用Provider包裹应用(给快递员“工作范围”)
// App.js
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import UserList from './UserList';

// 创建“快递员总部”(QueryClient)
const queryClient = new QueryClient();

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <UserList />
    </QueryClientProvider>
  );
}
3. 组件用useQuery取数据
// UserList.js
import { useQuery } from '@tanstack/react-query';

// 模拟从服务器取数据的函数(实际用fetch/axios)
const fetchUsers = async () => {
  const res = await fetch('https://jsonplaceholder.typicode.com/users');
  return res.json();
};

function UserList() {
  // 用useQuery取数据,参数:[数据唯一标识, 取数据函数]
  const { data, isLoading, error } = useQuery({
    queryKey: ['users'], // 数据的唯一标识(比如“用户列表”)
    queryFn: fetchUsers  // 取数据的函数
  });

  if (isLoading) return <p>加载中...</p>; // 快递在路上
  if (error) return <p>出错了:{error.message}</p>; // 快递丢了

  return (
    <ul>
      {data.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

关键点

  • React Query专门管“服务器数据”,自动处理缓存(比如第二次取同一数据直接用缓存)、加载状态、错误状态。
  • useQuery用于“查数据”(GET请求),useMutation用于“改数据”(POST/PUT/DELETE请求,比如添加用户后自动刷新列表)。

总结:什么时候用什么?

场景工具比喻
组件自己记数据useState自己的小本本
父子传数据props父母给孩子的零花钱
跨多层组件共享数据Context家族公告栏(传家宝)
大应用复杂状态管理Redux公司总部(统一管理)
服务器数据(API)React Query快递员(取、存、自动更新)

记住:简单用state/props,跨组件用Context,复杂状态用Redux,服务器数据用React Query,数据管理就清清楚楚啦!

  • 使用简洁的大白话同时结合案例,讲解React setState 调用的原理,React setState 调用之后发生了什么?是同步还是异步?React中的setState批量更新的过程是什么?React中有使用过getDefaultProps吗?它有什么作用?React中setState的第二个参数作用是什么?React中的setState和replaceState的区别是什么?在React中组件的this.state和setState有什么区别?state 是怎么注入到组件的,从 reducer 到组件经历了什么样的过程React组件的state和props有什么区别?React中的props为什么是只读的?在React中组件的props改变时更新组件的有哪些方法?React中怎么检验props?验证props的目的是什么?

好的,咱们用大白话+生活案例,把React状态管理这些“老大难”问题一次性讲透!


一、setState 调用原理 & 调用后发生了什么?同步还是异步?

大白话原理

setState 就像你给 React 状态更新中心 打电话说:“嘿,我的状态要变了!新值是XXX!”
React 不会立刻帮你改(异步),而是先记在小本本(更新队列)上。等它忙完手头的事(比如处理事件、渲染组件),再一次性把所有记录的状态更新处理掉(批量更新),最后重新渲染组件(“刷个新界面”)。

调用后发生了什么?(分步说)
  1. 你打电话(调用 setState
    this.setState({ count: 1 }); // 告诉React:count要改成1
    
  2. React 记账(加入队列)
    React 把 { count: 1 } 这个更新请求塞进一个叫 “更新队列” 的口袋里(不立刻执行)。
  3. React 找时机批量处理
    等 React 当前任务忙完(比如点击事件处理完),它会打开口袋,把里面所有更新请求 合并处理(比如你连续调了3次 setState,它可能合并成一次)。
  4. 计算新状态 & 重新渲染
    React 用合并后的新状态替换旧状态,然后触发组件的 render() 方法,生成新界面(“刷个屏”)。
同步还是异步?
  • 异步(大部分情况)
    在 React 事件(如 onClick)、生命周期函数中调用 setState,是异步的(React 要攒一起批量处理)。
    案例
    handleClick() {
      this.setState({ count: 1 }); // 异步:先记账,不立刻改
      console.log(this.state.count); // 输出 0(还是旧值!)
      this.setState({ count: 2 }); // 再记账
      console.log(this.state.count); // 输出 0(还是旧值!)
      // React 事件结束后,批量处理:count = 2,然后重新渲染
    }
    
  • 同步(特殊情况)
    setTimeoutPromise.then()、原生 DOM 事件等 React 控制不到 的地方调用,React 会立刻处理(同步)。
    案例
    handleClick() {
      setTimeout(() => {
        this.setState({ count: 1 }); // 同步:立刻改!
        console.log(this.state.count); // 输出 1(新值!)
      }, 0);
    }
    

二、setState 批量更新的过程是什么?

大白话过程

批量更新就像 食堂打饭

  1. 你排队喊:“我要一份饭!”(setState
  2. 食堂阿姨(React)不立刻打,而是继续问后面的人:“你要什么?”(继续记账)
  3. 等队伍没人了(当前任务结束),阿姨一次性把所有人的饭打完(批量处理更新),然后叫大家取餐(重新渲染)。
技术过程
  1. React 进入“批量模式”
    在事件处理、生命周期开始时,React 开启一个“批量更新开关”(isBatchingUpdates = true)。
  2. 收集更新请求
    所有 setState 调用不会立即执行,而是被推入 更新队列dirtyComponents)。
  3. 关闭“批量模式” & 执行更新
    事件/生命周期结束时,React 关闭开关(isBatchingUpdates = false),然后:
    • 合并队列中的多个更新(比如 { count: 1 }{ count: 2 } 合并成 { count: 2 })。
    • 用合并后的新状态调用 shouldComponentUpdate(判断是否需要更新)。
    • 如果需要更新,调用 render() 生成新 Virtual DOM,对比后更新真实 DOM。

三、React 中有使用过 getDefaultProps 吗?它有什么作用?

大白话

getDefaultProps老版本 React(16.3 前) 给组件设置 默认 props 的方法,就像给手机设置“默认壁纸”:如果用户没传壁纸,就用默认的。

作用
  • 为 props 设置默认值,避免因未传 props 导致报错(如 undefined 调用方法)。
  • 案例(老写法)
    class MyComponent extends React.Component {
      static getDefaultProps() {
        return { name: "默认用户" }; // 如果没传 name,就用这个
      }
      render() {
        return <div>你好,{this.props.name}</div>;
      }
    }
    // 使用时:<MyComponent /> → 显示“你好,默认用户”
    
现在怎么写?

React 16.3+ 推荐用 静态属性 defaultProps

class MyComponent extends React.Component {
  static defaultProps = {
    name: "默认用户" // 效果一样,更简洁
  };
  // 或函数组件:
  // MyComponent.defaultProps = { name: "默认用户" };
}

四、setState 的第二个参数作用是什么?

大白话

第二个参数是 回调函数,就像你告诉快递员:“送完货给我打个电话!”——React 在 状态更新完成、界面渲染后,会自动调用这个函数。

作用
  • 在状态更新后执行操作(比如发请求、操作 DOM)。
  • 案例
    this.setState(
      { count: 1 }, // 第一个参数:新状态
      () => {       // 第二个参数:回调函数
        console.log("状态更新完成!新值是:", this.state.count); // 输出 1
        document.getElementById("myDiv").scrollIntoView(); // 滚动到某个元素
      }
    );
    
注意

现在更推荐用 useEffect 替代(函数组件):

useEffect(() => {
  console.log("count 更新了:", count); // 依赖 count,更新后执行
}, [count]);

五、setState 和 replaceState 的区别?

大白话
  • setState修改 状态(像在笔记本上 改一行字,其他行保留)。
  • replaceState替换 整个状态(像 撕掉旧笔记本,换本新的,旧状态全丢)。
区别对比
特点setStatereplaceState(已废弃)
操作方式合并旧状态({ ...oldState, ...newState }直接覆盖旧状态(丢弃未更新的属性)
案例this.setState({ a: 1 }) → 保留 b, cthis.replaceState({ a: 1 }) → 只剩 a
现状正在使用React 16+ 已废弃,用 setState 替代
为什么废弃 replaceState

容易误操作导致状态丢失(比如不小心把其他状态属性删了),setState 的合并方式更安全。


六、组件的 this.state 和 setState 有什么区别?

大白话
  • this.state当前状态(像你钱包里的 现金,可以直接看,但别直接改!)。
  • setState修改状态的工具(像 银行柜台,改钱必须通过它,不然会出问题)。
核心区别
特点this.statesetState
作用读取当前状态修改状态并触发更新
能否直接修改❌ 绝对禁止!(React 不会知道你改了)✅ 唯一合法修改方式
案例console.log(this.state.count)this.setState({ count: 1 })
后果直接改 → 界面不更新,数据混乱正确触发更新流程

七、state 是怎么注入到组件的?从 reducer 到组件的过程(以 Redux 为例)

大白话流程

Redux 的 state 像公司 总部仓库,组件像 门店

  1. 仓库(Store):存所有 state(用 createStore 创建)。
  2. 门店申请(Connect):组件用 connect(或 useSelector)告诉总部:“我要这些数据!”
  3. 总部发货(Provider)Provider 组件把仓库“快递服务”覆盖到整个应用。
  4. 门店收货(State 注入):组件通过 props(或 useSelector 返回值)收到 state,就能用了。
技术流程
// 1. 创建仓库(Store)
const store = createStore(reducer); // reducer 是“仓库管理员”

// 2. 用 Provider 包裹应用(提供快递服务)
<Provider store={store}>
  <App />
</Provider>

// 3. 组件连接仓库(门店申请)
class MyComponent extends React.Component {
  render() {
    // 4. state 通过 props 注入到组件!
    return <div>{this.props.count}</div>; // count 来自仓库的 state
  }
}

// 用 connect 连接组件和仓库
const mapStateToProps = (state) => ({
  count: state.count // 从仓库取 count,注入到组件的 props
});
export default connect(mapStateToProps)(MyComponent);

函数组件用 useSelector 更简单

function MyComponent() {
  const count = useSelector(state => state.count); // 直接从仓库取数据
  return <div>{count}</div>;
}

八、组件的 state 和 props 有什么区别?

大白话
  • state:组件 自己的钱(自己管,自己改,改了自己刷新)。
  • props:别人 给你的钱(只能花,不能改,想改得让主人改完再给你)。
核心区别
特点StateProps
来源组件内部自己定义(this.state父组件传递(<Child name="小明" />
可修改性✅ 组件自己用 setState 修改❌ 只读(子组件不能改)
作用管理组件内部动态数据(如输入框内容)父子组件通信(传递配置、数据)
案例计数器的 count父组件传给子组件的 用户名

九、React 中的 props 为什么是只读的?

大白话

props 是 父母给孩子的零花钱,孩子如果自己乱改(比如把 10 元改成 100 元),会导致:

  1. 父母不知道:父母以为孩子只有 10 元,实际孩子有 100 元(数据不一致)。
  2. 家庭混乱:其他亲戚(兄弟组件)也以为孩子有 10 元,结果用错了(数据流混乱)。

React 强制 props 只读,是为了 保证数据单向流动(父→子),让数据流向清晰可预测。

技术原因
  • 单向数据流:React 的核心设计,数据从父组件流向子组件,子组件改 props 会破坏这个规则。
  • 可维护性:如果子组件能改 props,调试时很难追踪数据变化(“谁改了它?”)。

十、props 改变时更新组件的方法

大白话

props 变了,就像别人给了你新零花钱,你要 重新算账(更新组件)。React 默认会帮你更新,但你可以“插手”决定更不更新。

方法
  1. 默认更新(React 自动)
    父组件传新 props → 子组件自动 render()(大部分情况够用)。
  2. 手动控制更新(shouldComponentUpdate
    在类组件中,通过 shouldComponentUpdate 决定是否更新。
    案例:只有 props.name 变了才更新:
    shouldComponentUpdate(nextProps) {
      return nextProps.name !== this.props.name; // name变了才返回true
    }
    
  3. 性能优化(React.PureComponent / React.memo
    • PureComponent(类组件):自动浅比较 props 和 state,没变就不更新。
    • React.memo(函数组件):同上,包裹函数组件即可。
      案例
    function MyComponent(props) { /* ... */ }
    export default React.memo(MyComponent); // props没变就不重新渲染
    

十一、怎么检验 props?验证 props 的目的是什么?

大白话

检验 props 就像 检查快递包裹

  • 目的:防止收到“假货”(错误类型的数据),避免组件报错(比如传数字却传了字符串)。
检验方法
  1. PropTypes(推荐)
    React 官方库,专门用来检查 props 类型。
    安装npm install prop-types
    案例
    import PropTypes from 'prop-types';
    
    function MyComponent({ name, age }) {
      return <div>{name} - {age}</div>;
    }
    
    MyComponent.propTypes = {
      name: PropTypes.string.isRequired, // name必须是字符串,必传
      age: PropTypes.number              // age必须是数字
    };
    // 如果传 <MyComponent name={123} />,控制台会报错!
    
  2. TypeScript(更强大)
    在 TS 中直接定义 props 类型,编译时就能报错。
    案例
    interface Props {
      name: string;
      age?: number; // ?表示可选
    }
    
    function MyComponent({ name, age }: Props) {
      // ...
    }
    // <MyComponent name={123} /> → 编译时报错!
    
验证 props 的目的
  1. 防错:避免因 props 类型错误导致组件崩溃(如 undefined.length)。
  2. 文档PropTypes 相当于组件的“说明书”,告诉其他开发者怎么用。
  3. 调试:控制台警告能快速定位 props 传错的地方。

总结一张表

问题点核心答案
setState 原理异步记账 → 批量处理 → 重新渲染
同步/异步事件中异步,setTimeout/Promise 中同步
批量更新React 开启批量模式 → 收集更新 → 合并处理 → 渲染
getDefaultProps老版本设置 props 默认值,现用 defaultProps 静态属性
setState 第二个参数状态更新后的回调函数(现多用 useEffect 替代)
setState vs replaceStatesetState 合并状态,replaceState 替换状态(已废弃)
state vs setStatestate 是当前状态(只读),setState 是修改状态的唯一方式
state 注入流程(Redux)Store → Provider → connect/useSelector → 组件 props/state
state vs propsstate 是组件自己的数据(可改),props 是父组件传的数据(只读)
props 只读原因保证单向数据流,避免数据混乱
props 改变更新方法默认自动更新,可用 shouldComponentUpdatePureComponentReact.memo 控制
props 检验方法PropTypes(运行时检查)或 TypeScript(编译时检查)
props 检验目的防错、文档化、方便调试

用大白话讲完这些,React 状态管理是不是清晰多了?记住核心:state 是自己的,props 是别人的;改状态用 setState,别瞎动 props;批量更新是 React 的“懒人优化”,PropTypes 是“防错神器”

  • 使用简洁的大白话同时结合案例,帮我复习React知识之生命周期

好的,咱们用“人的一生”来比喻React组件的生命周期,结合大白话和实际案例,保证你一看就懂!


一、生命周期是什么?

组件从“出生”(创建)→ “成长”(更新)→ “死亡”(卸载)的整个过程,React在不同阶段提供“钩子函数”(生命周期方法),让你在合适的时机做该做的事(比如出生时起名字、成长时换衣服、死亡时写遗书)。


二、生命周期三大阶段(类比人的一生)

1. 挂载阶段(Mounting)——组件“出生”

流程:怀孕(创建)→ 出生(插入DOM)→ 办出生证(初始化完成)
钩子函数顺序
constructorgetDerivedStateFromPropsrendercomponentDidMount

(1)constructor(怀孕期)
  • 大白话:组件“怀上了”,初始化“基因”(state)和“本能”(绑定方法)。
  • 作用
    • 初始化state(this.state = { ... }
    • 绑定事件处理函数(this.handleClick = this.handleClick.bind(this)
  • 案例
    class Baby extends React.Component {
      constructor(props) {
        super(props); // 必须写!相当于“继承父母的基因”
        this.state = { name: "小明", age: 0 }; // 初始化state(出生时的名字和年龄)
        this.cry = this.cry.bind(this); // 绑定“哭”这个本能
      }
      cry() {
        console.log("哇哇哇~");
      }
    }
    
(2)render(出生)
  • 大白话:组件“出生”了,第一次“亮相”(渲染到DOM上)。
  • 作用
    • 必须实现! 返回要显示的JSX(就像婴儿出生后第一次被看到的样子)。
    • 不能改state! 纯粹的“展示”,不能有副作用(比如不能在这里发网络请求)。
  • 案例
    render() {
      return (
        <div>
          <p>我叫{this.state.name},今年{this.state.age}岁</p>
          <button onClick={this.cry}>哭一下</button>
        </div>
      );
    }
    
(3)componentDidMount(办出生证)
  • 大白话:组件“出生”后,医院给它办出生证(DOM操作、发请求、定时器等“正式工作”)。
  • 作用
    • 操作DOM(比如获取元素尺寸、添加事件监听)
    • 发网络请求(比如从服务器获取数据)
    • 启动定时器(比如倒计时)
  • 案例:从服务器获取用户信息
    componentDidMount() {
      // 发请求获取用户数据(相当于“上户口”)
      fetch("https://api.example.com/user")
        .then(res => res.json())
        .then(data => {
          this.setState({ name: data.name, age: data.age }); // 更新state
        });
    
      // 启动定时器(比如“成长倒计时”)
      this.timer = setInterval(() => {
        this.setState(prevState => ({ age: prevState.age + 1 }));
      }, 1000);
    }
    

2. 更新阶段(Updating)——组件“成长”

触发条件

  • 父组件传了新props(比如父母给孩子换了新衣服)
  • 自己调用了setState(比如孩子自己长高了)

流程:检查成长需求 → 决定是否成长 → 长个子(重新渲染)→ 成长后调整
钩子函数顺序
getDerivedStateFromPropsshouldComponentUpdaterendergetSnapshotBeforeUpdatecomponentDidUpdate

(1)getDerivedStateFromProps(检查成长需求)
  • 大白话:父母(props)给孩子新衣服,孩子先检查“这件衣服我能不能穿?”(根据props更新state)。
  • 作用
    • 静态方法(不能访问this),接收propsstate,返回新state或null
    • 少用! 大部分场景用其他方法替代,容易导致逻辑复杂。
  • 案例:props变时同步更新state
    static getDerivedStateFromProps(props, state) {
      // 如果父组件传了新name,更新state的name
      if (props.name !== state.name) {
        return { name: props.name };
      }
      return null; // 不更新state
    }
    
(2)shouldComponentUpdate(决定是否成长)
  • 大白话:孩子问自己:“我真的需要长个子吗?”(避免不必要的重新渲染)。
  • 作用
    • 返回true(更新)或false(不更新),默认true
    • 性能优化神器! 比如props没变时就不重新渲染。
  • 案例:只有年龄变化时才更新
    shouldComponentUpdate(nextProps, nextState) {
      // 只有年龄变了才更新(名字变了不更新)
      return nextState.age !== this.state.age;
    }
    
(3)render(长个子)
  • 大白话:孩子“长个子”了(重新渲染),展示新样子。
  • 注意:和挂载阶段的render一样,必须实现且不能改state!
(4)getSnapshotBeforeUpdate(成长前拍照)
  • 大白话:孩子长个子前,先拍张照片(记录更新前的DOM信息,比如滚动位置)。
  • 作用
    • 在DOM更新前调用,返回值会传给componentDidUpdate
    • 场景:处理滚动位置、DOM尺寸等(比如聊天窗口更新前记住滚动条位置)。
  • 案例:聊天窗口更新前记住滚动位置
    getSnapshotBeforeUpdate(prevProps, prevState) {
      // 如果消息列表更新了,记住当前滚动位置
      if (this.props.list.length !== prevProps.list.length) {
        return this.listRef.scrollHeight; // 返回滚动高度
      }
      return null;
    }
    
(5)componentDidUpdate(成长后调整)
  • 大白话:孩子长个子后,调整衣服(操作DOM、发请求等)。
  • 作用
    • 接收3个参数:prevProps(旧props)、prevState(旧state)、snapshotgetSnapshotBeforeUpdate的返回值)。
    • 注意:如果要调用setState,必须加条件判断,否则会死循环!
  • 案例:聊天窗口更新后恢复滚动位置
    componentDidUpdate(prevProps, prevState, snapshot) {
      // 如果消息列表更新了,恢复滚动位置
      if (this.props.list.length !== prevProps.list.length && snapshot !== null) {
        this.listRef.scrollTop = snapshot; // 恢复滚动位置
      }
    }
    

3. 卸载阶段(Unmounting)——组件“死亡”

流程:写遗书(清理工作)→ 永别(从DOM移除)
钩子函数componentWillUnmount

componentWillUnmount(写遗书)
  • 大白话:组件“死亡”前,写遗书(清理定时器、取消订阅、移除事件监听等)。
  • 作用
    • 清理副作用! 避免内存泄漏(比如定时器没关会一直运行)。
  • 案例:清除定时器和事件监听
    componentWillUnmount() {
      // 清除定时器(避免“死了还在长个子”)
      clearInterval(this.timer);
    
      // 移除事件监听(避免“死了还在听消息”)
      window.removeEventListener("resize", this.handleResize);
    }
    

三、旧版生命周期(已废弃,了解即可)

React 16.3前有一些生命周期方法,现在标记为UNSAFE_(不安全),因为容易导致问题:

  • componentWillMount(挂载前):现在用componentDidMount替代(避免服务端渲染不一致)。
  • componentWillReceiveProps(props更新前):现在用getDerivedStateFromProps替代(避免多次渲染)。
  • componentWillUpdate(更新前):现在用getSnapshotBeforeUpdate+componentDidUpdate替代(避免DOM操作时机错误)。

四、函数组件的生命周期(Hooks模拟)

函数组件没有生命周期方法,但用useEffect可以模拟:

  • useEffect(() => { ... }, []):模拟componentDidMount(挂载后执行)。
  • useEffect(() => { ... return () => { ... } }, [dep]):模拟componentDidUpdate+componentWillUnmount(依赖变化时执行,卸载时清理)。
案例:函数组件模拟生命周期
import { useState, useEffect } from "react";

function FunctionBaby() {
  const [age, setAge] = useState(0);

  // 模拟componentDidMount(挂载后执行)
  useEffect(() => {
    console.log("组件挂载了!");
    const timer = setInterval(() => {
      setAge(prev => prev + 1);
    }, 1000);

    // 模拟componentWillUnmount(卸载时清理)
    return () => {
      clearInterval(timer);
      console.log("组件卸载了!");
    };
  }, []); // 空依赖,只在挂载/卸载时执行

  // 模拟componentDidUpdate(age变化时执行)
  useEffect(() => {
    console.log("年龄更新了:", age);
  }, [age]); // 依赖age

  return <p>今年{age}岁</p>;
}

五、生命周期总结表

阶段钩子函数大白话比喻作用/案例
挂载constructor怀孕期初始化state、绑定方法
render出生第一次渲染JSX
componentDidMount办出生证发请求、操作DOM、启动定时器
更新getDerivedStateFromProps检查成长需求根据props更新state(少用)
shouldComponentUpdate决定是否成长性能优化(避免不必要渲染)
render长个子重新渲染JSX
getSnapshotBeforeUpdate成长前拍照记录DOM信息(如滚动位置)
componentDidUpdate成长后调整操作DOM、发请求(加条件判断)
卸载componentWillUnmount写遗书清理定时器、取消订阅、移除事件监听

六、经典面试题

Q1:为什么在componentDidMount里发请求,不在constructorrender里?
  • constructor:组件还没挂载到DOM,发请求可能导致数据回来时组件还没“出生”,报错。
  • render:不能有副作用(发请求会阻塞渲染,且可能触发多次渲染导致重复发请求)。
  • componentDidMount:组件已挂载,DOM可用,发请求最安全。
Q2:shouldComponentUpdateReact.memo有什么区别?
  • shouldComponentUpdate:类组件用,手动控制是否更新(返回true/false)。
  • React.memo:函数组件用,自动浅比较props,没变就不更新(相当于函数版的shouldComponentUpdate)。
Q3:componentWillUnmount里不清理定时器会怎么样?

定时器会一直运行,即使组件卸载了,导致内存泄漏(页面卡顿,甚至崩溃)。就像人死了,心脏还在跳,吓人!


最后口诀

挂载:constructor → render → DidMount
更新:DerivedState → ShouldUpdate → render → Snapshot → DidUpdate
卸载:WillUnmount(清理要记牢)
函数组件用useEffect,依赖数组别忘掉!

用生命周期,就像带孩子,该出生时出生,该成长时成长,该死亡时写好遗书——React组件的一生,清清楚楚!

  • 使用简洁的大白话同时结合案例,讲解React的生命周期有哪些? React 废弃了哪些生命周期?为什么?React 16.X 中 props 改变后在哪个生命周期中处理React 性能优化在哪个生命周期?它优化的原理是什么?state 和 props 触发更新的生命周期分别有什么区别?React中发起网络请求应该在哪个生命周期中进行?为什么?React 16中新生命周期有哪些

用“人的一生”比喻React生命周期,结合案例和原理,帮你彻底搞懂!


一、React生命周期有哪些?(类比人的一生)

1. 挂载阶段(Mounting)——组件“出生”
钩子函数大白话比喻作用/案例
constructor怀孕期(初始化基因)初始化state、绑定方法:
this.state = { name: "小明" }
render出生(第一次亮相)渲染JSX:
return <div>{this.state.name}</div>
componentDidMount办出生证(正式工作)发请求、操作DOM、启动定时器:
fetch("/api/user")
2. 更新阶段(Updating)——组件“成长”
钩子函数大白话比喻作用/案例
getDerivedStateFromProps检查新衣服(props变时更新state)少用!props同步state:
if (props.name !== state.name) return { name: props.name }
shouldComponentUpdate决定是否长个子(优化性能)控制是否更新:
return nextProps.age !== this.state.age
render长个子(重新渲染)同上,必须实现
getSnapshotBeforeUpdate成长前拍照(记录DOM信息)保存滚动位置:
return this.list.scrollHeight
componentDidUpdate成长后调整(处理副作用)操作DOM/发请求(加条件判断):
if (prevProps.id !== this.props.id) fetchData()
3. 卸载阶段(Unmounting)——组件“死亡”
钩子函数大白话比喻作用/案例
componentWillUnmount写遗书(清理工作)清除定时器/事件监听:
clearInterval(this.timer)

二、废弃了哪些生命周期?为什么?

废弃的3个生命周期(标记为UNSAFE_)
废弃钩子替代方案废弃原因(大白话)
componentWillMountcomponentDidMount出生前(挂载前)做事太早:
- 服务端渲染时可能执行两次
- 发请求可能阻塞渲染
componentWillReceivePropsgetDerivedStateFromProps收到新props时做事太随意:
- 容易导致无限循环setState
- 与异步渲染冲突
componentWillUpdategetSnapshotBeforeUpdate + componentDidUpdate更新前做事太危险:
- DOM还没更新就操作,可能出错
- 与新异步渲染机制冲突
核心原因:React 16引入“异步渲染”
  • 旧问题:同步渲染中,componentWill*钩子可能导致渲染中断(比如在componentWillUpdate里setState会卡死)。
  • 新方案:用getDerivedStateFromProps(静态)和getSnapshotBeforeUpdate(DOM更新前)替代,确保渲染流程可控。

三、React 16.X 中 props 改变后在哪个生命周期处理?

答案:getDerivedStateFromPropscomponentDidUpdate
场景推荐生命周期案例
props变 → 更新stategetDerivedStateFromProps父组件传新name,子组件同步更新:
static getDerivedStateFromProps(props, state) {<br/> if (props.name !== state.name) {<br/> return { name: props.name };<br/> }<br/> return null;<br/>}
props变 → 副作用操作componentDidUpdateprops变后发请求:
componentDidUpdate(prevProps) {<br/> if (prevProps.id !== this.props.id) {<br/> fetch(`/api/user/${this.props.id}`);<br/> }<br/>}

注意:优先用componentDidUpdategetDerivedStateFromProps是“静态方法”(不能访问this),容易导致逻辑混乱,官方建议少用。


四、React性能优化在哪个生命周期?原理是什么?

优化生命周期:shouldComponentUpdate
  • 作用:决定组件是否需要更新(返回true更新,false不更新)。
  • 原理避免不必要的渲染(比如props没变时跳过render和DOM操作)。
案例:只有age变化时才更新
class User extends React.Component {
  shouldComponentUpdate(nextProps, nextState) {
    // 只有age变化才更新(name变化不更新)
    return nextState.age !== this.state.age;
  }
  render() {
    console.log("组件渲染了!"); // 只有age变时才打印
    return <div>{this.state.name} - {this.state.age}</div>;
  }
}
进阶优化方案
方案适用组件原理
React.PureComponent类组件自动浅比较props和state(相当于内置shouldComponentUpdate
React.memo函数组件自动浅比较props(相当于函数版PureComponent

浅比较原理:只比较第一层数据(如props.astate.b),不比较嵌套对象(如props.user.name)。复杂对象需手动优化。


五、state 和 props 触发更新的生命周期区别

触发条件生命周期流程关键区别
setStategetDerivedStateFromPropsshouldComponentUpdaterendergetSnapshotBeforeUpdatecomponentDidUpdate可控性强:可在shouldComponentUpdate中拦截更新,优化性能。
props变同上(但getDerivedStateFromProps必触发)被动更新:子组件无法阻止props更新(除非用shouldComponentUpdate)。
案例对比
// 父组件
class Parent extends React.Component {
  render() {
    return <Child name="小明" />; // 传props给子组件
  }
}

// 子组件
class Child extends React.Component {
  state = { age: 10 };

  // props变时触发
  static getDerivedStateFromProps(props, state) {
    console.log("props变了,但我不一定更新state");
    return null;
  }

  // setState时触发
  handleClick = () => {
    this.setState({ age: 11 }); // 触发state更新
  };

  // 优化:只有state.age变才更新
  shouldComponentUpdate(nextProps, nextState) {
    return nextState.age !== this.state.age;
  }

  render() {
    return <button onClick={this.handleClick}>点我更新age</button>;
  }
}

六、网络请求应该在哪个生命周期?为什么?

唯一答案:componentDidMount
为什么?
  1. 安全:组件已挂载到DOM,操作DOM或更新state不会报错。
  2. 只执行一次:避免重复请求(如在rendercomponentWillUpdate中请求,每次渲染都发)。
  3. 兼容服务端渲染componentWillMount在服务端会执行,但服务端没有window/fetch,会报错。
错误示范
// ❌ 错误:在constructor中请求(组件未挂载)
constructor(props) {
  super(props);
  fetch("/api/user"); // 可能导致数据回来时组件还没“出生”
}

// ❌ 错误:在render中请求(每次渲染都发)
render() {
  fetch("/api/user"); // 无限循环!
  return <div>...</div>;
}
正确案例
class UserList extends React.Component {
  state = { users: [] };

  componentDidMount() {
    // ✅ 正确:挂载后发请求
    fetch("/api/users")
      .then(res => res.json())
      .then(users => this.setState({ users }));
  }

  render() {
    return (
      <ul>
        {this.state.users.map(user => <li key={user.id}>{user.name}</li>)}
      </ul>
    );
  }
}

七、React 16新生命周期有哪些?

新增的2个生命周期
新钩子作用案例
getDerivedStateFromPropsrender前调用,根据props返回新state(静态方法)props变时同步state:
static getDerivedStateFromProps(props) {<br/> return { name: props.name };<br/>}
getSnapshotBeforeUpdate在DOM更新前调用,返回值传给componentDidUpdate(保存DOM信息)保存滚动位置:
getSnapshotBeforeUpdate() {<br/> return this.list.scrollHeight;<br/>}
废弃的3个旧生命周期(见第二部分)
  • componentWillMount → 替代:componentDidMount
  • componentWillReceiveProps → 替代:getDerivedStateFromProps + componentDidUpdate
  • componentWillUpdate → 替代:getSnapshotBeforeUpdate + componentDidUpdate

八、总结:生命周期核心口诀

挂载:constructor → render → DidMount
更新:DerivedState → ShouldUpdate → render → Snapshot → DidUpdate
卸载:WillUnmount(清理要记牢)
废弃:WillMount/WillReceiveProps/WillUpdate(异步渲染惹的祸)
请求:DidMount(安全又可靠)
优化:ShouldUpdate/PureComponent/memo(浅比较防浪费)

用生命周期就像带孩子:该出生时出生,该成长时成长,该死亡时写好遗书——React组件的一生,清清楚楚!