背景 开始业务交给外包同学迭代,导致业务耦合,我在接手开发一段时间后发现原有UMI项目存在巨石应用风险,功能模块耦合度高,导致部署冲突、代码合并复杂(如B/C项目无法独立迭代) 。为实现技术栈升级与业务解耦,采用qiankun微前端框架,通过主应用+子应用(React)架构,逐步替换老功能并引入新技术栈 操作,技术难点 ● 怎么把业务拆出来 ● 升级方案的调研
- 主应用改造 ○ 注册微应用路由,使用loadMicroApp动态加载子应用,通过URL参数控制组件渲染: javascript
loadMicroApp({
name: 'react-app',
entry: '//localhost:3000',
container: '#subapp-container',
props: { component: 'data-market' }
});
○ 配置全局通信桥接对象globalBridge,实现主子应用API与状态共享7。
2. 子应用适配
○ 曝露bootstrap/mount/unmount生命周期钩子,支持动态组件加载:
javascript
const ComponentRouter = ({ component }) => (
{component === 'analytics' ? : ...}
);
○ Webpack配置libraryTarget: 'umd',解决公共路径冲突问题6。
3. 路由驱动管理
○ 主应用通过RouteManager监听URL变化,按需加载子应用组件:
javascript
handleRouteChange() {
if (routeInfo.needMicroApp) this.loadMicroApp(routeInfo.component);
}
技术难点
- 动态路由与组件映射 ○ 主应用需解析URL参数并传递给子应用,子应用内部需根据参数渲染不同组件,需确保路由规则与组件逻辑强绑定6。
- 样式与缓存隔离 ○ 避免iframe的独立window问题,通过qiankun默认的沙箱机制实现样式隔离,但需手动处理共享缓存(如window.legacyState)2。
- 通信与状态同步 ○ 主子应用通过globalBridge传递API与状态,需设计统一接口(如getUserInfo/callAPI),并处理跨域与异步调用7。
- 公共路径配置 ○ 子应用Webpack需配置publicPath为动态值,避免静态资源加载失败: javascript
output: {
library: ${packageName}-[name],
libraryTarget: 'umd',
chunkLoadingGlobal: webpackJsonp_${packageName}
};
结果
- 架构解耦 ○ 主应用(jQuery)与子应用(React)独立开发、部署,老功能无需重写即可共存。
- 部署效率提升 ○ 新功能通过子应用独立打包,部署时间从30分钟缩短至5分钟。
- 资源优化 ○ 单个子应用承载多个组件,减少微应用数量,降低维护成本。
- 用户体验 ○ 通过动态加载与SPA切换,页面无刷新跳转,支持弹窗级微应用嵌入。
- 技术兼容性 ○ 老项目API、组件可被React子应用复用,实现渐进式升级。
背景 学习强国是一个学习类app,拥有几百个频道,用户通过行为获取积分,我们需要将app上面的基本功能迁移到小程序上面 我们当时遇到的困难是 项目外:时间紧,任务重 项目内: ● 在短时间内完成基础功能的同时确保性能不比APP差 ● 怎么针对用户的行为封装好积分SDK,以最低的成本接入并确保用户的行为正确上报拿到积分。 优化策略 针对首次加载(LCP)优化:
- 分包加载:将非首屏频道代码拆分为独立分包,首次加载仅加载主包(主包 ≤ 2MB),显著减少初始资源体积。
- 数据预加载:在 Tab 初始化阶段预加载首屏频道数据,避免渲染阻塞。例如,在 onLoad 生命周期通过 my.request 提前获取信息流内容。
- 静态资源优化:
○ 字体/图片使用 CDN 加速传输,并结合格式压缩(JPEG 用于照片,PNG 用于图标)。
○ 图片启用懒加载(
),优先加载首屏内容。 如何测量LCP: opendocs.alipay.com/mini/06ikay… 点开支付宝开发者工具-性能分析,扫描进去小程序首页,结束后会出现一个类似与日志的图标,然后通过时间轨道来分析 还可以降级方案(兼容旧版本) 如果 my.getPerformance 不可用,可通过手动标记关键元素渲染完成时间来模拟 LCP: javascript
Page({ onLoad() { this.pageStartTime = Date.now(); },
onImageLoad() {
// 假设首图是 LCP 元素
const lcpTime = Date.now() - this.pageStartTime;
console.log('模拟 LCP:', lcpTime);
my.reportCustomPerformance('lcp', lcpTime);
this.setData({ imageLoaded: true });
}
});
WXML:
● 针对频道/Tab 切换优化:
- 数据缓存机制: ○ 缓存频道数据至本地存储(my.setStorage),设置过期时间,切换时优先读取缓存。由于本地存储有10M限制,维护了一个滑动窗口,已经访问过的并且未过期的不进行请求直接拿到缓存数据。
- 请求合并与预加载: ○ 合并频道接口请求(如商品信息与评价整合至单接口),减少网络开销。 ○ 用户浏览当前频道时,后台预加载相邻频道数据,降低切换延迟。 最终结果 ● 首次加载(LCP):从原始 4.2s 降至 2s 以内。 ● 切换流畅性:频道/Tab 切换延迟 ≤ 300ms,缓存命中率超 85%,弱网环境下仍可流畅操作。 ● 整体性能提升:用户操作响应速度提升 70%。 埋点积分SDK
- SDK封装 a. 业务字段协商 ■ 基础字段:user_unique_id(用户ID)、page_path(页面路径)、event_time(事件时间戳) ■ 扩展字段:与业务方约定动态参数(如content_id内容ID、read_duration阅读时长) b. 分层架构实现 方法层:
export const trackEvent = (eventName, params) => { const baseData = { app_id: 0000, // 从初始化配置读取 timestamp: Date.now() }; sendRequest({ ...baseData, ...params }); // 调用接口层 }; 接口层:控制一次性请求数量,十个埋点数据请求 2. 支付宝小程序生命周期劫持实现 通过重写全局Page、和component构造函数,在onHiden生命周期自动触发上报 3. 美中不足的是audio和video最终没有做成自动上报的样子,最终是封装成了两个组件调用,在组件里面统一进行上报。
基础知识 juejin.cn/post/692799… 实例 文档:juejin.cn/post/689522… 代码地址:github.com/Clloz/webpa… 实例二: const path = require('path'); const webpack = require('webpack'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const { CleanWebpackPlugin } = require('clean-webpack-plugin'); const TerserPlugin = require('terser-webpack-plugin'); const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); const CopyPlugin = require('copy-webpack-plugin'); const WorkboxPlugin = require('workbox-webpack-plugin');
module.exports = (env, argv) => { // 判断当前是否是生产环境 const isProduction = argv.mode === 'production';
return { entry: { main: './src/index.js', // 主入口文件 vendor: ['react', 'react-dom', 'lodash'], // 外部库单独打包 }, output: { // 输出的配置 path: path.resolve(__dirname, 'dist'), // 输出文件夹路径 filename: isProduction ? 'js/[name].[contenthash].js' // 生产环境使用 contenthash 以便缓存优化 : 'js/[name].[hash].js', // 开发环境使用 hash 方便热更新 chunkFilename: isProduction ? 'js/[name].[contenthash].chunk.js' : 'js/[name].[hash].chunk.js', // 动态加载的 chunk 文件命名规则 publicPath: '/', // 静态资源的公共路径 assetModuleFilename: 'assets/[hash][ext]', // 资源文件的命名规则 }, resolve: { // 解析模块时的配置 extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'], // 支持的文件扩展名 alias: { '@': path.resolve(__dirname, 'src'), // 'src' 目录的别名 'react-native': 'react-native-web', // 支持 react-native-web }, modules: ['node_modules', path.resolve(dirname, 'src')], // 模块查找路径 }, module: { rules: [ { test: /.(js|jsx|ts|tsx)/, // 用 Babel 编译 JS, JSX, TS, TSX 文件 exclude: /node_modules/, // 排除 'node_modules' 目录 use: [ { loader: 'babel-loader', // 使用 babel-loader 来编译代码 options: { cacheDirectory: true, // 启用缓存以加快构建速度 cacheCompression: false, // 禁用缓存压缩 plugins: [ isProduction && 'transform-react-remove-prop-types', // 在生产环境去除 prop-types isProduction && 'transform-runtime', // 在生产环境启用 transform-runtime 插件 ].filter(Boolean), // 过滤掉未定义的插件 }, }, ], }, { test: /\.css/, // 处理 CSS 文件 use: [ isProduction ? MiniCssExtractPlugin.loader // 在生产环境提取 CSS : 'style-loader', // 在开发环境通过 style-loader 插入 CSS { loader: 'css-loader', options: { modules: { mode: 'local', // 启用 CSS 模块化 localIdentName: isProduction ? '[hash:base64:5]' // 生产环境下使用较短的哈希值 : '[path][name][local]', // 开发环境下使用详细的类名 }, }, }, { loader: 'postcss-loader', // 使用 PostCSS 处理 CSS options: { postcssOptions: { plugins: [ 'postcss-preset-env', // 启用未来的 CSS 特性 'autoprefixer', // 自动添加前缀 isProduction && 'cssnano', // 生产环境下使用 cssnano 进行压缩 ].filter(Boolean), }, }, }, ], }, { test: /.s[ac]ss/, // 处理 SASS/SCSS 文件 use: [ isProduction ? MiniCssExtractPlugin.loader : 'style-loader', 'css-loader', { loader: 'sass-loader', // 使用 sass-loader 编译 SCSS 文件 options: { implementation: require('sass'), }, }, ], }, { test: /\.(png|svg|jpg|gif|ico)/, // 处理图片文件 type: 'asset/resource', // 使用资源模块处理图片 generator: { filename: 'images/[hash][ext]', // 图片存放路径及命名规则 }, }, { test: /.(woff|woff2|eot|ttf|otf)/i, // 处理字体文件 type: 'asset/resource', // 使用资源模块处理字体 generator: { filename: 'fonts/[hash][ext]', // 字体存放路径及命名规则 }, }, { test: /\.svg/, // 处理 SVG 文件 use: [ { loader: '@svgr/webpack', // 将 SVG 文件转换为 React 组件 options: { svgo: true, // 启用 SVG 优化 titleProp: true, // 启用 SVG 的 title 属性 }, }, ], }, { test: /.ya?ml$/, // 处理 YAML 文件 use: 'yaml-loader', // 使用 YAML 加载器 }, ], }, plugins: [ new CleanWebpackPlugin(), // 每次构建前清理 dist 目录 new HtmlWebpackPlugin({ template: './public/index.html', // 使用自定义的 HTML 模板 inject: 'body', // 将 JS 注入到 body 标签中 favicon: './public/favicon.ico', // 设置网页图标 minify: isProduction ? { removeComments: true, // 移除注释 collapseWhitespace: true, // 压缩空格 removeRedundantAttributes: true, // 移除多余属性 useShortDoctype: true, // 使用短 doctype removeEmptyAttributes: true, // 移除空属性 removeStyleLinkTypeAttributes: true, // 移除 style/link 标签中的 type 属性 keepClosingSlash: true, // 保留自闭合标签的斜杠 minifyJS: true, // 压缩 JS minifyCSS: true, // 压缩 CSS minifyURLs: true, // 压缩 URL } : false, // 开发环境不进行 HTML 压缩 }), new MiniCssExtractPlugin({ filename: isProduction ? 'css/[name].[contenthash].css' // 生产环境使用 contenthash 生成 CSS 文件名 : 'css/[name].css', // 开发环境直接使用文件名 chunkFilename: isProduction ? 'css/[name].[contenthash].chunk.css' : 'css/[name].chunk.css', }), new webpack.DefinePlugin({ APP_ENV: JSON.stringify(process.env.NODE_ENV), // 定义应用环境(如:生产环境或开发环境) API_URL: JSON.stringify(process.env.REACT_APP_API_URL), // 从环境变量中获取 API URL }), new CopyPlugin({ patterns: [ { from: 'public/robots.txt', to: '.' }, // 将 robots.txt 复制到输出目录 { from: 'public/manifest.json', to: '.' }, // 将 manifest.json 复制到输出目录 ], }), isProduction && new WorkboxPlugin.GenerateSW({ clientsClaim: true, // 安装后立即接管客户端 skipWaiting: true, // 跳过等待阶段,立即激活 Service Worker runtimeCaching: [ { urlPattern: /^api.example.com/.*/, // 缓存以 api.example.com 开头的请求 handler: 'NetworkFirst', // 使用 NetworkFirst 策略 options: { cacheName: 'api-cache', // 设置缓存名称 expiration: { maxEntries: 50, // 最多缓存 50 个请求 maxAgeSeconds: 60 * 60 * 24 * 7, // 缓存 1 周 }, }, }, ], }), ].filter(Boolean), optimization: { splitChunks: { chunks: 'all', // 分割所有类型的代码块 cacheGroups: { vendor: { test: /[\/]node_modules[\/]/, // 匹配所有 node_modules 下的模块 name: 'vendors', // 生成一个独立的 vendors.bundle.js priority: 10, // 优先级较高 chunks: 'initial', // 仅考虑初始的 chunks }, common: { minChunks: 2, // 如果某个模块在至少 2 个文件中出现,提取成公共模块 priority: 5, // 优先级较低 name: 'common', // 公共模块的名称 }, }, }, runtimeChunk: { name: 'runtime', // 提取 runtime 代码到单独的 chunk }, minimize: isProduction, // 生产环境启用压缩 minimizer: [ new TerserPlugin({ parallel: true, // 启用多进程压缩 terserOptions: { ecma: 2020, // 使用 ECMAScript 2020 语法 compress: { drop_console: isProduction, // 生产环境下去掉 console.log drop_debugger: isProduction, // 生产环境下去掉 debugger }, output: { comments: false, // 去除代码中的注释 }, }, }), new CssMinimizerPlugin(), // 压缩 CSS 文件 ], }, devtool: isProduction ? false : 'eval-source-map', // 开发环境使用 source-map devServer: { static: { directory: path.join(__dirname, 'public'), // 静态文件目录 }, compress: true, // 启用 gzip 压缩 port: 3000, // 端口号 hot: true, // 启用热模块替换 historyApiFallback: true, // 单页面应用支持历史 API 路由 proxy: { '/api': { target: 'http://localhost:5000', // 设置代理,将 /api 请求转发到本地服务器 secure: false, // 不验证 SSL }, }, }, performance: { hints: 'warning', // 超过指定大小时显示警告 maxEntrypointSize: 512000, // 最大入口文件大小 500KB maxAssetSize: 512000, // 最大资源文件大小 500KB }, infrastructureLogging: { level: 'info', // 设置基础设施日志的级别 }, }; };
编译过程 Webpack 的运行过程可概括为以下几个核心步骤: 加载配置 Webpack 默认读取项目根目录的 webpack.config.js 文件,或通过 --config 参数指定其他配置文件(如 webpack.dev.js)
- 4。配置文件中定义了入口(entry)、输出(output)、模块规则(module)、插件(plugins)等选项。 构建依赖图 从入口文件(如 ./src/index.js)开始,递归分析所有依赖模块(包括直接和间接依赖),构建内部依赖关系图
- 7。例如,若 index.js 引入 print.js,Webpack 会将其纳入依赖图。
- 模块处理 ○ Loader 转换:根据 module.rules 中的规则,对不同类型的模块应用对应的 Loader。例如,使用 babel-loader 转译 JavaScript,css-loader 解析 CSS 文件 1。 ○ Source Map:在开发环境(mode: 'development'),通过 devtool 配置生成 Source Map,便于调试原始代码 2。
- 插件执行 插件通过生命周期钩子在构建的不同阶段执行自定义逻辑。例如: ○ HtmlWebpackPlugin 生成 HTML 文件并注入资源 5。 ○ TerserPlugin 在生产环境压缩 JavaScript 代码 4。 输出打包 根据 output 配置,将处理后的模块和资源打包成一个或多个文件(如 dist/main.bundle.js)。生产环境(mode: 'production')默认启用代码压缩和优化
- 5。 开发服务器(可选) 若使用 webpack-dev-server,会启动一个本地开发服务器,支持热更新(HMR)和实时加载。开发服务器通过内存提供打包后的文件,无需写入磁盘
- 6。 环境差异 ● 开发环境:启用 inline-source-map 和 webpack-dev-server,优化调试体验 2。 ● 生产环境:使用 TerserPlugin 压缩代码,并通过 mode: 'production' 自动优化构建结果 4。 Webpack 的运行过程高度依赖配置文件的结构和选项,开发者可通过拆分 webpack.common.js、webpack.dev.js 和 webpack.prod.js 来管理不同环境的配置 。 与vite的差异 Webpack与Vite的核心差异主要体现在架构设计、开发体验和适用场景三个方面: 一、架构设计 打包机制 Webpack采用基于捆绑的构建模式,需要预先打包整个应用的依赖树,生成Bundle文件后再启动服务
- 2。Vite基于原生ES Modules,开发阶段仅对请求的模块进行按需编译,由浏览器直接加载ESM文件,跳过传统打包流程25。 依赖处理 Webpack每次变更需重新构建整个依赖树,导致大规模项目性能下降。Vite通过预构建依赖(使用Go编写的esbuild工具)和源码按需编译实现快速启动,依赖构建速度比Webpack快10-100倍
- 25。 二、开发体验 启动速度 Webpack的冷启动时间随项目规模线性增长,Vite通过依赖预构建和原生ESM实现毫秒级启动,与项目大小无关
- 。 热更新(HMR) Webpack的热更新需要重新打包变更模块及其依赖链,大规模项目延迟明显。Vite利用浏览器缓存和精确的ESM依赖分析,实现亚秒级热更新,仅更新受影响模块
- 。 配置复杂度 Webpack需要手动配置Loader、插件和优化策略,适合深度定制。Vite提供零配置开箱体验,默认集成TypeScript、CSS预处理等现代工具链
- 。 三、生产构建 构建工具 Webpack自带成熟的打包能力,Vite生产环境使用Rollup进行构建,两者均支持代码分割、Tree Shaking等优化
- 。 输出策略 Webpack通过复杂配置可实现细粒度优化(如渐进式 hydration)。Vite的Rollup配置更精简,适合标准SPA/SSG场景,复杂定制需扩展Rollup插件
- 。 四、生态系统 插件生态 Webpack拥有10,000+插件,覆盖各类边缘场景。Vite插件生态正在快速发展,核心覆盖主流框架需求,但复杂场景支持度仍不及Webpack
- 34]。 框架适配 Vite原生优化Vue/React等现代框架,Webpack通过Loader支持更广泛的遗留系统
- 15]。
面试官您好,我是段林振,24岁,来自于江西九江,23年毕业,毕业后一直在数字马力担任前端开发。过去两年参与并主导过公司几个比较重要的项目落地,如学习强国小程序、公司业务管理平台的架构升级、目前主要带领其他前端同学支撑支付宝内部一系列功能迭代,主要技术栈是react和原生移动端小程序,比较久没有面试了,还是比较紧张。
1.JSX jsx的本质 ● 定义:JSX(JavaScript XML)是React提供的语法扩展,允许在JavaScript中编写类似HTML的结构 ● 底层实现:jsx会被babel编译成react.creatElement()调用,接收三个参数,type,config,children,react.creatElement()会将jsx拆分成一些属性,最后尾调用reactElement,reactElement会将拆分出来的参数赋值给element对象(虚拟dom),然后返回给react.creatElement(),react.creatElement()返回开发者,最后通过reactDOM.render方法渲染成真实dom 如何避免JSX回调中的闭包陷阱?
2.生命周期方法演进 1.类组件生命周期演进 ● 16.3之前
● 16.3-16.4:只有new props才能触发getDerivedStateFromProps
● 16.4之后
1.Mounting阶段 ● constructor:初始化state、绑定方法 ● getDerivedStateFromProps:props初始化时同步到state ● render:生成虚拟DOM ● componentDidMount:网络请求、DOM操作、订阅事件 2.Updating阶段 ● getDerviedStateFromProps:props变化时更新state ● shouldComponentUpdate:返回false可阻止渲染(性能优化核心) ● render:生成新虚拟DOM ● getSnapshotBeforeUpdate:获取DOM更新前的状态(如滚动位置) ● componentDidUpdate:DOM更新后操作、网络请求 3.Unmounting阶段 ● componentWillUnmount:清除定时器、取消订阅、释放资源等 2.函数组件生命周期模拟 通过useEffect Hook实现生命周期控制: function Example() { // Mounting useEffect(() => { // componentDidMount const timer = setInterval(...);
return () => { // componentWillUnmount
clearInterval(timer);
}
}, []);
// Updating useEffect(() => { // componentDidUpdate(任意状态变化时) });
useEffect(() => { // 特定状态变化时执行(替代 componentDidUpdate) }, [count]);
// getDerivedStateFromProps 模拟 const [derivedState, setDerivedState] = useState(); useEffect(() => { setDerivedState(props.input); }, [props.input]); }
3.生命周期废弃原因与最佳实践 废弃方法 替代方案 废弃原因 componentWillMount constructor 或 useEffect 异步渲染导致可能多次执行(fiber) componentWillReceiveProps getDerivedStateFromProps + useEffect 容易产生副作用和竞态条件 componentWillUpdate getSnapshotBeforeUpdate 不安全副作用操作风险
3.受控组件与非受控组件
- 核心定义 组件类型 数据管理方式 控制权 受控组件 (Controlled) 表单数据由 React 组件状态(state)驱动 React 完全控制 非受控组件 (Uncontrolled) 表单数据由 DOM 节点自身维护 DOM 原生控制
- 实现原理对比 a. 受控组件实现 核心特点 ● value绑定到React state ● onChange同步更新state ● 数据流:React state -> DOM显示
function ControlledForm() { const [value, setValue] = useState('');
const handleSubmit = (e) => { e.preventDefault(); console.log('提交值:', value); };
return (
<input type="text" value={value} onChange={(e) => setValue(e.target.value)} /> 提交 ); }b. 非受控组件实现 function UncontrolledForm() { const inputRef = useRef(null);
const handleSubmit = (e) => { e.preventDefault(); console.log('提交值:', inputRef.current.value); };
return (
提交 ); }- 为什么文件输入必须用非受控组件?
浏览器的安全策略 浏览器禁止通过 JavaScript 直接设置 的值(即 value 属性),这是为了防止恶意代码绕过用户交互强制上传文件。例如,开发者无法通过 document.getElementById("file").value = "test.txt" 或 React 的 value 属性来指定文件内容
4.类组件和函数式组件 心智成本与设计理念的不同
- 重装战舰与轻盈快艇
- 函数组件会捕获 render(渲染时) 内部的状态,这是两类组件最大的不同。aiguangyuan.blog.csdn.net/article/det…
- 是面向对象和函数式编程这两套不同的设计思想之间的差异 高频面试题 1.为什么推荐使用函数式组件? ● 心智成本低、代码简洁:避免this绑定和类语法冗余 ● 逻辑复用:自定义Hooks比HOC更灵活 ● 性能优化:Hooks提供更细颗粒度的控制(如useMemo) ● 未来兼容:新特性(如并发模式)优先支持Hooks ● 符合react最初的设计思想 2.Hooks的限制和突破 ● 规则:只能在函数顶层调用Hooks ● 原理:依赖调用顺序的链表结构记录状态
5.合成事件机制(onClick与onChange之类的)
-
设计目的与核心原理 a. 跨浏览器一致性 b. 性能优化 ■ 事件委托:将事件绑定到根节点(React 17+ 为应用根DOM),而非每个子元素 ■ 事件池化(Event Pooling):复用事件对象,减少内存开销
-
事件委托机制演进 React 版本 委托层级 核心变化 16.x 及之前 所有事件委托到 document 多 React 应用共存时事件可能冲突 17.x 及之后 委托到应用根 DOM 节点 隔离不同 React 版本的事件系统,避免全局污染 // React 17+ 事件委托结构 const rootNode = document.getElementById('root'); ReactDOM.render(, rootNode); // 所有 React 事件监听器绑定到 rootNode 而非 document
-
合成事件对象 a. 核心属性 interface SyntheticEvent { nativeEvent: Event; // 原生事件对象 currentTarget: DOMElement; // 事件绑定的 React 元素 target: DOMElement; // 触发事件的 DOM 元素 type: string; // 事件类型(如 'click') isDefaultPrevented(): boolean; isPropagationStopped(): boolean; persist(): void; // 禁用事件池化 }
-
事件处理流程 a. 事件注册 ○ React初始化时注册所有支持的事件(如onClick,onChange) ○ 通过EventListener在根节点监听原生事件 事件触发: 原生事件触发 → 根节点捕获事件 → React 生成 SyntheticEvent → 收集事件监听器 → 按组件树冒泡/捕获顺序执行 function App() { const handleParentCapture = () => console.log('Parent Capture'); const handleChildCapture = () => console.log('Child Capture'); const handleChildBubble = () => console.log('Child Bubble'); const handleParentBubble = () => console.log('Parent Bubble');
return (
- 原生事件触发:点击按钮时,浏览器原生 click 事件在按钮元素上触发。
- 根节点捕获事件:React 将所有事件委托到根节点(如 document),原生事件通过冒泡机制传播至根节点。
- 生成 SyntheticEvent:React 将原生事件包装为 SyntheticEvent,并记录事件类型(click)和目标元素(按钮)。
- 收集事件监听器:React 根据组件树结构,收集所有绑定在捕获和冒泡阶段的监听器: ○ 捕获阶段监听器:handleParentCapture(父 div 的 onClickCapture)、handleChildCapture(按钮的 onClickCapture)。 ○ 冒泡阶段监听器:handleChildBubble(按钮的 onClick)、handleParentBubble(父 div 的 onClick)。
- 按组件树顺序执行: ○ 捕获阶段(从根节点到目标元素): ⅰ. handleParentCapture(父 div 的捕获监听器)。 ⅱ. handleChildCapture(按钮的捕获监听器)。 ○ 冒泡阶段(从目标元素到根节点): ⅰ. handleChildBubble(按钮的冒泡监听器)。 ⅱ. handleParentBubble(父 div 的冒泡监听器)。 1 (body 捕获) → 3 (目标元素冒泡) → 2 (父元素冒泡)
- 面试题 1.为什么React不直接将事件绑定在元素上? ● 事件委托减少内存占用,动态更新组件时无需重新绑定事件 2.合成事件和原生事件的区别 ● 合成事件跨浏览器统一行为 ● 原生事件直接操作DOM,无React抽象层 3.如何全局阻止React事件 ● 劫持根节点事件监听
document.getElementById('root').addEventListener('click', e => { e.stopImmediatePropagation(); }, true); 6.组件更新 1.组件更新触发条件 组件重新渲染的根本原因是组件状态或数据依赖发生变化,具体触发场景如下: 触发条件 说明 State 变化 组件内部 useState /useReducer /this.setState 更新状态 Props 变化 父组件重新渲染导致传入的 props 值变化 Context 更新 组件订阅的 Context 数据发生变更 父组件重新渲染 即使子组件的 props 未变化,父组件渲染仍可能导致子组件重新渲染(默认行为) 强制更新 类组件调用 this.forceUpdate() Hooks 依赖变化 useEffect /useMemo /useCallback 的依赖数组元素变更 2.React渲染机制核心原理 1.渲染流程 ● 触发更新 -> 生成虚拟DOM -> Diff算法比较 -> 确定DOM更新范围 -> 提交到真实DOM 2.协调(Reconciliation)策略 ● 树对比:仅对比同层级节点,时间复杂度O(n) ● Key值优化:列表项使用key帮助React识别元素移动/复用 3.渲染优化策略与实践 1.避免不必要的父组件渲染 ● 场景:父组件状态变化导致所有子组件重新渲染 ● 优化方案: js
体验AI代码助手 代码解读 复制代码// 父组件:将状态隔离到独立子组件 function Parent() { return ( <> // 将易变状态抽离 </> ); } 2.组件自身渲染控制 ● 类组件 js
体验AI代码助手 代码解读 复制代码class MyComponent extends React.PureComponent { // 自动浅比较 props/state shouldComponentUpdate(nextProps, nextState) { return !shallowEqual(this.props, nextProps); // 手动控制更新条件 } } ● 函数组件 js
体验AI代码助手 代码解读 复制代码const MemoizedComponent = React.memo(MyComponent, (prevProps, nextProps) => { return prevProps.id === nextProps.id; // 自定义 props 比较 }); 3.精细化Hooks使用 ● 缓存计算结果 js
体验AI代码助手 代码解读 复制代码const expensiveValue = useMemo(() => computeValue(a, b), [a, b]); ● 稳定函数引用 js
体验AI代码助手 代码解读 复制代码const handleClick = useCallback(() => { // 依赖 a 但保持引用稳定 }, [a]); ● 按需订阅Context js
体验AI代码助手 代码解读 复制代码const value = useContextSelector(MyContext, v => v.requiredField); 4.列表渲染优化 ● 虚拟滚动:使用react-window或react-virtualized js
体验AI代码助手 代码解读 复制代码import { FixedSizeList as List } from 'react-window';
{({ index, style }) =>体验AI代码助手 代码解读 复制代码// ❌ 错误:使用索引作为 key {items.map((item, index) => )}
// ✅ 正确:使用唯一标识 {items.map(item => )} 4.高频面试题 1.为什么父组件更新会导致所有子组件渲染?如何避免? ● react默认采用"render and diff"策略,使用React.memo/shouldComponentUpdate阻断无效更新 2.useMemo一定能提升性能吗?使用场景是什么? ● 不一定。仅当计算开销大且稳定时使用,否则可能因依赖数组计算反增开销 3.如何优化Context引起的渲染? ● 拆分多个Context ● 使用useContextSelector按需订阅 js
体验AI代码助手 代码解读 复制代码const ThemeButton = () => { const theme = useContextSelector(ThemeContext, v => v.color); return <button style={{ color: theme }}>Submit; }; 4.函数组件每次渲染都会创建新函数,如何避免传递新props? ● 使用useCallback缓存函数引用 js
体验AI代码助手 代码解读 复制代码const handleSubmit = useCallback(() => { /.../ }, [deps]); 6.性能优化法则
- 优先解决重复渲染问题:使用React DevTools Profiler定位关键路径
- 避免过早优化:只在性能瓶颈出现时实施优化
- 保持组件纯净:减少渲染过程中的副作用操作
- 控制渲染范围:使用children props阻断无关更新 js
体验AI代码助手 代码解读 复制代码// 父组件 {/* 内容不会因 Layout 更新而重渲染 */}
// Layout 组件 function Layout({ children }) { const [state, setState] = useState(); return
- 逻辑复用:解决类组件中高阶组件(HOC)和Render Props的嵌套地狱问题
- 简化组件:告别this绑定和生命周期方法的分散逻辑
- 函数式优先:拥抱函数式编程范式,提升代码可预测性
- 渐进式升级:兼容现有组件,无需重写即可逐步迁移 2.核心原理 1.闭包与链表存储 ● 存储结构:Hooks数据存储在Fiber节点的memoizedState属性中,通过单向链表管理 ● 执行顺序依赖:Hooks调用顺序在每次渲染中必须严格一致(链表顺序不可变) ● 闭包陷阱:每个Hooks闭包捕获当次渲染的props/state快照 js
体验AI代码助手 代码解读 复制代码// Fiber 节点结构示意 const fiber = { memoizedState: { memoizedState: '状态值', // useState 的状态 next: { // 下一个 Hook memoizedState: [], // useEffect 的依赖数组 next: null } } }; 2.调度机制 ● 优先级调度:Hooks更新请求会被Scheduler模块根据优先级(Immediate/UserBlocking/Normal)排队处理 ● 批量更新:React自动合并多个setState调用,减少渲染次数 3.核心Hooks原理解析 1.useState ● 存储结构:[state, dispatchAction]存储在链表节点中 ● 更新触发:调用dispatchAction会创建更新对象,触发重新渲染 ● 异步更新:状态更新在下次渲染时生效(遵循批量更新原则) js
体验AI代码助手 代码解读 复制代码// 简化实现 function useState(initial){ const fiber = getCurrentFiber(); const hook = fiber.memorizedState?.isStateHook ? fiber.memoizedState : { memouzedState: initial, next: null }; const dispatch = (action) => { const update = { action }; hook.queue.push(update); scheduleWork(); // 触发重新渲染 }; return [hook.memoizedState, dispatch]; } 2.useEffect ● 依赖对比:使用浅比较(Object.is)判断依赖数组变化 ● 执行时机:在浏览器完成布局与绘制后异步执行(避免阻塞渲染) ● 清理机制:返回的清理函数会在下次effect执行前或组件卸载前执行 js
体验AI代码助手 代码解读 复制代码// 依赖对比伪代码 function areDepsEqual(prevDeps, nextDeps) { if (!prevDeps || !nextDeps) return false; for (let i = 0; i < prevDeps.length; i++) { if (!Object.is(prevDeps[i], nextDeps[i])) return false; } return true; } 3.useRef ● 跨渲染存储:ref对象在组件生命周期内保持不变 ● 直接修改:ref.current的修改不会触发重新渲染 js
体验AI代码助手 代码解读 复制代码// 简化实现 function useRef(initialValue) { const ref = { current: initialValue }; return useMemo(() => ref, []); // 永远返回同一引用 } 4.Hooks规则的本质 1.为什么必须顶层调用? ● 链表顺序依赖:Hooks的存储依赖调用顺序,条件语句会破坏链表结构 2.为什么只能在函数组件中使用? ● Fiber关联:Hooks需要绑定当前组件的Fiber节点,普通函数无此上下文 5.闭包陷阱与解决方案 1.过期闭包问题 js
体验AI代码助手 代码解读 复制代码function Timer() { const [count, setCount] = useState(0);
useEffect(() => { const timer = setInterval(() => { setCount(count + 1); // 始终捕获初始值 0 }, 1000); return () => clearInterval(timer); }, []); // ❌ 错误:空依赖数组
// 正确方案:使用函数式更新或添加依赖 useEffect(() => { const timer = setInterval(() => { setCount(c => c + 1); // ✅ 获取最新值 }, 1000); return () => clearInterval(timer); }, []); } 2.解决方案 ● 函数式更新:setState(c => c + 1) ● 依赖数组精准化:确保所有依赖项被正确声明 ● useRef穿透闭包:通过ref访问最新值 js
体验AI代码助手 代码解读 复制代码const countRef = useRef(count); countRef.current = count; 6.高频面试题 1.Hooks如何实现状态隔离? ● 每个组件实例的Fiber节点维护独立的Hooks链表,相同组件不同实例互不影响 2.自定义Hook的本质是什么? ● 将多个内置Hooks组合为可复用的逻辑单元,遵循Hooks规则 3.为什么useEffect的依赖数组是浅比较 ● 性能优化考量,深比较成本过高。复杂对象应使用useMemo稳定引用 4.useMemo/useCallback如何避免重复计算? ● 通过依赖数组决定是否重新计算,引用稳定时返回缓存值 js
体验AI代码助手 代码解读 复制代码const memoValue = useMemo(() => compute(a), [a]); const memoFn = useCallback(() => action(a), [a]); 7.性能优化策略 优化手段 实现方式 适用场景 精细化依赖数组 确保依赖数组只包含必要变量 所有 Hooks 状态提升 将状态提升到父组件或 Context 多组件共享状态 惰性初始 state useState(() => expensiveInit()) 初始值计算成本高时 批量更新 unstable_batchedUpdates (React 18 自动) 合并多次状态更新
作者:EB_Coder 链接:juejin.cn/post/750454… 来源:稀土掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 8.常用Hooks 1.基础Hooks 1.useState ● 作用:为函数组件添加状态管理能力 ● 使用场景:组件内部的状态管理 js
体验AI代码助手 代码解读 复制代码const [count, setCount] = useState(() => 0); // 惰性初始化 const increment = () => setCount(prev => prev + 1); ● 注意: ○ 状态更新是异步的,连续调用会被合并 ○ 复杂对象建议使用useReducer 2.useEffect ● 作用:处理副作用(数据请求、DOM操作、订阅) ● 生命周期映射: ○ componentDidMount -> 依赖数组为空 [] ○ componentDidUpdate -> 指定依赖项 [dep] ○ componentWillUnmount -> 返回清理函数 js
体验AI代码助手 代码解读 复制代码useEffect(() => { const subscription = props.source.subscribe(); return () => subscription.unsubscribe(); // 清理 }, [props.source]); ● 注意 ○ 默认在浏览器完成渲染后异步执行 ○ 使用useLayoutEffect处理同步DOM操作 3.useContext ● 作用:跨组件层级传递数据 js
体验AI代码助手 代码解读 复制代码const ThemeContext = createContext('light');
function App() { return ( <ThemeContext.Provider value="dark"> </ThemeContext.Provider> ); }
function Toolbar() { const theme = useContext(ThemeContext); return
体验AI代码助手 代码解读 复制代码const expensiveValue = useMemo(() => compute(a, b), [a, b]); ● 注意: ○ 依赖数组引用,避免子组件无效渲染 ○ 不应用于有副作用的操作 2.useCallback ● 作用:缓存函数引用,避免子组件无效渲染 js
体验AI代码助手 代码解读 复制代码const handleSubmit = useCallback(() => { submitData(name); }, [name]); ● 等价写法: js
体验AI代码助手 代码解读 复制代码const memoizedFn = useMemo(() => () => submitData(name), [name]); 3.React.memo ● 作用:浅比较props变化,阻止无效渲染 js
体验AI代码助手 代码解读 复制代码const MemoComponent = React.memo(Child, (prev, next) => { return prev.id === next.id; // 自定义比较函数 }); 进阶Hooks 1.useReducer ● 作用:复杂状态逻辑管理(类似Redux) js
体验AI代码助手 代码解读 复制代码const initialState = { count: 0 }; function reducer(state, action) { switch(action.type) { case 'increment': return { count: state.count + 1 }; default: return state; } } const [count, dispatch] = useReducer(reducer, initialState); ● 优势:适合多状态联动或逻辑状态复杂的场景 2.useRef ● 作用: ○ 访问DOM元素 ○ 保存可变值(不触发渲染) js
体验AI代码助手 代码解读 复制代码const inputRef = useRef(); useEffect(() => inputRef.current.focus(), []);
// 保存上一次 props 值 const prevCount = useRef(count); useEffect(() => { prevCount.current = count; }); 3.useLayoutEffect ● 作用:同步执行副作用,在浏览器绘制前完成DOM修改 ● 场景:测量DOM布局、同步样式调整 js
体验AI代码助手 代码解读 复制代码useLayoutEffect(() => { const width = divRef.current.offsetWidth; setWidth(width); // 同步更新状态 }, []); 4.特殊场景Hooks 1.useImperativeHandle ● 作用:自定义暴露给父组件的实例方法 js
体验AI代码助手 代码解读 复制代码const FancyInput = forwardRef((props, ref) => { const inputRef = useRef(); useImperativeHandle(ref, () => ({ focus: () => inputRef.current.focus() })); return ; }); 2.useDebugValue ● 作用:在React开发中工具中显示自定义hook标签 js
体验AI代码助手 代码解读 复制代码function useFriendStatus() { const [isOnline] = useState(null); useDebugValue(isOnline ? 'Online' : 'Offline'); return isOnline; } 5.高频面试题 1.useEffect和useLayoutEffect的区别? ● useEffect异步执行(不阻塞渲染) ● useLayoutEffect同步执行(在DOM更新后,浏览器绘制前) 2.如何避免useEffect的无限循环? ● 正确设置依赖数组 ● 使用useCallback/useMemo稳定引用 3.useMemo和React.memo的区别? ● useMemo缓存值 ● React.memo缓存组件渲染结果
9.虚拟DOM算法原理 1.核心设计思想 React的Diff算法基于两个核心假设,以O(n)时间复杂度完成树结构的高效比较
- 类型差异假设:不同类型的元素会生成不同的树结构,直接替换整个子树
- Key稳定性假设:通过key标识相同层级的元素是否可复用 2.分层对比策略 React采用逐层递归比较,但不会跨层级追踪节点变化: text
体验AI代码助手 代码解读 复制代码旧树:
处理逻辑:发现 div → section 类型不同,直接销毁整个 div 子树,重建 section 子树 3.同层级节点比较 当父节点类型相同时,递归比较其他子节点 1.列表节点对比优化 ● 场景:动态列表项的顺序变化(增删、排序) ● 策略:使用唯一的key标识元素身份,最小化移动操作 js
体验AI代码助手 代码解读 复制代码// 旧列表
- A
- B
// 新列表(B 移动到 A 前面)
- B
- A
// React 处理:仅移动 DOM 节点,而非销毁重建 2.无key列表的默认行为 ● 使用索引作为key:可能导致错误复用(如列表项内容变化但位置不变) ● 性能陷阱:列表顺序变化时,索引变化导致不必要的重新渲染 4.元素类型处理 1.类型相同 ● 更新属性:仅修改变化的属性(如className、style) ● 递归比较子节点:继续对比子元素 2.类型不同 ● 销毁旧子树:触发旧组件 componentWillUnmount ● 创建新子树:触发新组件 constructor -> render -> componentDidMount 5.Diff算法步骤分解 1.根节点对比 ● 类型不同 -> 整树替换 ● 类型相同 -> 进入属性比较 2.属性更新 js
体验AI代码助手 代码解读 复制代码// 伪代码示例 function updateDOMProperties(domElement, prevProps, nextProps) { // 移除旧属性 Object.keys(prevProps).forEach(propName => { if (!nextProps.hasOwnProperty(propName)) { domElement.removeAttribute(propName); } });
// 设置新属性 Object.keys(nextProps).forEach(propName => { if (prevProps[propName] !== nextProps[propName]) { domElement.setAttribute(propName, nextProps[propName]); } }); } 3.子节点递归对比 ● 策略:双指针遍历新旧子节点列表 ● 操作类型: ○ INSERT:新增节点 ○ MOVE:移动已有节点 ○ REMOVE:删除废旧节点 key的优化机制 场景 无 Key 有 Key 列表项顺序变化 索引变化导致全部重新渲染 识别移动项,仅调整 DOM 顺序 列表中间插入项 后续项索引变化触发多节点更新 仅插入新节点,后续项无变化 删除中间项 后续项索引变化触发多节点更新 仅删除目标节点 7.性能优化实践 1.key的使用原则 场景 无 Key 有 Key 列表项顺序变化 索引变化导致全部重新渲染 识别移动项,仅调整 DOM 顺序 列表中间插入项 后续项索引变化触发多节点更新 仅插入新节点,后续项无变化 删除中间项 后续项索引变化触发多节点更新 仅删除目标节点 7.性能优化实践 1.key的使用原则 ● 使用数据唯一的标识(如id) ● 避免随机数或索引(不稳定) 2.避免跨层级移动节点 ● 修改父节点类型会导致子树重建 3.减少顶层节点类型变化 ● 保持稳定的组件结构 4.复杂列表优化 ● 虚拟滚动(如reac-window) ● 分页加载 高频面试题 1.为什么列表必须使用key? ● 帮助React识别元素身份,在顺序变化时高效复用DOM节点,避免以下问题: ○ 不必要的子组件重新渲染 ○ 表单元素状态错乱(如输入框内容错位) 2.如何强制组件重新挂载? ● 改变key值触发销毁/重建 js
体验AI代码助手 代码解读 复制代码 3.Diff算法能完全避免DOM操作吗? ● 不能。目的是最小化操作次数,但无法消除必要的更新(如数据变化必然导致DOM修改) 10.Fiber 1.Fiber的诞生背景 1.旧版协调算法瓶颈 ● 递归不可中断:同步遍历整个虚拟DOM树,长时间占用主线程 ● 卡顿问题:复杂组件树更新导致掉帧(如大型列表、动画场景) 2.目标 ● 实现增量渲染,支持异步可中断的更新 2.Fiber核心设计思想 1.时间切片 ● 将渲染任务拆分为多个小任务(Fiber节点) ● 利用浏览器空闲时段(requestIdleCallback)分片执行 2.优先级调度 ● 用户交互(如输入)优先于数据更新(如API响应) 3.可恢复工作单元 ● 保存中间状态,允许暂停/恢复渲染流程 3.Fiber节点数据结构 每个Fiber节点对应一个组件实例或DOM节点,包含以下核心属性: 属性 类型 作用 type String/Object 组件类型(如 'div' 、函数组件引用) stateNode Object 对应的 DOM 节点或类组件实例 child Fiber 第一个子节点 sibling Fiber 下一个兄弟节点 return Fiber 父节点 pendingProps Object 新传入的 props memoizedProps Object 上一次渲染使用的 props memoizedState Object 上一次渲染后的 state(如 Hooks 链表) effectTag Number 标记副作用类型(如 Placement 、Update 、Deletion ) alternate Fiber 指向当前 Fiber 的镜像(用于 Diff 比较) 4.Fiber双缓冲机制 React维护两颗Fiber树已确保更新无冲突
- Current Tree:当前已渲染的UI对应的Fiber树
- WorkInProgress Tree:正在构建的新Fiber树 ● 切换流程:更新完成后,WorkInProgress Tree树变为Current树 text
体验AI代码助手 代码解读 复制代码初始渲染: Current Tree: null WorkInProgress Tree: → 构建完成 → 提交后成为 Current Tree
更新阶段: Current Tree ←→ WorkInProgress Tree(复用或新建节点) 3.Fiber渲染 1.协调阶段 ● 目标:生成副作用列表,不修改DOM ● 过程: a. 递阶段:调用render生成子节点,标记变化 b. 归阶段:向上回溯收集副作用 ● 可中断:根据剩余时间片暂停/恢复遍历 2.提交阶段 ● 目标:同步执行所有DOM变更 ● 过程: a. Before Mutation:调用getSnapshotBeforeUpdate b. Mutation:执行DOM增删改 c. Layout:调用useLayoutEffect和componentEDidMount/Update ● 不可中断:避免中间状态导致UI不一致 6.优先级调度模型 优先级 对应场景 ImmediatePriority 同步任务(如 flushSync ) UserBlockingPriority 用户交互(点击、输入) NormalPriority 数据更新、网络响应 LowPriority 过渡更新(Concurrent 模式) IdlePriority 空闲时执行的任务 ● 调度策略:高优先级任务可中断低优先级任务 7.Fiber对生命周期的影响 1.废弃生命周期: ● componentWillMount、componentWillReceiveProps、componentWillUpdate ● 原因:异步可渲染可能导致多次调用,引发副作用错误 2.新增API ● getDerivedStateFromProps(静态方法,替代componentWillReceiveProps) ● getSnapshotBeforeUpdate(替代componentWillUpdate) 8.Fiber与并发模式 1.并发特性 ● useTranstion:标记非紧急更新,可被高优先级任务打断 ● Suspense:等待异步数据加载时显示回退UI 2.启用方式 // 创建根节点时启用 ReactDOM.createRoot(document.getElementById('root')).render(); 9.高频面试题 1.Fiber如何实现可中断更新? ● 通过链表结构存储Fiber节点,记录遍历进度。每次处理一个Fiber后检查剩余时间片,不足时保存当前进度,将控制权交还浏览器 2.协调阶段和提交阶段的区别 ● 协调阶段负责计算变更(可中断),提交阶段执行DOM操作(同步不可中断) 3.为什么需要双缓冲机制? ● 保证渲染过程中Current Tree始终完整可用,避免更新过程中出现UI不一致 11.路由(简单描述) React Router 的不同版本在路由配置和 API 设计上有显著差异,以下是 v5、v6 和 v7 的基础用法示例:
React Router v5 import { BrowserRouter as Router, Route, Switch, Link } from 'react-router-dom';
function App() { return (
Home About Users ); } ● 核心特性:使用 匹配第一个匹配的 ,通过 component 属性绑定组件精确匹配是指路由路径必须完全匹配当前 URL 才会触发对应的组件渲染。在 React Router v5 中,通过 exact 属性实现此行为,例如: jsx
若未设置 exact,路径 /home 会匹配 /home/about 等子路径 React Router v6 改进了路径匹配算法,默认启用精确匹配,无需 exact 属性。例如: jsx<Route path="/home" element={} /> 此时 /home 仅匹配 /home,不会匹配 /home/about。此外,v6 的匹配逻辑基于最长路径优先,自动选择最佳匹配,无需手动调整路由顺序 React Router v6 import { BrowserRouter as Router, Route, Routes, Link, Outlet } from 'react-router-dom';
function App() { return (
Home About <Route path="/" element={}> <Route index element={} /> <Route path="about" element={} /> ); }function Layout() { return (
Layout
{/* 渲染子路由 */}React Router v7 import { createBrowserRouter, RouterProvider, Link } from 'react-router-dom';
const router = createBrowserRouter([ { path: '/', element: , children: [ { index: true, element: }, { path: 'about', element: }, ], }, ]);
function App() { return ; }
function Layout() { return (
版本差异总结 ● v5:扁平路由 + Switch + component。 ● v6:嵌套路由 + + element。 ● v7:数据路由器 + 配置对象 + 更灵活的数据加载能力。 路由模式区别 在 React Router 中,BrowserRouter(对应 browserHistory)和 HashRouter(对应 hashHistory)的跳转方式主要通过 编程式导航 和 声明式导航 实现,具体区别如下:
- 声明式导航(使用 组件) ● BrowserRouter jsx
import { Link } from 'react-router-dom';
跳转到用户页 URL 会变为 https://example.com/user/123,无 # 符号 。 ● HashRouter jsximport { Link } from 'react-router-dom';
跳转到用户页 URL 会变为 https://example.com/#/user/123,包含 # 。- 编程式导航(使用 history 对象) ● BrowserRouter javascript
import { useHistory } from 'react-router-dom'; const history = useHistory(); history.push('/user/123'); // 跳转并添加历史记录 history.replace('/user/456'); // 替换当前历史记录 使用 HTML5 pushState/replaceState 方法更新 URL 。 ● HashRouter javascript
import { useHistory } from 'react-router-dom';
const history = useHistory();
history.push('/user/123'); // 内部自动处理为 /#/user/123
实际通过修改 window.location.hash 或 pushState 实现跳转 。
- 直接操作浏览器 API(非 React Router 推荐方式) ● BrowserRouter javascript
window.history.pushState(null, '', '/user/123'); // 需手动触发路由更新(React Router 不自动监听) ● HashRouter javascript
window.location.hash = '/user/123'; // React Router 会监听 hashchange 事件并更新路由
- 路由配置示例 ● BrowserRouter jsx
import { BrowserRouter as Router, Route } from 'react-router-dom'; 需要服务器配置(如 Express 的 app.get('*', ...))以支持刷新页面 。 ● HashRouter jsx
import { HashRouter as Router, Route } from 'react-router-dom'; 无需服务器配置,直接使用 # 分割的 URL 。
关键区别 全屏复制 特性 BrowserRouter HashRouter URL 格式 example.com/user/123 example.com/#/user/123 跳转方式 history.push /replace (依赖 History API) history.push /replace (内部处理 hash) 服务器配置 需配置以支持所有路径返回 index.html 无需配置 SEO 友好性 更友好(搜索引擎可抓取真实路径) 较差(# 后内容可能被忽略)
注意事项 ● BrowserRouter 需确保服务器配置正确,否则刷新页面会返回 404 错误 7。 ● HashRouter 的 # 会被浏览器忽略发送到服务器,适合静态部署场景 1。
1.微前端 2.首页渲染时间 3.前端部署原理,webpack等
复习排期 基础复习: [X] 7.24-7.28 html、css、js基础复习 [X] 7.29-7.30 react复习 (指标 复习一遍深入浅出搞定react,包括掘金上面文章) [X] 7.31 react面试题总结问答 [X] 8.1 计算机网络篇 [X] 8.2 浏览器原理及性能优化篇 [X] 8.3 手写代码篇 [X] 8.4 代码输出结果篇
简历专项: [X] 8.5 1.微前端:yuque.antfin.com/dfx/etc/xlc… juejin.cn/post/742407… [X] 8.6 2.首页渲染时间 yuque.antfin.com/duanlinzhen… [X] 8.7 前端部署原理,webpack之类的