一、基础概念

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.生命周期废弃原因与最佳实践

废弃方法替代方案废弃原因
componentWillMountconstructor 或 useEffect异步渲染导致可能多次执行
componentWillReceivePropsgetDerivedStateFromProps + useEffect容易产生副作用和竞态条件
componentWillUpdategetSnapshotBeforeUpdate不安全副作用操作风险

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.最佳实践场景

受控组件适用场景
  1. 实时表单验证(如密码强度提示)
  2. 条件禁用提交按钮
  3. 动态表单字段(根据输入增减表单项)
  4. 复杂表单联动(多个输入相互依赖)
非受控组件适用场景
  1. 一次性表单提交(只需要最终值)
  2. 文件上传<input type="file">
  3. 第三方库集成(需要直接操作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.ComponentJavaScript 函数
状态管理this.state + this.setState()useState/useReducer Hooks
生命周期完整生命周期方法(如 componentDidMountuseEffect 模拟生命周期
副作用处理生命周期方法(如 componentDidUpdateuseEffect + 依赖数组
代码量较冗长(需处理 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 或 shouldComponentUpdateReact.memo + 自定义比较函数
计算缓存手动缓存计算结果useMemo
函数引用稳定性箭头函数或 binduseCallback
渲染节流手动实现防抖/节流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.核心定义对比

维度propsstate
数据来源外部传入(父组件 → 子组件)组件内部维护
可变性只读(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.更新机制对比
更新方式propsstate
类组件父组件重新渲染时自动更新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