前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
  • 面经汇总
    • 饿了么(25 题)
      • 1. 小程序里面开页面最多多少
      • 2. React 父子组件之间如何传值
      • 3. Emit事件怎么发,需要引入什么
      • 4. 介绍下React高阶组件,和普通组件有什么区别
      • 5. 一个对象数组,每个子对象包含一个id和name,React如何渲染出全部的 name
      • 6. 在哪个生命周期里写
      • 7. 其中有几个name不存在,通过异步接口获取,如何做
      • 8. 渲染的时候key给什么值,可以使用index吗,用id好还是index好 a. Webpack如何配sass,需要配哪些loader b. 配CSS需要哪些loader
      • 9. 如何配置把JavaScript、CSS、HTML单独打包成一个文件
      • 10. div垂直水平居中(flex、绝对定位)
      • 11. 两个元素块,一左一右,中间相距10像素
      • 12. 上下固定,中间滚动布局如何实现
      • 13. [1, 2, 3, 4, 5]变成[1, 2, 3, a, b, 5]
      • 14. 取数组的最大值(ES5、ES6)
      • 15. apply和call的区别
      • 16. ES5和ES6有什么区别
      • 17. some、every、find、filter、map、forEach有什么区别
      • 18. 上述数组随机取数,每次返回的值都不一样
      • 19. 如何找0-5的随机数,95-99呢
      • 20. 页面上有1万个button如何绑定事件
      • 21. 如何判断是button
      • 22. 页面上生成一万个button,并且绑定事件,如何做(JavaScript原生操作DOM)
      • 23. 循环绑定时的index是多少,为什么,怎么解决
      • 24. 页面上有一个input,还有一个p标签,改变input后p标签就跟着变化, 如何处理
      • 25. 监听input的哪个事件,在什么时候触发
    • 百分点(14 题)
    • 滴滴(55 题)
    • 今日头条(80 题)
    • 阿里(76 题)
    • 携程(8 题)
    • 有赞(49 题)
    • 宝宝树(15 题)
    • 兑吧(33 题)
    • 微医(40 题)
    • 网易(39 题)
    • 寺库(20 题)
    • 海康威视(15 题)
    • 酷家乐(51 题)
    • 挖财(24 题)
    • 蘑菇街(15 题)
    • 喜马拉雅(30 题)
    • 沪江(23 题)
    • 海风教育(14 题)
    • 深圳某海外电商(13 题)
    • 某 AI 工具公司(1 题)
    • 某矩阵投放公司(4 题)
    • 某交易所(8 题)
    • 某跨境电商品牌公司(3 题)
    • 广州某游戏公司(1 题)
    • 深圳某科技公司(19 题)
    • 深圳某交易所(3 题)
    • 某互动游戏公司(16 题)
    • 51信用卡(4 题)
    • 腾讯(119 题)
    • 字节跳动三轮技术+一轮HR(5 题)
    • 深圳某做海外加速器公司(6 题)
    • 深圳某电商公司(2 题)
    • 深圳某云产品公司(19 题)
    • 深圳某房地产公司(4 题)
    • 深圳某海外直播公司(50 题)
    • 深圳某国内直播公司(18 题)
    • 明源云(32 题)
    • CVTE(80 题)
    • 中信银行信用卡中心(13 题)
    • 招创礼记(18 题)
    • 铱云科技(21 题)
    • 平行空间(20 题)
    • 科创先锋(13 题)
    • 兔展(38 题)
    • 顺德美云智数(17 题)
    • 拼多多(37 题)
    • 金山软件(45 题)
    • 富途证券(22 题)
    • 欢聚时代(22 题)
    • SHEIN(12 题)
    • 虾皮(70 题)
    • 百度(41 题)
    • 快手(13 题)
    • YY(18 题)
    • 编程猫(30 题)
    • 深圳开思(11 题)
    • 美团(1 题)
    • 综合面试题(20 题)

# 饿了么(25 题)

# 1. 小程序里面开页面最多多少

30 秒速记

  • 微信小程序页面栈最多 10 层,满了再 navigateTo 会走 fail
  • navigateTo 入栈 / redirectTo 替换当前页 / reLaunch 清空重建 / navigateBack 出栈
  • tabBar 页用 switchTab 切换,也可用 reLaunch 重建;navigateTo 和 redirectTo 都打不开
  • 详情页套详情页(推荐商品、相关文章)是最容易撞上限的场景,该替换就替换
  • 分享卡片冷启动进来时栈里只有一页,返回按钮要自己兜底回首页

微信小程序的页面栈最多 10 层,超过以后 navigateTo 直接失败。 可以把页面栈想成浏览器的历史记录,只不过平台为它设了层数上限,页面及其状态也会占用资源;具体渲染方式取决于 WebView 或 Skyline 等渲染引擎。真碰到上限一般不是数字的问题,而是导航设计有问题,比如商品详情里点推荐商品一路往下点。我会封装一个跳转方法,用 getCurrentPages().length 判断深度,接近上限时改成 redirectTo 替换当前页;回首页这类动作用 reLaunch 或 switchTab,不往栈里堆。

原理拆开看

微信小程序同时打开的页面栈通常最多 10 层,超过后不能继续通过 navigateTo 入栈;返回用 navigateBack,切换 tab 用 switchTab。这个值属于平台限制,面试时还应补一句:开发要以当前官方文档和实际基础库为准,页面层级过深本身也说明导航设计需要收敛。 小程序页面栈可以理解为一段受平台管理的导航历史:navigateTo 新增普通页,navigateBack 回退,redirectTo 替换当前页,reLaunch 重建整条栈,switchTab 切换到 tabBar 页面。具体层数是平台能力,不应把“能开多少层”当作导航设计目标。

最小实现

function openDetail(id) {
  const pages = getCurrentPages()
  if (pages.length >= 9) {
    wx.redirectTo({ url: `/pages/detail/index?id=${id}` })
    return
  }
  wx.navigateTo({ url: `/pages/detail/index?id=${id}` })
}

// tabBar 页面必须使用 switchTab
function goHome() {
  wx.switchTab({ url: '/pages/home/index' })
}
@前端进阶之旅: 代码已经复制到剪贴板

普通情况下用 navigateTo 保留返回路径;当页面栈已经很深时,示例改用 redirectTo 替换当前页,避免继续入栈。tabBar 页面不能用普通入栈 API,必须 switchTab。生产还应根据流程语义决定是否替换,不能只按数字硬切。

边界与排查

开发时先区分普通页和 tabBar 页,再根据用户是否需要返回选择入栈、替换或重建。可以用 getCurrentPages 查看当前栈深度,在接近上限前收敛跳转;订单、支付等流程还要避免重复入栈并处理从分享卡片直接进入中间页的情况。 基础库版本、tabBar 限制、分享冷启动和业务流程过深,都会影响导航行为。

面试官追问

追问 1商品详情里点推荐商品,连点十次就打不开了,怎么改?
参考回答

详情跳详情用 redirectTo 替换当前页,或者干脆在当前页改参数重新拉数据,不再入栈。用户点返回会回到列表页,这个行为大多数电商是能接受的。

追问 2用户从分享卡片直接进了订单详情,左上角返回没反应,怎么处理?
参考回答

这时 getCurrentPages().length === 1,没有可返回的页面。页面里判断一下,栈深为 1 时显示「回首页」按钮;首页是 tabBar 页就调 wx.switchTab,否则用 wx.reLaunch 打开首页。

追问 3同事用 navigateTo 跳 tabBar 页面,模拟器偶尔行真机不行,为什么?
参考回答

navigateTo 和 redirectTo 不支持打开 tabBar 页,切换标签页应使用 switchTab;清空页面后重新打开 tabBar 页也可以用 reLaunch。而且 switchTab 的 url 不能带 ?id=1 这种参数,要传数据得走全局状态或缓存。

追问 4reLaunch 和 switchTab 都能回首页,有什么区别?
参考回答

reLaunch 会关掉所有页面再打开目标页,新页面会走 onLoad;switchTab 关闭非 tabBar 页,切到已加载的标签页通常只触发 onShow,首次打开仍会触发 onLoad。支付后刷新数据可在 onShow 检查失效标记后请求,不必为刷新而重建全部页面。

# 2. React 父子组件之间如何传值

30 秒速记

  • 父传子:props;子传父:父组件传一个回调下去,子组件调用它
  • 兄弟组件:状态提升到最近的共同父组件,再分别往下传
  • 跨很多层:Context;跨页面、更新频繁的共享状态再上 zustand / Redux
  • 父组件要调子组件方法:ref + useImperativeHandle,少用,优先用 props 驱动
  • 单向数据流:子组件不改 props,只「通知」父组件改

React 是单向数据流:数据通过 props 往下传,子组件想改数据就调用父组件传下来的回调。 打个比方,父组件是数据的主人,子组件只能借来看,想改得跟主人说一声。比如父组件写 <Counter value={count} onChange={setCount} />,子组件里点按钮调 onChange(value + 1)。两个兄弟要共享数据,就把状态挪到它们共同的父组件里;层级太深、一路透传 props 很烦的时候再用 Context。我一般不会一开始就把状态丢进全局 store,放得越高,牵连重渲染的组件越多。

原理拆开看

父传子用 props,子通知父由父组件下发回调;共享状态要提升到最近公共父级。层级很深再考虑 Context,跨页面且更新复杂才引入外部状态库,避免所有状态一上来就全局化。 React 题要先分清 render 阶段和 commit 阶段:前者计算下一棵界面树,可以被打断或重做;后者才真正修改宿主环境并执行布局相关副作用。组件的输入是 props、state、context 与外部订阅,优化的前提是先确认哪一个输入真的导致了重复工作。

最小实现

function Parent() {
  const [count, setCount] = React.useState(0)
  return <Counter value={count} onChange={setCount} />
}

function Counter({ value, onChange }) {
  return <button onClick={() => onChange(value + 1)}>{value}</button>
}
@前端进阶之旅: 代码已经复制到剪贴板

状态由真正拥有它的父组件保存,通过 props 向下传;子组件不直接改父状态,而是调用回调表达“希望发生什么”。兄弟组件共享时把状态提升到最近公共父级,跨很多层再考虑 Context 或外部 store。

边界与排查

排查时先用 React DevTools Profiler 录制一次真实交互,再看是哪一层组件重复 render、提交耗时落在哪里。不要一上来堆 memo;引用每次都重建、状态放得太高或 effect 依赖不完整,都会让表面优化失效。涉及生命周期和事件时还要主动说明 React 16、17、18+ 的版本边界。 版本、并发渲染、组件卸载和 Strict Mode 重复检查,是这类题最常被追问的边界。

面试官追问

追问 1子组件里直接 props.list.push(item),页面为什么没更新?
参考回答

props 是只读的,push 改的是同一个数组引用,父组件的 state 没有经过 setState,React 根本不知道变了。要让子组件调 onAdd(item),父组件里 setList(prev => [...prev, item])。

追问 2表单弹窗里,父组件想点按钮让子组件重置表单,怎么写?
参考回答

子组件用 forwardRef + useImperativeHandle(ref, () => ({ reset })) 暴露一个方法,父组件调 ref.current.reset()。React 19 里函数组件可以直接把 ref 当普通 prop 收,不用再包 forwardRef。不过能用 key 换一个新实例来重置的,我更倾向 key。

追问 3props 透传了四五层,中间组件根本不用这个值,怎么办?
参考回答

先考虑组件组合:把子组件当 children 直接传下去,中间层就不用转手了。真是全局性的东西(主题、当前用户)再用 Context。

追问 4用了 Context 之后,改一个值整个页面都在重渲染,怎么优化?
参考回答

所有用到这个 Context 的组件都会在 value 变化时重渲染。按更新频率拆成多个 Context,value 用 useMemo 包住,避免父组件每次渲染都生成新对象;更新特别频繁的状态换成 zustand 这种能按字段订阅的库。

# 3. Emit事件怎么发,需要引入什么

30 秒速记

基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
  • 面经汇总
    • 饿了么(25 题)
      • 1. 小程序里面开页面最多多少
      • 2. React 父子组件之间如何传值
      • 3. Emit事件怎么发,需要引入什么
      • 4. 介绍下React高阶组件,和普通组件有什么区别
      • 5. 一个对象数组,每个子对象包含一个id和name,React如何渲染出全部的 name
      • 6. 在哪个生命周期里写
      • 7. 其中有几个name不存在,通过异步接口获取,如何做
      • 8. 渲染的时候key给什么值,可以使用index吗,用id好还是index好 a. Webpack如何配sass,需要配哪些loader b. 配CSS需要哪些loader
      • 9. 如何配置把JavaScript、CSS、HTML单独打包成一个文件
      • 10. div垂直水平居中(flex、绝对定位)
      • 11. 两个元素块,一左一右,中间相距10像素
      • 12. 上下固定,中间滚动布局如何实现
      • 13. [1, 2, 3, 4, 5]变成[1, 2, 3, a, b, 5]
      • 14. 取数组的最大值(ES5、ES6)
      • 15. apply和call的区别
      • 16. ES5和ES6有什么区别
      • 17. some、every、find、filter、map、forEach有什么区别
      • 18. 上述数组随机取数,每次返回的值都不一样
      • 19. 如何找0-5的随机数,95-99呢
      • 20. 页面上有1万个button如何绑定事件
      • 21. 如何判断是button
      • 22. 页面上生成一万个button,并且绑定事件,如何做(JavaScript原生操作DOM)
      • 23. 循环绑定时的index是多少,为什么,怎么解决
      • 24. 页面上有一个input,还有一个p标签,改变input后p标签就跟着变化, 如何处理
      • 25. 监听input的哪个事件,在什么时候触发
    • 百分点(14 题)
    • 滴滴(55 题)
    • 今日头条(80 题)
    • 阿里(76 题)
    • 携程(8 题)
    • 有赞(49 题)
    • 宝宝树(15 题)
    • 兑吧(33 题)
    • 微医(40 题)
    • 网易(39 题)
    • 寺库(20 题)
    • 海康威视(15 题)
    • 酷家乐(51 题)
    • 挖财(24 题)
    • 蘑菇街(15 题)
    • 喜马拉雅(30 题)
    • 沪江(23 题)
    • 海风教育(14 题)
    • 深圳某海外电商(13 题)
    • 某 AI 工具公司(1 题)
    • 某矩阵投放公司(4 题)
    • 某交易所(8 题)
    • 某跨境电商品牌公司(3 题)
    • 广州某游戏公司(1 题)
    • 深圳某科技公司(19 题)
    • 深圳某交易所(3 题)
    • 某互动游戏公司(16 题)
    • 51信用卡(4 题)
    • 腾讯(119 题)
    • 字节跳动三轮技术+一轮HR(5 题)
    • 深圳某做海外加速器公司(6 题)
    • 深圳某电商公司(2 题)
    • 深圳某云产品公司(19 题)
    • 深圳某房地产公司(4 题)
    • 深圳某海外直播公司(50 题)
    • 深圳某国内直播公司(18 题)
    • 明源云(32 题)
    • CVTE(80 题)
    • 中信银行信用卡中心(13 题)
    • 招创礼记(18 题)
    • 铱云科技(21 题)
    • 平行空间(20 题)
    • 科创先锋(13 题)
    • 兔展(38 题)
    • 顺德美云智数(17 题)
    • 拼多多(37 题)
    • 金山软件(45 题)
    • 富途证券(22 题)
    • 欢聚时代(22 题)
    • SHEIN(12 题)
    • 虾皮(70 题)
    • 百度(41 题)
    • 快手(13 题)
    • YY(18 题)
    • 编程猫(30 题)
    • 深圳开思(11 题)
    • 美团(1 题)
    • 综合面试题(20 题)
完整面试题地址:
作者:程序员poetry
扫码关注作者公众号:「前端进阶之旅」 每天分享技术干货
前端进阶之旅公众号二维码

前端高频面试题-前端面经汇总

# 饿了么(25 题)

# 1. 小程序里面开页面最多多少

30 秒速记

  • 微信小程序页面栈最多 10 层,满了再 navigateTo 会走 fail
  • navigateTo 入栈 / redirectTo 替换当前页 / reLaunch 清空重建 / navigateBack 出栈
  • tabBar 页用 switchTab 切换,也可用 reLaunch 重建;navigateTo 和 redirectTo 都打不开
  • 详情页套详情页(推荐商品、相关文章)是最容易撞上限的场景,该替换就替换
  • 分享卡片冷启动进来时栈里只有一页,返回按钮要自己兜底回首页

微信小程序的页面栈最多 10 层,超过以后 navigateTo 直接失败。 可以把页面栈想成浏览器的历史记录,只不过平台为它设了层数上限,页面及其状态也会占用资源;具体渲染方式取决于 WebView 或 Skyline 等渲染引擎。真碰到上限一般不是数字的问题,而是导航设计有问题,比如商品详情里点推荐商品一路往下点。我会封装一个跳转方法,用 getCurrentPages().length 判断深度,接近上限时改成 redirectTo 替换当前页;回首页这类动作用 reLaunch 或 switchTab,不往栈里堆。

原理拆开看

微信小程序同时打开的页面栈通常最多 10 层,超过后不能继续通过 navigateTo 入栈;返回用 navigateBack,切换 tab 用 switchTab。这个值属于平台限制,面试时还应补一句:开发要以当前官方文档和实际基础库为准,页面层级过深本身也说明导航设计需要收敛。 小程序页面栈可以理解为一段受平台管理的导航历史:navigateTo 新增普通页,navigateBack 回退,redirectTo 替换当前页,reLaunch 重建整条栈,switchTab 切换到 tabBar 页面。具体层数是平台能力,不应把“能开多少层”当作导航设计目标。

最小实现

function openDetail(id) {
  const pages = getCurrentPages()
  if (pages.length >= 9) {
    wx.redirectTo({ url: `/pages/detail/index?id=${id}` })
    return
  }
  wx.navigateTo({ url: `/pages/detail/index?id=${id}` })
}

// tabBar 页面必须使用 switchTab
function goHome() {
  wx.switchTab({ url: '/pages/home/index' })
}
@前端进阶之旅: 代码已经复制到剪贴板

普通情况下用 navigateTo 保留返回路径;当页面栈已经很深时,示例改用 redirectTo 替换当前页,避免继续入栈。tabBar 页面不能用普通入栈 API,必须 switchTab。生产还应根据流程语义决定是否替换,不能只按数字硬切。

边界与排查

开发时先区分普通页和 tabBar 页,再根据用户是否需要返回选择入栈、替换或重建。可以用 getCurrentPages 查看当前栈深度,在接近上限前收敛跳转;订单、支付等流程还要避免重复入栈并处理从分享卡片直接进入中间页的情况。 基础库版本、tabBar 限制、分享冷启动和业务流程过深,都会影响导航行为。

面试官追问

追问 1商品详情里点推荐商品,连点十次就打不开了,怎么改?
参考回答

详情跳详情用 redirectTo 替换当前页,或者干脆在当前页改参数重新拉数据,不再入栈。用户点返回会回到列表页,这个行为大多数电商是能接受的。

追问 2用户从分享卡片直接进了订单详情,左上角返回没反应,怎么处理?
参考回答

这时 getCurrentPages().length === 1,没有可返回的页面。页面里判断一下,栈深为 1 时显示「回首页」按钮;首页是 tabBar 页就调 wx.switchTab,否则用 wx.reLaunch 打开首页。

追问 3同事用 navigateTo 跳 tabBar 页面,模拟器偶尔行真机不行,为什么?
参考回答

navigateTo 和 redirectTo 不支持打开 tabBar 页,切换标签页应使用 switchTab;清空页面后重新打开 tabBar 页也可以用 reLaunch。而且 switchTab 的 url 不能带 ?id=1 这种参数,要传数据得走全局状态或缓存。

追问 4reLaunch 和 switchTab 都能回首页,有什么区别?
参考回答

reLaunch 会关掉所有页面再打开目标页,新页面会走 onLoad;switchTab 关闭非 tabBar 页,切到已加载的标签页通常只触发 onShow,首次打开仍会触发 onLoad。支付后刷新数据可在 onShow 检查失效标记后请求,不必为刷新而重建全部页面。

# 2. React 父子组件之间如何传值

30 秒速记

  • 父传子:props;子传父:父组件传一个回调下去,子组件调用它
  • 兄弟组件:状态提升到最近的共同父组件,再分别往下传
  • 跨很多层:Context;跨页面、更新频繁的共享状态再上 zustand / Redux
  • 父组件要调子组件方法:ref + useImperativeHandle,少用,优先用 props 驱动
  • 单向数据流:子组件不改 props,只「通知」父组件改

React 是单向数据流:数据通过 props 往下传,子组件想改数据就调用父组件传下来的回调。 打个比方,父组件是数据的主人,子组件只能借来看,想改得跟主人说一声。比如父组件写 <Counter value={count} onChange={setCount} />,子组件里点按钮调 onChange(value + 1)。两个兄弟要共享数据,就把状态挪到它们共同的父组件里;层级太深、一路透传 props 很烦的时候再用 Context。我一般不会一开始就把状态丢进全局 store,放得越高,牵连重渲染的组件越多。

原理拆开看

父传子用 props,子通知父由父组件下发回调;共享状态要提升到最近公共父级。层级很深再考虑 Context,跨页面且更新复杂才引入外部状态库,避免所有状态一上来就全局化。 React 题要先分清 render 阶段和 commit 阶段:前者计算下一棵界面树,可以被打断或重做;后者才真正修改宿主环境并执行布局相关副作用。组件的输入是 props、state、context 与外部订阅,优化的前提是先确认哪一个输入真的导致了重复工作。

最小实现

function Parent() {
  const [count, setCount] = React.useState(0)
  return <Counter value={count} onChange={setCount} />
}

function Counter({ value, onChange }) {
  return <button onClick={() => onChange(value + 1)}>{value}</button>
}
@前端进阶之旅: 代码已经复制到剪贴板

状态由真正拥有它的父组件保存,通过 props 向下传;子组件不直接改父状态,而是调用回调表达“希望发生什么”。兄弟组件共享时把状态提升到最近公共父级,跨很多层再考虑 Context 或外部 store。

边界与排查

排查时先用 React DevTools Profiler 录制一次真实交互,再看是哪一层组件重复 render、提交耗时落在哪里。不要一上来堆 memo;引用每次都重建、状态放得太高或 effect 依赖不完整,都会让表面优化失效。涉及生命周期和事件时还要主动说明 React 16、17、18+ 的版本边界。 版本、并发渲染、组件卸载和 Strict Mode 重复检查,是这类题最常被追问的边界。

面试官追问

追问 1子组件里直接 props.list.push(item),页面为什么没更新?
参考回答

props 是只读的,push 改的是同一个数组引用,父组件的 state 没有经过 setState,React 根本不知道变了。要让子组件调 onAdd(item),父组件里 setList(prev => [...prev, item])。

追问 2表单弹窗里,父组件想点按钮让子组件重置表单,怎么写?
参考回答

子组件用 forwardRef + useImperativeHandle(ref, () => ({ reset })) 暴露一个方法,父组件调 ref.current.reset()。React 19 里函数组件可以直接把 ref 当普通 prop 收,不用再包 forwardRef。不过能用 key 换一个新实例来重置的,我更倾向 key。

追问 3props 透传了四五层,中间组件根本不用这个值,怎么办?
参考回答

先考虑组件组合:把子组件当 children 直接传下去,中间层就不用转手了。真是全局性的东西(主题、当前用户)再用 Context。

追问 4用了 Context 之后,改一个值整个页面都在重渲染,怎么优化?
参考回答

所有用到这个 Context 的组件都会在 value 变化时重渲染。按更新频率拆成多个 Context,value 用 useMemo 包住,避免父组件每次渲染都生成新对象;更新特别频繁的状态换成 zustand 这种能按字段订阅的库。

# 3. Emit事件怎么发,需要引入什么

30 秒速记

  • emit 就是「按事件名发消息」:一边 on 订阅,一边 emit 触发,用完 off
  • Vue 2:子组件 this.$emit('change', val),不用引入任何东西
  • Vue 3:<script setup> 里用 defineEmits,它是编译宏,不用 import;非 setup 语法从 setup(props, { emit }) 拿
  • Node.js:require('events') 拿 EventEmitter;浏览器原生:EventTarget + CustomEvent
  • Vue 3 删了 $on / $off,全局事件总线要换成 mitt 这类小库

emit 本质上是发布订阅:先按事件名注册回调,触发时把数据传给所有回调。 要引入什么看在哪个环境。Vue 组件里最常见,Vue 2 直接 this.$emit,Vue 3 的 <script setup> 里写 const emit = defineEmits(['change']),这是编译器宏,不用 import;Node.js 里从 events 模块拿 EventEmitter;浏览器原生可以用 new CustomEvent('change', { detail }) 加 dispatchEvent。React 没有 emit 这一套,子传父就是调用父组件传下来的回调。不管哪种,订阅了就要记得取消,不然组件反复挂载会越挂越多。

const { EventEmitter } = require('events')
const bus = new EventEmitter()
bus.on('paid', order => console.log('收到', order.id))
bus.emit('paid', { id: 'A1001' }) // 收到 A1001
@前端进阶之旅: 代码已经复制到剪贴板
fe