每次面对无穷无尽、不停迭代的前端知识,只想祈求秦始皇复活,来统一前端这些框架!学不完,根本学不完。但是为了找工作还是得老老实实背八股。背过的八股,不叫八股,是生产力!
1、React的事件和普通事件有什么不同
✅ 1. 事件绑定方式不同
React 中使用的是 驼峰命名,并且直接传入函数引用:
xml
<button onClick={handleClick}>点我</button>
原生 DOM 则是小写并用字符串或函数赋值:
ini
<button onclick="handleClick()">点我</button>
或者在 JS 中绑定:
arduino
element.addEventListener('click', handleClick)
✅ 2. React 使用的是合成事件(SyntheticEvent)
React 实现了一套自己的事件系统:SyntheticEvent 合成事件。
- 它对原生事件进行了封装,兼容性更强。
- 所有事件都会被委托到最外层(
document
),提升性能(事件委托机制)。 e
是一个合成事件对象,不是原生事件,但可以通过e.nativeEvent
访问原生事件。
javascript
function handleClick(e) {
console.log(e); // React SyntheticEvent
console.log(e.nativeEvent); // 原生事件
}
✅ 3. 阻止默认行为和冒泡方式不同
React 的合成事件里不能直接使用 return false
,而是要使用专门方法:
scss
function handleClick(e) {
e.preventDefault(); // 阻止默认行为
e.stopPropagation(); // 阻止事件冒泡
}
✅ 4. 事件解绑方式不同
React 中不推荐在组件内部动态绑定/解绑事件,因为 React 会在重新渲染时自动处理。
如果真要控制事件生命周期,推荐使用 useEffect
中手动绑定原生事件:
javascript
useEffect(() => {
const handler = () => console.log('clicked');
window.addEventListener('click', handler);
return () => window.removeEventListener('click', handler);
}, []);
2、React的事件委托机制
🧠 什么是事件委托?
事件委托的意思是:不在每个子元素上绑定事件,而是把事件绑定到父元素,利用事件冒泡机制来处理子元素的事件。
🧩 React 是怎么做事件委托的?
React 并不是在每一个 DOM 节点上都添加事件监听器,而是:
✅ 把所有事件都绑定在根节点(通常是 document
或 root
容器)上。 然后:
-
当事件发生时,比如你点击了一个按钮;
-
浏览器的事件冒泡机制会把事件从按钮冒泡到
document
; -
React 统一在顶层监听到这个事件后,会:
- 根据事件目标(
event.target
)和虚拟 DOM 中注册的事件, - 找到对应的组件方法,调用你写的事件处理函数。
- 根据事件目标(
🧪 举个例子
javascript
function App() {
function handleClick() {
console.log('按钮被点击了!');
}
return <button onClick={handleClick}>点我</button>;
}
你写了一个 onClick
,React 并不是在这个 button
上真的加了一个 click
事件,而是:
- 把事件注册到了
document
上; - 通过事件冒泡捕获到点击;
- 再根据虚拟 DOM 的结构找到这个按钮的
onClick
函数并执行。
🌟 为什么这么做?
好处是:
- ✅ 减少真实 DOM 上的事件绑定数量;
- ✅ 更容易做统一管理(比如事件池、清理、合成事件等);
- ✅ 提升性能,尤其是组件多、结构复杂的情况下。
🧬 React 的合成事件(SyntheticEvent)
React 实际上不是直接处理原生事件,而是封装了一层叫做 合成事件(SyntheticEvent) 的机制:
- 它是对原生事件的包装;
- 提供跨浏览器一致的事件属性和行为;
- 同时还能复用事件对象(提升性能);
- 最终仍然会在事件执行结束后调用原生事件。
🧯 注意点
- React 的事件是 冒泡阶段触发 的;
- 如果你使用了
stopPropagation()
,事件就不会继续冒泡; - 如果你使用
nativeEvent
,那是原生事件对象(需要小心使用);
3、React中如何避免不必要的render
🧠 1. 使用 React.memo
(函数组件)
对函数组件 来说,React.memo
是最常用的优化方式。
javascript
const MyComponent = React.memo((props) => {
console.log("render");
return <div>{props.name}</div>;
});
- 默认情况下,
React.memo
会浅比较 props,如果 props 没变化,就跳过渲染。 - 如果你需要自定义比较逻辑,可以传第二个参数:
bash
React.memo(Component, (prevProps, nextProps) => {
return prevProps.id === nextProps.id; // 返回 true 表示"跳过更新"
});
🧠 2. 使用 useMemo
缓存计算结果
当你有复杂计算或生成的变量 ,用 useMemo
可以避免每次 render 都重新计算:
ini
const expensiveValue = useMemo(() => {
return computeExpensiveValue(a, b);
}, [a, b]);
- 只有当依赖项变了才会重新计算。
🧠 3. 使用 useCallback
缓存函数引用
传函数作为 prop 时,如果每次都传一个新函数,会让子组件以为 props 变了导致重渲。
ini
const handleClick = useCallback(() => {
doSomething();
}, [dependency]);
搭配 React.memo
使用效果最佳。
🧠 4. 使用 shouldComponentUpdate
(类组件)
对于类组件,可以手动控制组件是否需要更新。
javascript
shouldComponentUpdate(nextProps, nextState) {
return nextProps.value !== this.props.value;
}
也可以继承 React.PureComponent
,它会帮你做浅比较。
🧠 5. 避免匿名函数和内联对象作为 props
ini
// ❌ 这种每次 render 都会生成新的函数和对象
<MyComponent onClick={() => {}} style={{ color: "red" }} />
// ✅ 用 useCallback 和 useMemo
const onClick = useCallback(() => {}, []);
const style = useMemo(() => ({ color: "red" }), []);
<MyComponent onClick={onClick} style={style} />
🧠 6. 拆分组件,让局部更新不影响整体
把组件拆小一点,避免父组件更新时,所有子组件都被迫重渲染。
🧠 7. 避免不必要的 setState
例如:
scss
// ❌ 其实 value 没变,就没必要调用 setState
if (value !== newValue) {
setValue(newValue);
}
✅ 总结:避免不必要 render 的常见方式
方法 | 使用场景 |
---|---|
React.memo |
缓存组件,props 没变就不渲染 |
useMemo |
缓存计算结果(如 filter/map/sort) |
useCallback |
缓存函数引用,避免子组件误判 |
PureComponent / shouldComponentUpdate |
类组件控制更新 |
拆组件 | 局部更新更高效 |
精准控制 setState |
减少无意义的状态更新 |
4、Webpack的打包过程
Webpack 的打包过程就像一个流水线,把你的项目代码"加工处理"成浏览器能直接运行的格式。我们可以从宏观上分 5 个阶段来理解整个打包流程:
1. 初始化阶段(Initialization)
- 读取配置文件(
webpack.config.js
) - 合并默认配置和用户配置
- 初始化编译器(
Compiler
)对象 - 注册插件(调用
plugins.apply()
)
css
module.exports = {
entry: './src/index.js',
output: { filename: 'bundle.js' },
plugins: [new MyPlugin()]
}
2. 构建依赖图(Build)
- 从
entry
开始,调用对应的 Loader 处理模块 - 解析依赖:比如
import
,require()
等 - 把依赖的模块也加入依赖图
- 递归处理所有依赖模块
最终会生成一张完整的依赖关系图(Dependency Graph)
3. 模块转换(Transform)
- 所有模块交给 Loader 处理(如 Babel 转换 ES6)
- 每个模块变成 Webpack 能识别的格式(CommonJS)
bash
module: {
rules: [
{ test: /.js$/, use: 'babel-loader' },
{ test: /.css$/, use: ['style-loader', 'css-loader'] }
]
}
4. 生成 Chunk(代码块)
-
按照依赖图,把模块组合成不同的 Chunk
- 主 Chunk(入口)+ 异步 Chunk(懒加载)
-
插件可以介入修改 Chunk 内容
5. 输出阶段(Emit)
-
Webpack 根据
output
设置生成最终文件- 比如
bundle.js
、index.html
、图片等
- 比如
-
插件如
HtmlWebpackPlugin
会生成 HTML 文件并注入打包好的 JS -
写入硬盘或内存中(如
webpack-dev-server
)
css
output: {
filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist')
}
5、常见的状态码
🟢 1xx 信息响应类
状态码 | 含义 |
---|---|
100 | Continue,请继续请求 |
101 | Switching Protocols,协议切换 |
🟩 2xx 成功响应类
状态码 | 含义 |
---|---|
200 | OK,请求成功 |
201 | Created,资源创建成功(常见于 POST) |
204 | No Content,请求成功但无返回内容(常见于 DELETE) |
🟨 3xx 重定向响应类
状态码 | 含义 |
---|---|
301 | 永久重定向(资源永久移动) |
302 | 临时重定向 |
304 | Not Modified,资源未修改(用于缓存) |
🟥 4xx 客户端错误类
状态码 | 含义 |
---|---|
400 | Bad Request,请求语法错误 |
401 | Unauthorized,未认证(需要登录) |
403 | Forbidden,已认证但没权限 |
404 | Not Found,资源不存在 |
405 | Method Not Allowed,不支持的请求方法 |
🟥 5xx 服务器错误类
状态码 | 含义 |
---|---|
500 | Internal Server Error,服务器内部出错 |
502 | Bad Gateway,网关错误 |
503 | Service Unavailable,服务器当前无法处理请求(比如宕机、限流) |
504 | Gateway Timeout,网关超时 |