一、基础概念
1.React核心设计思想
组件化设计
- 原子化构建:
UI
分解为独立的功能单元(如函数式组件/类组件) - 组合模式:通过
props
嵌套实现组件树结构 - 隔离型:每个组件维护自身状态和样式(
CSS-in-JS
生态支持) - 复用机制:高阶组件(
HOC
)与自定义Hooks
实现逻辑复用
声明式编程范式
- 状态驱动:
UI=f(state)
的数学表达式(无需手动操作DOM) - 抽象渲染:开发者专注描述目标状态,框架处理渲染细节
- 幂等性保证:相同
state
必定输出相同视图(确定性原则)
高效更新引擎
- 虚拟DOM层:内存中轻量级
DOM
表示(对象树结构) - Diff算法优化:启发式
O(n)
复杂度比较策略(基于树遍历策略) - 批量更新策略:自动合并
setState
操作(异步更新策略) - 渲染流水线:
Fiber
架构实现可中断渲染(并发模式)
单向数据控制
- 严格数据通道:
props
自上而下传递(严禁子级逆向修改) - 状态托管:通过
Context/Redux
实现跨层级通信 - 副作用隔离:
Hooks
机制约束副作用边界(useEffect
依赖链)
这些原则使React具备:高维护性(组件解耦)、高性能(智能更新)、可预测性(数据流透明)。
2.JSX
1.JSX的本质
- 定义:
JSX(JavaScript XML)
是React提供的语法扩展,允许在JavaScript中编写类似HTML的结构 - 核心作用:提升代码可读性,直观描述UI的层次结构,同时保留JavaScript的全部编程能力
- 底层实现:JSX会被Babel或TypeScript编译器转换为
React.createElement()
调用,生成React元素(即虚拟DOM对象)
1const element = <div className="title">Hello React</div>;
2
3const element = React.createElement(
4 'div',
5 { className:'title' },
6 'Hello React'
7)
2.JSX编译过程
- 编译阶段:Babel的
@babel/plugin-transform-react-jsx
插件负责转换 - 新转换模式(React17+):通过
jsx-runtime
自动引入_jsx
函数,无需手动引入React
1import { jsx as _jsx } from 'react/jsx-runtime';
2const element = _jsx('div', { className: 'title', children: 'Hello React' });
3.JSX与HTML的关键区别
- 属性命名:使用小驼峰命名(如
className,htmlFor,onClick
) - 样式对象:CSS属性转为小驼峰,值为字符串或数值
1<div style={{ fontSize: 14, padding: '10px' }} ></div>
- 布尔值属性:属性省略值时默认为
true
(如<input disabled />
) - 子元素类型:支持字符串、JSX元素、数组,
false/null/undefined
不渲染
4.JSX中的表达式嵌入
- 动态内容:使用{}嵌入任意JavaScript表达式
1<h1>当前时间:{new Date().toLocaleTimeString()}</h1>
- 条件渲染:
1{isLoggedIn ? <UserPanel /> : <LoginButton />}
- 列表渲染:
1<ul>
2 {items.map(item => (
3 <li key={item.id}>{item.name}</li>
4 ))}
5</ul>
5.JSX安全特性
- 自动转义:嵌入内容(如用户输入)会被转义为字符串,防止XSS攻击
1const userInput = '<script>恶意代码</script>';
2<div>{userInput}</div>
- dangerouslySetInnerHTML:显式插入原始HTML(需手动防范风险)
1<div dangerouslySetInnerHTML={{ __html: sanitizedHTML }} />
6.JSX高级用法
- 片段(Fragment):包裹多个元素避免额外的DOM节点
1<React.Fragment>
2 <td>Column 1</td>
3 <td>Column 2</td>
4</React.Fragment>
5
- 组件嵌套:自定义组件必须以大写字母开头,小写标签视为原生标签
1<MyComponent prop1="value">
2 <ChildComponent />
3</MyComponent>
7.JSX与模版引擎对比
特性 | JSX | 传统模板引擎(如 Vue) |
---|---|---|
语法灵活性 | 原生 JavaScript 能力,无 DSL 限制 | 受限于模板语法(指令、过滤器等) |
组件逻辑 | 逻辑与 UI 紧密耦合 | 分离的模板与脚本块 |
类型安全 | 完美支持 TypeScript 类型检查 | 需要额外工具支持 |
8.实战技巧
- 条件渲染优化:使用短路运算简化简单条件
1{isLoading && <Spinner />}
- 动态组件:通过变量名渲染不同组件
1const components = { photo: PhotoComponent, video: VideoComponent };
2const DynamicComponent = components[type];
3return <DynamicComponent />;
高频面试题
1.为什么JSX中组件首字母必须大写?
- React通过首字母大小写区分原生DOM标签(如
<div>
)和自定义组件(如<MyComponent>
)
2.如何避免JSX回调中的闭包陷阱?
- 使用
useCallback
缓存函数,或通过函数参数传递最新值
3.生命周期方法演进
1.类组件生命周期演进
1.传统生命周期(React 16.3前)
1class Example extends React.Component {
2
3 constructor(props) {
4 super(props);
5 this.state = { };
6 }
7
8
9 componentWillMount() { }
10 render() { return <div>...</div> }
11 componentDidMount() { }
12
13
14 componentWillReceiveProps(nextProps) { }
15 shouldComponentUpdate(nextProps, nextState) { return true }
16 componentWillUpdate() { }
17 render() { }
18 componentDidUpdate(prevProps, prevState) { }
19
20
21 componentWillUnmount() { }
22}
4.新版本生命周期(React 16.3+)
1class Example extends React.Component {
2
3 static getDerivedStateFromProps(props, state) {
4
5 }
6
7
8 componentDidCatch(error, info) { }
9
10
11 getSnapshotBeforeUpdate(prevProps, prevState) {
12
13 }
14}
2.生命周期阶段
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:清除定时器、取消订阅、释放资源等
3.函数组件生命周期模拟
通过useEffect Hook实现生命周期控制:
1function Example() {
2
3 useEffect(() => {
4
5 const timer = setInterval(...);
6
7 return () => {
8 clearInterval(timer);
9 }
10 }, []);
11
12
13 useEffect(() => {
14
15 });
16
17 useEffect(() => {
18
19 }, [count]);
20
21
22 const [derivedState, setDerivedState] = useState();
23 useEffect(() => {
24 setDerivedState(props.input);
25 }, [props.input]);
26}
4.生命周期废弃原因与最佳实践
废弃方法 | 替代方案 | 废弃原因 |
---|---|---|
componentWillMount | constructor 或 useEffect | 异步渲染导致可能多次执行 |
componentWillReceiveProps | getDerivedStateFromProps + useEffect | 容易产生副作用和竞态条件 |
componentWillUpdate | getSnapshotBeforeUpdate | 不安全副作用操作风险 |
5.性能优化
- shouldComponentUpdate优化
1shouldComponentUpdate(nextProps, nextState) {
2
3 return !shallowEqual(this.props, nextProps)
4 || !shallowEqual(this.state, nextState);
5}
- PureComponent自动浅比较
1class Example extends React.PureComponent { ... }
- React.memo函数组件优化
1const MemoComponent = React.memo(MyComponent, areEqual);
6.错误边界处理
1class ErrorBoundary extends React.Component {
2 state = { hasError: false };
3
4 static getDerivedStateFromError(error) {
5 return { hasError: true };
6 }
7
8 componentDidCatch(error, info) {
9 logErrorToService(error, info);
10 }
11
12 render() {
13 return this.state.hasError
14 ? <FallbackUI />
15 : this.props.children;
16 }
17}
7.高频面试题
1.为什么componentWillXXX系列方法被标记为UNSAFE
- 异步渲染模式(Concurrent Mode)下可能被多次调用
- 副作用操作可能会导致渲染不一致
2.useEffect与生命周期方法的对应关系
useEffect(fn, []) -> componentDidMount + componentWillUnmount
useEffect(fn) -> componentDidUpdate
useEffect(fn, [dep]) -> 特定依赖更新时的副作用
3.getDerivedStateFromProps的正确使用场景
- 仅当需要根据props变化被动更新state时使用
- 避免在此方法中触发副作用
4.受控组件与非受控组件
1.核心定义
组件类型 | 数据管理方式 | 控制权 |
---|---|---|
受控组件 (Controlled) | 表单数据由 React 组件状态(state)驱动 | React 完全控制 |
非受控组件 (Uncontrolled) | 表单数据由 DOM 节点自身维护 | DOM 原生控制 |
2.实现原理对比
1.受控组件实现
1function ControlledForm() {
2 const [value, setValue] = useState('');
3
4 const handleSubmit = (e) => {
5 e.preventDefault();
6 console.log('提交值:', value);
7 };
8
9 return (
10 <form onSubmit={handleSubmit}>
11 <input
12 type="text"
13 value={value}
14 onChange={(e) => setValue(e.target.value)}
15 />
16 <button type="submit">提交</button>
17 </form>
18 );
19}
核心特点
- value绑定到React state
- onChange同步更新state
- 数据流:
React state -> DOM
显示
2.非受控组件实现
1function UncontrolledForm() {
2 const inputRef = useRef(null);
3
4 const handleSubmit = (e) => {
5 e.preventDefault();
6 console.log('提交值:', inputRef.current.value);
7 };
8
9 return (
10 <form onSubmit={handleSubmit}>
11 <input
12 type="text"
13 defaultValue="初始值"
14 ref={inputRef}
15 />
16 <button type="submit">提交</button>
17 </form>
18 );
19}
核心特点
- 使用ref访问DOM节点值
- defaultValue设置初始值(非动态更新)
- 数据流:DOM节点 -> 手动获取值
3.核心差异分析
维度 | 受控组件 | 非受控组件 |
---|---|---|
数据存储位置 | React 组件状态 | DOM 节点 |
值更新机制 | 通过 onChange 事件同步更新 state | 用户输入直接修改 DOM,需手动获取 |
表单验证时机 | 实时验证(每次输入触发) | 提交时验证 |
动态表单控制 | 支持动态禁用/启用字段 | 需要手动操作 DOM |
性能影响 | 高频输入场景可能引发多次渲染 | 无额外渲染开销 |
文件上传支持 | 不支持(文件输入天生不可控) | 必须使用 |
4.最佳实践场景
受控组件适用场景
- 实时表单验证(如密码强度提示)
- 条件禁用提交按钮
- 动态表单字段(根据输入增减表单项)
- 复杂表单联动(多个输入相互依赖)
非受控组件适用场景
- 一次性表单提交(只需要最终值)
- 文件上传
<input type="file">
- 第三方库集成(需要直接操作DOM)
5.进阶
1.为什么文件输入必须用非受控组件?
1<input type="file" onChange={handleFile} />
- 浏览安全限制:JavaScript无法以编程方式设置文件输入的值
- 只读属性:文件路径由用户选择,无法通过React state控制
2.如何给非受控组件设置初始值?
- 使用
defaultValue/defaultChecked
属性(类似原生HTML)
1<input type="text" defaultValue="初始值" ref={inputRef} />
3.受控组件性能优化策略
- 防抖处理(避免高频触发渲染)
1const debouncedSetValue = useMemo(() =>
2 _.debounce(setValue, 300), []
3);
4<input onChange={e => debouncedSetValue(e.target.value)} />
- 精细化渲染控制:使用React.memo隔离表单组件
6.混用模式
1function HybridInput({ value: propValue, onChange }) {
2 const [internalValue, setInternalValue] = useState(propValue);
3 const ref = useRef();
4
5
6 useEffect(() => {
7 if (ref.current.value !== propValue) {
8 ref.current.value = propValue;
9 setInternalValue(propValue);
10 }
11 }, [propValue]);
12
13 const handleChange = (e) => {
14 setInternalValue(e.target.value);
15 onChange?.(e.target.value);
16 };
17
18 return <input ref={ref} value={internalValue} onChange={handleChange} />;
19}
7.高频面试题
1.如何避免受控组件的value变成undefined?
- 确保value的值始终为受控值(字符串/数字),避免null或undefined
2.受控组件中如何实现文本域(textarea)的换行符保留
- 使用
value={ text.replace(/\n/g, '\\n') }
处理,展示时转换回\n
3.为什么非受控组件不需要onChange处理?
- 非受控组件的数据流是单向的(DOM -> React),仅在需要时通过ref获取值
8.决策流程图
1是否需要实时验证/控制? → 是 → 使用受控组件
2 ↓
3 否
4 ↓
5是否涉及文件上传? → 是 → 使用非受控组件
6 ↓
7 否
8 ↓
9是否性能要求极高? → 是 → 使用非受控组件
10 ↓
11 否
12 ↓
13默认推荐 → 受控组件
14
5.类组件和函数式组件
1.核心定义与语法对比
维度 | 类组件 | 函数式组件 |
---|---|---|
定义方式 | ES6 类继承 React.Component | JavaScript 函数 |
状态管理 | this.state + this.setState() | useState /useReducer Hooks |
生命周期 | 完整生命周期方法(如 componentDidMount ) | useEffect 模拟生命周期 |
副作用处理 | 生命周期方法(如 componentDidUpdate ) | useEffect + 依赖数组 |
代码量 | 较冗长(需处理 this 绑定) | 更简洁 |
2.生命周期与Hooks映射关系
类组件生命周期方法
1class Example extends React.Component {
2 componentDidMount() { }
3 componentDidUpdate() { }
4 componentWillUnmount() { }
5 shouldComponentUpdate() { }
6}
函数式组件等效实现
1function Example() {
2
3 useEffect(() => {
4
5 return () => { };
6 }, []);
7
8
9 useEffect(() => { });
10
11
12 const memoizedComponent = React.memo(() => (), (prevProps, nextProps) => {
13 return shallowEqual(prevProps, nextProps);
14 });
15}
3.状态管理对比
类组件状态管理
1class Counter extends React.Component {
2 constructor(props) {
3 super(props);
4 this.state = { count: 0 };
5 }
6
7 increment = () => {
8 this.setState(prev => ({ count: prev.count + 1 }));
9 };
10}
函数式组件状态管理
1function Counter() {
2 const [count, setCount] = useState(0);
3 const increment = useCallback(() => {
4 setCount(prev => prev + 1);
5 }, []);
6}
4.性能优化策略
优化手段 | 类组件 | 函数式组件 |
---|---|---|
浅比较控制渲染 | PureComponent 或 shouldComponentUpdate | React.memo + 自定义比较函数 |
计算缓存 | 手动缓存计算结果 | useMemo |
函数引用稳定性 | 箭头函数或 bind | useCallback |
渲染节流 | 手动实现防抖/节流 | useDebounce 自定义 Hook |
5.代码组织与逻辑复用
类组件复用
- 高阶组件(HOC)
1const withLogger = WrappedComponent => {
2 return class extend React.Component {
3 componentDidMount(){
4 console.log('component mounted');
5 }
6 render() {
7 return <WrappedComponent {...this.props} />
8 }
9 }
10}
函数式组件复用方式
- 自定义Hooks
1const useLogger = () => {
2 useEffect(() => {
3 console.log('Component mounted');
4 }, []);
5};
6
7function MyComponent() {
8 useLogger();
9 return <div>...</div>;
10}
6.关键差异深度解析
1.this绑定问题(类组件)
1class Button extends React.Component {
2 handleClick() {
3 console.log(this);
4 }
5
6
7
8
9}
2.闭包陷阱(函数式组件)
1function Timer() {
2 const [count, setCount] = useState(0);
3
4 useEffect(() => {
5 const timer = setInterval(() => {
6
7 setCount(count + 1);
8 }, 1000);
9 return () => clearInterval(timer);
10 }, []);
11
12
13 useEffect(() => {
14 const timer = setInterval(() => {
15 setCount(prev => prev + 1);
16 }, 1000);
17 return () => clearInterval(timer);
18 }, []);
19}
7.现代React开发趋势
1.Hooks的统治地位
- 官方推荐函数式组件+Hooks作为主要开发模式
- React18新特性(如并发模式)优先支持Hooks
2.类组件使用场景
- 旧项目维护
- 需要
Error Boundaries
(函数式组件暂不支持)
1class ErrorBoundary extends React.Component {
2 state = { hasError: false };
3 static getDerivedStateFromError(error) {
4 return { hasError: true };
5 }
6 componentDidCatch(error, info) { }
7 render() { }
8}
8.高频面试题
1.为什么推荐使用函数式组件?
- 代码简洁:避免this绑定和类语法冗余
- 逻辑复用:自定义Hooks比HOC更灵活
- 性能优化:Hooks提供更细颗粒度的控制(如useMemo)
- 未来兼容:新特性(如并发模式)优先支持Hooks
2.如何选择组件类型?
- 新项目:100%函数式组件+Hooks
- 旧项目:逐步迁移至函数式组件
- 特殊需求:需要getSnapshotBeforeUpdate或componentDidCatch时使用类组件
3.Hooks的限制和突破
- 规则:只能在函数顶层调用Hooks
- 原理:依赖调用顺序的链表结构记录状态
- 解决方案:使用eslint-plugin-react-hooks强制规范
9.核心对比表
维度 | 类组件优势 | 函数式组件优势 |
---|---|---|
代码可读性 | 生命周期逻辑集中 | 逻辑按功能聚合(Hooks 分组) |
学习曲线 | 需掌握 OOP 概念 | 纯函数思维更符合 JavaScript 习惯 |
TypeScript 支持 | 类型推断较复杂 | 类型推导更直观 |
测试友好度 | 需处理实例方法 | 纯函数更易单元测试 |
未来维护性 | 官方逐步弱化支持 | 新特性优先适配 |
6.props和state
1.核心定义对比
维度 | props | state |
---|---|---|
数据来源 | 外部传入(父组件 → 子组件) | 组件内部维护 |
可变性 | 只读(Immutable) | 可修改(通过 setState 或 useState ) |
作用范围 | 跨组件层级传递 | 组件私有,外部不可访问 |
更新触发 | 父组件重新渲染时传递新 props | 调用状态更新方法触发重新渲染 |
2.使用场景
1.何时用props?
- 组件通信:父组件向子组件传递数据或回调函数
1<UserProfile name="Alice" age={25} onUpdate={handleUpdate} />
2
3
4function UserProfile({ name, age, onUpdate }) { ... }
- 配置参数:定义数组的默认行为或样式
1<Button type="primary" size="large" />
- 渲染控制:通过props条件性渲染子组件
1<Modal visible={showModal} />
2.何时用state
- 用户交互时:表单输入、按钮点击等
1const [inputValue, setInputValue] = useState('');
- 组件私有数据:计时器ID、动画状态等
1const [timeId, setTimeId] = useState(null);
- 动态UI状态:下拉菜单展开/收起、加载状态
1const [isOpen, setIsOpen] = useState(false);
3.深度对比与交互
1.数据流方向
- 单项数据流:props只能自上而下传递,state仅在组件内部流动
1父组件 → props → 子组件
2子组件 → 回调函数 → 父组件更新 state → 传递新 props
2.更新机制对比
更新方式 | props | state |
---|---|---|
类组件 | 父组件重新渲染时自动更新 | this.setState() 触发异步更新 |
函数组件 | 父组件重新渲染时自动更新 | useState 的 setter 函数触发更新 |
更新影响 | 子组件接收新 props 后重新渲染 | 当前组件及其子组件重新渲染 |
3.数据交互示例
1function Parent() {
2 const [count, setCount] = useState(0);
3 return <Child count={count} onIncrement={() => setCount(c => c + 1)} />;
4}
5
6
7function Child({ count, onIncrement }) {
8 return (
9 <div>
10 <span>{count}</span>
11 <button onClick={onIncrement}>+</button>
12 </div>
13 );
14}
4.高级使用模式
1.状态提升
当多个组件需要共享状态的时候,将state提升到最近的共同祖先
1function App() {
2 const [theme, setTheme] = useState('light');
3 return (
4 <>
5 <Header theme={theme} />
6 <Content theme={theme} onToggle={() => setTheme(t => t === 'light' ? 'dark' : 'light')} />
7 </>
8 );
9}
2.受控组件
表单的值由props或state控制
1<input
2 value={inputValue}
3 onChange={(e) => setInputValue(e.target.value)}
4/>
3.非受控组件
使用ref直接访问DOM节点值(避免与state发生冲突)
1const inputRef = useRef();
2
3const handleSubmit = () => console.log(inputRef.current.value);
4return <input ref={inputRef} defaultValue="" />;
5.开发注意事项
1.props不可变性原则
- 禁止修改props
1function User({ user }) {
2 user.name = 'Bob';
3 return <div>{user.name}</div>;
4}
2.state更新陷阱
- 异步更新问题
1setCount(count + 1);
2setCount(count + 1);
3
4
5setCount(prev => prev + 1);
6setCount(prev => prev + 1);
3.性能优化
- 避免不必要的渲染
1const MemoComponent = React.memo(MyComponent);
2
3
4shouldComponentUpdate(nextProps, nextState) {
5 return !shallowEqual(this.props, nextProps);
6}
六、高频面试题
1.能否在子组件修改props?
- 不能。props是只读的,修改会导致React抛出警告。若需要修改数据,应通过父组件传递回调函数更新父级state
2.props和state可以相互转换吗?
- 可以,例如父组件的state作为props传递给子组件,或子组件通过回调函数触发父组件更新
3.如何实现跨多层组件传递props
- 使用
Context API
或状态管理库(如Redux、Mobx
)避免props逐层传递
个人笔记记录 2021 ~ 2025