重新学习前端之React

1 阅读23分钟

React

一、JSX 语法

1. JSX 是什么?本质是什么?

定义: JSX(JavaScript XML)是 JavaScript 的一种语法扩展,允许在 JavaScript 代码中编写类似 HTML 的标记语法。

原理:

  • JSX 本身不能被浏览器直接识别,需要通过 Babel 等编译器转换为普通的 JavaScript 代码
  • 转换后的代码实际上是 React.createElement() 函数调用
  • JSX 是语法糖,不是模板引擎,最终会生成虚拟 DOM 对象

代码示例:

// JSX 语法
const element = (
  <div className="container">
    <h1>Hello, {name}</h1>
    <p>Welcome to React</p>
  </div>
);

// Babel 编译后的等价代码
const element = React.createElement(
  'div',
  { className: 'container' },
  React.createElement('h1', null, `Hello, ${name}`),
  React.createElement('p', null, 'Welcome to React')
);

JSX 生成真实 DOM 的完整流程:

JSX 语法
  ↓ Babel 编译
React.createElement() 调用
  ↓ 执行
虚拟 DOM 对象(JavaScript 对象)
  ↓ ReactDOM.render()
真实 DOM 节点

JSX 编译规则:

  • 小写字母开头的标签:编译为原生 DOM 元素(如 div, span
  • 大写字母开头的标签:编译为自定义组件(如 <MyComponent />

常见误区:

  • ❌ JSX 是模板引擎 → ✅ JSX 是 JavaScript 的语法扩展
  • ❌ JSX 只能在 React 中使用 → ✅ JSX 可以被其他库使用,但 React 最常见
  • ❌ JSX 会被浏览器直接解析 → ✅ JSX 必须经过编译才能被浏览器执行

2. JSX 与原生 JS 的区别

对比表格:

特性JSX原生 JS
语法类似 HTML 的声明式语法函数调用的命令式语法
可读性高,结构清晰低,嵌套复杂
编译需要 Babel 转换直接执行
表达式使用 {} 嵌入 JS 表达式直接使用变量和函数
属性名使用 camelCase(如 className使用原生属性名

代码对比:

// JSX 写法
const component = (
  <div className="app">
    <h1 style={{ color: 'red' }}>{title}</h1>
    <button onClick={handleClick}>Click</button>
  </div>
);

// 原生 JS 写法
const component = React.createElement(
  'div',
  { className: 'app' },
  React.createElement(
    'h1',
    { style: { color: 'red' } },
    title
  ),
  React.createElement(
    'button',
    { onClick: handleClick },
    'Click'
  )
);

3. JSX 的优点

  1. 声明式编程:以声明方式描述 UI,代码更直观
  2. 类型安全:编译时可检测错误(配合 TypeScript)
  3. 防止注入攻击:默认转义嵌入值,防止 XSS
  4. 代码提示:IDE 提供更好的智能提示
  5. 逻辑与视图结合:将渲染逻辑与 UI 放在一起,更易维护

4. JSX 的注意事项

  1. 必须有一个根元素(或使用 Fragment)
  2. 属性名使用 camelCaseclassName 代替 classhtmlFor 代替 for
  3. 自闭合标签必须以 /> 结尾
  4. 表达式用 {} 包裹,不能使用语句
  5. 样式对象:使用双大括号 {{ }},属性名用 camelCase
  6. 注释:使用 {/* 注释内容 */}
// ✅ 正确示例
const Component = () => (
  <>
    {/* 这是注释 */}
    <div className="container">
      <h1>{user.name}</h1>
      <img src={user.avatar} alt="avatar" />
      <input type="text" onChange={handleChange} />
    </div>
  </>
);

// ❌ 错误示例
const Wrong = () => (
  <div>
    <p>变量: {let x = 1;}</p>  // 不能使用语句
    <p class="text">{text}</p>  // 应该用 className
    <br>  // 必须自闭合
  </div>
);

5. JSX 条件渲染

实现方式:

// 1. if-else 语句
function Greeting({ isLoggedIn }) {
  if (isLoggedIn) {
    return <h1>Welcome back!</h1>;
  }
  return <h1>Please sign in.</h1>;
}

// 2. 三元运算符
const element = (
  <div>
    {isLoggedIn ? <UserPanel /> : <LoginForm />}
  </div>
);

// 3. 逻辑与运算符 &&
const element = (
  <div>
    {messages.length > 0 && <MessageList messages={messages} />}
  </div>
);

// 4. 立即执行函数
const element = (
  <div>
    {(() => {
      switch (status) {
        case 'loading': return <Loading />;
        case 'success': return <Content />;
        case 'error': return <Error />;
        default: return <NotFound />;
      }
    })()}
  </div>
);

常见误区:

// ❌ 0 会被渲染出来
const List = ({ items }) => (
  <div>
    {items.length && <ul>{items.map(item => <li key={item.id}>{item.name}</li>)}</ul>}
  </div>
);
// 当 items.length 为 0 时,会渲染出 "0"

// ✅ 正确写法
const List = ({ items }) => (
  <div>
    {items.length > 0 && <ul>{items.map(item => <li key={item.id}>{item.name}</li>)}</ul>}
  </div>
);

6. JSX 列表渲染

const TodoList = ({ todos }) => (
  <ul>
    {todos.map((todo, index) => (
      <li key={todo.id || index}>
        <span>{todo.title}</span>
        {todo.completed && <span className="done"></span>}
      </li>
    ))}
  </ul>
);

// 提取组件的写法
const TodoItem = ({ todo }) => (
  <li>
    <span>{todo.title}</span>
    {todo.completed && <span className="done"></span>}
  </li>
);

const TodoList = ({ todos }) => (
  <ul>
    {todos.map(todo => (
      <TodoItem key={todo.id} todo={todo} />
    ))}
  </ul>
);

7. JSX 样式处理

// 1. 内联样式
const styles = {
  container: {
    display: 'flex',
    justifyContent: 'center',
    padding: '20px',
    backgroundColor: '#f5f5f5'
  },
  title: {
    fontSize: '24px',
    fontWeight: 'bold'
  }
};

const Component = () => (
  <div style={styles.container}>
    <h1 style={styles.title}>Hello</h1>
  </div>
);

// 2. CSS 类名
import './Component.css';

const Component = () => (
  <div className="container">
    <h1 className="title">Hello</h1>
  </div>
);

// 3. 条件类名
import classNames from 'classnames';

const Button = ({ variant, size, disabled }) => (
  <button 
    className={classNames(
      'btn',
      `btn-${variant}`,
      `btn-${size}`,
      { 'btn-disabled': disabled }
    )}
  >
    Click
  </button>
);

8. JSX 属性绑定

const Component = () => {
  const imgSrc = '/path/to/image.jpg';
  const isDisabled = true;
  const linkUrl = 'https://example.com';
  const customData = { id: 1, name: 'test' };

  return (
    <div>
      {/* 字符串属性 */}
      <img src={imgSrc} alt="description" />
      
      {/* 布尔属性 */}
      <button disabled={isDisabled}>Submit</button>
      
      {/* 动态属性 */}
      <a href={linkUrl} target="_blank" rel="noopener noreferrer">
        Link
      </a>
      
      {/* data-* 属性 */}
      <div data-testid="custom-element" data-info={JSON.stringify(customData)}>
        Content
      </div>
      
      {/* 展开属性 */}
      <input {...{ type: 'text', placeholder: 'Enter name' }} />
    </div>
  );
};

二、组件与 Props

9. React 组件是什么?

定义: React 组件是可复用的 UI 构建块,接收输入(props)并返回 React 元素。

核心特性:

  1. 可复用:同一组件可在多处使用
  2. 可组合:小组件组合成大组件
  3. 独立:每个组件管理自己的状态和渲染逻辑
  4. 单向数据流:数据从父组件流向子组件

两种定义方式:

// 1. 函数组件(推荐)
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

// ES6 箭头函数
const Welcome = ({ name }) => <h1>Hello, {name}</h1>;

// 2. 类组件
class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

10. 函数组件与类组件的区别

对比表格:

特性函数组件类组件
语法简单的 JavaScript 函数ES6 class,需要继承 React.Component
状态管理useState Hookthis.state + setState
生命周期useEffect 等 HookscomponentDidMount 等生命周期方法
this 绑定无 this 问题需要处理 this 绑定
性能稍好,无需实例化需要实例化,有额外的性能开销
代码量通常更少相对较多
逻辑复用自定义 Hooks高阶组件、render props
推荐度⭐⭐⭐⭐⭐ 推荐⭐⭐ 不推荐(遗留代码)

详细对比:

// 函数组件 - 简洁
function Counter() {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    document.title = `Count: ${count}`;
  }, [count]);

  return (
    <button onClick={() => setCount(count + 1)}>
      Count: {count}
    </button>
  );
}

// 类组件 - 繁琐
class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
    this.handleClick = this.handleClick.bind(this);
  }

  componentDidMount() {
    document.title = `Count: ${this.state.count}`;
  }

  componentDidUpdate() {
    document.title = `Count: ${this.state.count}`;
  }

  handleClick() {
    this.setState({ count: this.state.count + 1 });
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        Count: {this.state.count}
      </button>
    );
  }
}

选择策略:

  • ✅ 新项目全部使用函数组件 + Hooks
  • ✅ 旧项目逐步迁移类组件到函数组件
  • ❌ 不再在新代码中使用类组件

11. Props 的使用

定义: Props(properties)是组件间传递数据的机制,从父组件向子组件单向传递。

使用示例:

// 子组件
function UserProfile({ name, age, email, hobbies }) {
  return (
    <div>
      <h2>{name}</h2>
      <p>Age: {age}</p>
      <p>Email: {email}</p>
      <ul>
        {hobbies.map(hobby => <li key={hobby}>{hobby}</li>)}
      </ul>
    </div>
  );
}

// 父组件
function App() {
  return (
    <UserProfile 
      name="John"
      age={30}
      email="john@example.com"
      hobbies={['reading', 'gaming', 'coding']}
    />
  );
}

Props 默认值:

// 方式 1:defaultProps(类组件和函数组件)
function Greeting({ name }) {
  return <h1>Hello, {name}!</h1>;
}
Greeting.defaultProps = {
  name: 'Guest'
};

// 方式 2:解构默认值(函数组件推荐)
function Greeting({ name = 'Guest' }) {
  return <h1>Hello, {name}!</h1>;
}

Props 验证(PropTypes):

import PropTypes from 'prop-types';

function UserCard({ name, age, email, isActive }) {
  return (
    <div className={isActive ? 'active' : 'inactive'}>
      <h3>{name}</h3>
      <p>Age: {age}</p>
      <p>Email: {email}</p>
    </div>
  );
}

UserCard.propTypes = {
  name: PropTypes.string.isRequired,
  age: PropTypes.number,
  email: PropTypes.string.isRequired,
  isActive: PropTypes.bool
};

UserCard.defaultProps = {
  age: 0,
  isActive: false
};

12. Props 只读性

核心原则: Props 是只读的,组件不能修改自身的 props。

原理:

  • React 遵循单向数据流,数据从父组件流向子组件
  • 子组件只能通过回调函数通知父组件更新数据
  • 直接修改 props 会导致不可预测的行为
// ❌ 错误:直接修改 props
function BadComponent({ count }) {
  count = count + 1; // 无效且会导致错误
  return <p>{count}</p>;
}

// ✅ 正确:通过 state 管理可变数据
function GoodComponent({ initialCount }) {
  const [count, setCount] = useState(initialCount);
  return (
    <p>
      {count}
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </p>
  );
}

13. 组件通信方式

完整通信方案对比:

通信方式适用场景复杂度
Props + 回调父子组件简单
Context API跨层级组件中等
状态管理(Redux)全局状态复杂
事件总线任意组件中等
Refs直接操作子组件谨慎使用
父子组件通信
// 父传子:通过 Props
function Parent() {
  const [message, setMessage] = useState('Hello from Parent');
  
  return <Child message={message} />;
}

function Child({ message }) {
  return <p>{message}</p>;
}

// 子传父:通过回调函数
function Parent() {
  const [childData, setChildData] = useState('');
  
  const handleChildData = (data) => {
    setChildData(data);
  };
  
  return (
    <div>
      <p>From Child: {childData}</p>
      <Child onSendData={handleChildData} />
    </div>
  );
}

function Child({ onSendData }) {
  const sendData = () => {
    onSendData('Hello from Child');
  };
  
  return <button onClick={sendData}>Send to Parent</button>;
}
兄弟组件通信
// 通过父组件状态中转
function Parent() {
  const [sharedData, setSharedData] = useState('');

  return (
    <div>
      <SiblingA onDataChange={setSharedData} />
      <SiblingB data={sharedData} />
    </div>
  );
}

function SiblingA({ onDataChange }) {
  return (
    <input 
      onChange={(e) => onDataChange(e.target.value)}
      placeholder="Type here"
    />
  );
}

function SiblingB({ data }) {
  return <p>Sibling A typed: {data}</p>;
}
跨层级组件通信(Context)
// 创建 Context
const ThemeContext = React.createContext('light');

// 提供值
function App() {
  const [theme, setTheme] = useState('dark');
  
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      <ComponentA />
    </ThemeContext.Provider>
  );
}

// 消费值
function ComponentC() {
  const { theme, setTheme } = useContext(ThemeContext);
  return (
    <div className={theme}>
      <p>Current theme: {theme}</p>
      <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
        Toggle Theme
      </button>
    </div>
  );
}

三、State 与生命周期

14. State 是什么?与 Props 的区别

定义: State 是组件内部的可变数据,用于管理组件的动态行为和 UI 状态。

对比表格:

特性StateProps
来源组件内部定义由父组件传递
可变性组件自身可修改只读,不可修改
作用域组件内部从父到子传递
修改方式setState / useState父组件重新渲染时更新
默认值组件初始化时设置父组件传递或 defaultProps
控制权组件自身父组件

代码对比:

// State 示例
function Counter() {
  const [count, setCount] = useState(0); // 内部状态
  return (
    <button onClick={() => setCount(count + 1)}>
      Count: {count}
    </button>
  );
}

// Props 示例
function Display({ value }) { // 外部传入
  return <p>Value: {value}</p>;
}

15. setState 的原理与使用注意事项

原理:

  1. setState 是异步的(在 React 控制的事件处理中)
  2. React 会将多次 setState 调用批量合并
  3. 状态更新后触发组件重新渲染
  4. 使用虚拟 DOM diff 算法计算最小更新

同步与异步场景:

function Example() {
  const [count, setCount] = useState(0);

  // 异步场景:React 控制的事件处理中
  const handleClick = () => {
    setCount(count + 1);
    console.log(count); // 输出旧值 0
  };

  // 同步场景:原生事件、setTimeout 中
  useEffect(() => {
    const timer = setTimeout(() => {
      setCount(count + 1);
      console.log(count); // React 18 后也是批量更新
    }, 1000);
    return () => clearTimeout(timer);
  }, [count]);

  return <button onClick={handleClick}>{count}</button>;
}

React 18 的变化:Automatic Batching

  • React 18 之前:只在 React 事件处理中批量更新
  • React 18 之后:所有场景(setTimeout、Promise、原生事件)都批量更新
  • 如需同步更新:使用 flushSync
import { flushSync } from 'react-dom';

const handleClick = () => {
  flushSync(() => {
    setCount(count + 1);
  });
  console.log(count); // 输出新值
};

setState 合并机制:

// 对象合并:后面的覆盖前面的
const handleClick = () => {
  setState({ a: 1 });
  setState({ b: 2 });
  // 结果:{ a: 1, b: 2 }(在批量更新中)
};

// 函数式更新:避免依赖旧状态的问题
const handleClick = () => {
  setCount(prev => prev + 1);
  setCount(prev => prev + 1);
  setCount(prev => prev + 1);
  // 结果:count + 3
};

setState 回调:

// 类组件中 setState 的回调
this.setState({ count: 1 }, () => {
  console.log('State updated:', this.state.count);
});

// 函数组件中使用 useEffect 替代
useEffect(() => {
  console.log('Count updated:', count);
}, [count]);

16. React 生命周期(类组件)

生命周期流程图:

挂载阶段(Mount)
  ↓
constructor()
  ↓
getDerivedStateFromProps()
  ↓
render()
  ↓
componentDidMount()
  ↓
更新阶段(Update)
  ↓
getDerivedStateFromProps()
  ↓
shouldComponentUpdate()
  ↓
render()
  ↓
getSnapshotBeforeUpdate()
  ↓
componentDidUpdate()
  ↓
卸载阶段(Unmount)
  ↓
componentWillUnmount()
挂载阶段
class Component extends React.Component {
  // 1. 构造函数:初始化 state 和绑定方法
  constructor(props) {
    super(props);
    this.state = { count: 0 };
    this.handleClick = this.handleClick.bind(this);
  }

  // 2. 静态方法:根据 props 更新 state
  static getDerivedStateFromProps(props, state) {
    if (props.value !== state.prevValue) {
      return { prevValue: props.value, count: 0 };
    }
    return null; // 不需要更新 state
  }

  // 3. render:必须实现,返回 React 元素
  render() {
    return <div>{this.state.count}</div>;
  }

  // 4. 组件挂载完成:发送请求、订阅、操作 DOM
  componentDidMount() {
    console.log('Component mounted');
    this.fetchData();
  }
}
更新阶段
class Component extends React.Component {
  // 1. 性能优化:返回 false 阻止渲染
  shouldComponentUpdate(nextProps, nextState) {
    if (nextProps.id === this.props.id) {
      return false; // props.id 未变化,不更新
    }
    return true;
  }

  // 2. 渲染前获取 DOM 快照
  getSnapshotBeforeUpdate(prevProps, prevState) {
    if (prevProps.list.length < this.props.list.length) {
      // 记录滚动位置
      return this.listRef.current.scrollTop;
    }
    return null;
  }

  // 3. 更新完成后执行:接收 snapshot 参数
  componentDidUpdate(prevProps, prevState, snapshot) {
    if (snapshot !== null) {
      // 恢复滚动位置
      this.listRef.current.scrollTop = snapshot;
    }
  }
}
卸载阶段
class Component extends React.Component {
  componentWillUnmount() {
    // 清理工作
    clearInterval(this.timer);
    this.subscription.unsubscribe();
    window.removeEventListener('resize', this.handleResize);
  }
}

React 16.3 前后生命周期变化:

废弃方法替代方案
componentWillMountcomponentDidMount 或 constructor
componentWillReceivePropsgetDerivedStateFromProps
componentWillUpdategetSnapshotBeforeUpdate

17. Hooks 与生命周期的对应关系

// useEffect 对应生命周期
useEffect(() => {
  // componentDidMount 和 componentDidUpdate
  console.log('Component mounted/updated');
  
  return () => {
    // componentWillUnmount
    console.log('Component will unmount');
  };
}, []); // 空数组:只执行一次(相当于 componentDidMount)

// 对应关系表格
// componentDidMount     → useEffect(() => {}, [])
// componentDidUpdate    → useEffect(() => {}, [dependency])
// componentWillUnmount  → useEffect(() => { return cleanup }, [])

四、事件处理

18. React 事件处理

定义: React 实现了跨浏览器的合成事件系统(SyntheticEvent),提供一致的 API。

特点:

  1. 事件名使用 camelCase(如 onClick
  2. 事件处理函数是函数引用,不是字符串
  3. 使用 preventDefault() 阻止默认行为,不能返回 false
function EventExample() {
  const handleClick = (e) => {
    e.preventDefault(); // 阻止默认行为
    console.log('Clicked!');
  };

  return (
    <div>
      {/* 正确写法 */}
      <button onClick={handleClick}>Click Me</button>
      
      {/* 错误写法 */}
      {/* <button onclick="handleClick()">Click</button> */}
    </div>
  );
}

19. 合成事件与原生事件的区别

对比表格:

特性合成事件(SyntheticEvent)原生事件
兼容性跨浏览器一致不同浏览器可能不同
事件委托统一委托到 document直接绑定到元素
性能事件复用,减少内存每个事件独立创建
API遵循 W3C 规范遵循浏览器实现
事件对象SyntheticEvent原生 Event
异步访问React 17 前需要 persist()始终可用

事件委托机制:

DOM 树:
document
  └── div#root
        └── div.app
              └── button (onClick)

React 事件绑定:
所有事件统一绑定到 documentroot 容器
事件触发时,React 根据冒泡机制分发到对应组件

20. 事件传参

// 方式 1:箭头函数
function List({ items }) {
  const handleClick = (id) => {
    console.log('Delete item:', id);
  };

  return (
    <ul>
      {items.map(item => (
        <li key={item.id}>
          <button onClick={() => handleClick(item.id)}>
            Delete
          </button>
        </li>
      ))}
    </ul>
  );
}

// 方式 2:bind 方法
function List({ items }) {
  const handleClick = (id, e) => {
    console.log('Delete item:', id);
  };

  return (
    <ul>
      {items.map(item => (
        <li key={item.id}>
          <button onClick={handleClick.bind(this, item.id)}>
            Delete
          </button>
        </li>
      ))}
    </ul>
  );
}

// 方式 3:data 属性
function List({ items }) {
  const handleClick = (e) => {
    const id = e.currentTarget.dataset.id;
    console.log('Delete item:', id);
  };

  return (
    <ul>
      {items.map(item => (
        <li key={item.id}>
          <button 
            onClick={handleClick}
            data-id={item.id}
          >
            Delete
          </button>
        </li>
      ))}
    </ul>
  );
}

21. 事件池(Event Pooling)

说明: React 17 已移除了事件池,此问题主要针对 React 16 及更早版本。

React 16 的问题:

// React 16 中,事件对象会被复用
function handleClick(e) {
  console.log(e.target); // 可用
  
  setTimeout(() => {
    console.log(e.target); // undefined!事件对象已被回收
  }, 0);
}

// 解决方案 1:persist()
function handleClick(e) {
  e.persist();
  setTimeout(() => {
    console.log(e.target); // 可用
  }, 0);
}

// 解决方案 2:提取需要的值
function handleClick(e) {
  const target = e.target;
  setTimeout(() => {
    console.log(target); // 可用
  }, 0);
}

五、列表与 Keys

22. Key 的作用与最佳实践

定义: key 是 React 用于识别列表中哪些元素发生变化的特殊属性。

核心作用:

  1. 帮助 Diff 算法高效识别元素
  2. 保持组件状态与元素对应
  3. 避免不必要的重新渲染
// ✅ 正确:使用唯一 ID
const TodoList = ({ todos }) => (
  <ul>
    {todos.map(todo => (
      <TodoItem key={todo.id} todo={todo} />
    ))}
  </ul>
);

// ❌ 错误:使用 index 作为 key
const TodoList = ({ todos }) => (
  <ul>
    {todos.map((todo, index) => (
      <TodoItem key={index} todo={todo} />
    ))}
  </ul>
);

为什么不能随机数作为 key?

// ❌ 错误:每次渲染都生成新的 key
const List = ({ items }) => (
  <ul>
    {items.map(item => (
      <li key={Math.random()}>{item.name}</li>
    ))}
  </ul>
);
// 导致每次渲染都重新创建所有 DOM 节点

23. Index 作为 Key 的问题

问题场景:

// 初始渲染
const items = ['A', 'B', 'C'];
// key=0: A, key=1: B, key=2: C

// 在开头插入 'X' 后
const items = ['X', 'A', 'B', 'C'];
// key=0: X, key=1: A, key=2: B, key=3: C

问题分析:

  1. 所有 key 都发生了变化,导致全部重新渲染
  2. 如果有输入框等状态组件,状态会错乱
  3. 性能下降,失去了 key 的优化作用

何时可以使用 Index?

  • 列表是静态的,不会发生变化
  • 列表项没有输入框等状态
  • 仅作为展示,不需要维护状态

六、表单处理

24. 受控组件

定义: 表单元素的值由 React state 控制,每次输入都触发 state 更新。

function ControlledForm() {
  const [formData, setFormData] = useState({
    username: '',
    email: '',
    agree: false
  });

  const handleChange = (e) => {
    const { name, value, type, checked } = e.target;
    setFormData(prev => ({
      ...prev,
      [name]: type === 'checkbox' ? checked : value
    }));
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('Form data:', formData);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        name="username"
        value={formData.username}
        onChange={handleChange}
        placeholder="Username"
      />
      <input
        name="email"
        type="email"
        value={formData.email}
        onChange={handleChange}
        placeholder="Email"
      />
      <label>
        <input
          name="agree"
          type="checkbox"
          checked={formData.agree}
          onChange={handleChange}
        />
        I agree to the terms
      </label>
      <button type="submit">Submit</button>
    </form>
  );
}

25. 非受控组件

定义: 表单元素的值由 DOM 自身管理,通过 ref 获取值。

function UncontrolledForm() {
  const nameRef = useRef(null);
  const emailRef = useRef(null);
  const fileInputRef = useRef(null);

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('Name:', nameRef.current.value);
    console.log('Email:', emailRef.current.value);
    console.log('File:', fileInputRef.current.files[0]);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input ref={nameRef} defaultValue="John" placeholder="Name" />
      <input ref={emailRef} type="email" placeholder="Email" />
      <input ref={fileInputRef} type="file" />
      <button type="submit">Submit</button>
    </form>
  );
}

26. 受控组件与非受控组件对比

对比表格:

特性受控组件非受控组件
数据源React stateDOM 自身
值更新每次输入都更新 stateDOM 内部管理
获取值直接读 state通过 ref 获取
即时验证容易实现较难实现
性能频繁渲染可能影响性能性能更好
适用场景需要即时验证、禁用提交按钮简单表单、文件上传

选择策略:

  • 大多数表单使用受控组件
  • 文件上传必须使用非受控组件
  • 简单表单可使用非受控组件减少渲染

27. 表单验证

function ValidatedForm() {
  const [formData, setFormData] = useState({
    email: '',
    password: ''
  });
  const [errors, setErrors] = useState({});

  const validate = () => {
    const newErrors = {};
    if (!formData.email) {
      newErrors.email = 'Email is required';
    } else if (!/\S+@\S+\.\S+/.test(formData.email)) {
      newErrors.email = 'Email is invalid';
    }
    if (!formData.password) {
      newErrors.password = 'Password is required';
    } else if (formData.password.length < 6) {
      newErrors.password = 'Password must be at least 6 characters';
    }
    setErrors(newErrors);
    return Object.keys(newErrors).length === 0;
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    if (validate()) {
      console.log('Form submitted:', formData);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <input
          name="email"
          value={formData.email}
          onChange={(e) => setFormData({ ...formData, email: e.target.value })}
          placeholder="Email"
        />
        {errors.email && <span className="error">{errors.email}</span>}
      </div>
      <div>
        <input
          name="password"
          type="password"
          value={formData.password}
          onChange={(e) => setFormData({ ...formData, password: e.target.value })}
          placeholder="Password"
        />
        {errors.password && <span className="error">{errors.password}</span>}
      </div>
      <button type="submit">Submit</button>
    </form>
  );
}

七、Context API

28. Context API 使用

定义: Context 提供了跨组件传递数据的方式,无需逐层传递 props。

创建和使用:

// 1. 创建 Context
const ThemeContext = React.createContext('light');

// 2. 提供值(Provider)
function App() {
  const [theme, setTheme] = useState('dark');
  
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

// 3. 消费值 - 方式 1:useContext Hook(推荐)
function ThemedButton() {
  const { theme, setTheme } = useContext(ThemeContext);
  
  return (
    <button 
      className={theme}
      onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
    >
      Toggle Theme
    </button>
  );
}

// 4. 消费值 - 方式 2:Consumer 组件
function ThemedButton() {
  return (
    <ThemeContext.Consumer>
      {({ theme, setTheme }) => (
        <button 
          className={theme}
          onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
        >
          Toggle Theme
        </button>
      )}
    </ThemeContext.Consumer>
  );
}

// 5. 消费值 - 方式 3:classType.contextType
class ThemedButton extends React.Component {
  static contextType = ThemeContext;
  
  render() {
    const { theme, setTheme } = this.context;
    return <button className={theme}>Toggle</button>;
  }
}

29. Context 的应用场景

适用场景:

  1. 主题切换(Theme)
  2. 用户认证信息(User/Auth)
  3. 语言国际化(i18n)
  4. 全局配置

不适用场景:

  • 高频更新的状态(会导致所有消费者重新渲染)
  • 组件间频繁交互的数据

30. Context 的性能问题与优化

问题: Context value 变化时,所有消费者组件都会重新渲染。

优化方案:

// 1. 拆分 Context
const ThemeContext = React.createContext();
const UserContext = React.createContext();

// 2. 使用 useMemo 避免不必要的更新
function App() {
  const [theme, setTheme] = useState('light');
  const [user, setUser] = useState(null);

  const themeValue = useMemo(() => ({ theme, setTheme }), [theme]);
  const userValue = useMemo(() => ({ user, setUser }), [user]);

  return (
    <ThemeContext.Provider value={themeValue}>
      <UserContext.Provider value={userValue}>
        <Content />
      </UserContext.Provider>
    </ThemeContext.Provider>
  );
}

// 3. 使用 Context Selector 模式
function useThemeSelector(selector) {
  const context = useContext(ThemeContext);
  return selector(context);
}

function ThemedText() {
  const theme = useThemeSelector(ctx => ctx.theme);
  return <p className={theme}>Hello</p>;
}

八、React Router

31. React Router 基础

定义: React Router 是 React 生态中最流行的路由库,支持声明式路由导航。

核心组件:

import { BrowserRouter, Routes, Route, Link, Navigate } from 'react-router-dom';

function App() {
  return (
    <BrowserRouter>
      <nav>
        <Link to="/">Home</Link>
        <Link to="/about">About</Link>
        <Link to="/users/123">User</Link>
      </nav>

      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/users/:id" element={<User />} />
        <Route path="/protected" element={<ProtectedRoute />} />
        <Route path="*" element={<Navigate to="/" replace />} />
      </Routes>
    </BrowserRouter>
  );
}

32. BrowserRouter 与 HashRouter 对比

对比表格:

特性BrowserRouterHashRouter
URL 格式/about/#/about
History API使用 HTML5 History API使用 URL hash
服务器配置需要配置不需要
SEO友好不友好
兼容性IE9+所有浏览器
适用场景生产环境静态文件托管

33. 路由传参

// 1. 动态路由参数
// 定义
<Route path="/users/:id" element={<User />} />

// 使用
function User() {
  const { id } = useParams();
  return <h2>User ID: {id}</h2>;
}

// 导航
<Link to={`/users/${userId}`}>View User</Link>

// 2. 查询参数(Search Params)
// 导航
<Link to="/search?keyword=react&page=1">Search</Link>

// 使用
function Search() {
  const [searchParams] = useSearchParams();
  const keyword = searchParams.get('keyword');
  const page = searchParams.get('page');
  return <p>Searching for: {keyword}</p>;
}

// 3. 状态参数(State)
// 导航
navigate('/detail', { state: { from: 'home', data: item } });

// 使用
function Detail() {
  const location = useLocation();
  const { from, data } = location.state || {};
  return <p>Came from: {from}</p>;
}

34. 路由守卫

// 认证路由守卫
function ProtectedRoute({ children }) {
  const { isAuthenticated } = useAuth();
  const location = useLocation();

  if (!isAuthenticated) {
    return <Navigate to="/login" state={{ from: location }} replace />;
  }

  return children;
}

// 使用
<Route 
  path="/dashboard" 
  element={
    <ProtectedRoute>
      <Dashboard />
    </ProtectedRoute>
  } 
/>

35. React Router v5 到 v6 的变化

主要变化:

v5v6
<Switch><Routes>
<Route component={X} /><Route element={<X />} />
useHistory()useNavigate()
history.push()navigate()
exact prop默认精确匹配
<Route path=":id">保持不变
嵌套路由复杂原生支持嵌套路由
// v5
<Switch>
  <Route exact path="/" component={Home} />
  <Route path="/users/:id" component={User} />
</Switch>
const history = useHistory();
history.push('/about');

// v6
<Routes>
  <Route path="/" element={<Home />} />
  <Route path="/users/:id" element={<User />} />
</Routes>
const navigate = useNavigate();
navigate('/about');

九、状态管理(Redux、MobX)

36. Redux 核心概念

定义: Redux 是一个可预测的状态管理容器,基于 Flux 架构思想。

三大原则:

  1. 单一数据源:整个应用的 state 存储在一棵 object tree 中
  2. State 是只读的:唯一改变 state 的方法是触发 action
  3. 使用纯函数执行修改:reducers 是纯函数

核心概念:

Action:描述发生了什么的普通对象
  ↓
Dispatch:触发 action 的方法
  ↓
Reducer:纯函数,根据 action 更新 state
  ↓
Store:存储 state 的容器
  ↓
Subscribe:监听 state 变化
  ↓
View:根据 state 渲染 UI

37. Redux 工作流程

// 1. 定义 Action Types
const ADD_TODO = 'ADD_TODO';
const TOGGLE_TODO = 'TOGGLE_TODO';

// 2. 创建 Actions
const addTodo = (text) => ({
  type: ADD_TODO,
  payload: { id: Date.now(), text, completed: false }
});

// 3. 创建 Reducer
const initialState = { todos: [] };

function todoReducer(state = initialState, action) {
  switch (action.type) {
    case ADD_TODO:
      return {
        ...state,
        todos: [...state.todos, action.payload]
      };
    case TOGGLE_TODO:
      return {
        ...state,
        todos: state.todos.map(todo =>
          todo.id === action.payload.id
            ? { ...todo, completed: !todo.completed }
            : todo
        )
      };
    default:
      return state;
  }
}

// 4. 创建 Store
import { configureStore } from '@reduxjs/toolkit';

const store = configureStore({
  reducer: {
    todos: todoReducer
  }
});

// 5. 在组件中使用
import { useSelector, useDispatch } from 'react-redux';

function TodoList() {
  const todos = useSelector(state => state.todos.todos);
  const dispatch = useDispatch();

  return (
    <div>
      {todos.map(todo => (
        <div key={todo.id}>
          <span>{todo.text}</span>
          <button onClick={() => dispatch(addTodo('New Todo'))}>
            Add
          </button>
        </div>
      ))}
    </div>
  );
}

38. Redux 中间件

定义: 中间件位于 dispatch 和 reducer 之间,用于处理副作用。

常用中间件:

中间件用途特点
redux-thunk处理异步 action简单,适合简单异步
redux-saga处理复杂异步流程强大,使用 Generator
redux-observable响应式异步处理使用 RxJS

Redux Thunk 示例:

// Action Creator 返回函数
const fetchUser = (userId) => {
  return async (dispatch, getState) => {
    dispatch({ type: 'FETCH_USER_REQUEST' });
    try {
      const response = await fetch(`/api/users/${userId}`);
      const user = await response.json();
      dispatch({ type: 'FETCH_USER_SUCCESS', payload: user });
    } catch (error) {
      dispatch({ type: 'FETCH_USER_FAILURE', payload: error.message });
    }
  };
};

// 使用
dispatch(fetchUser(123));

Redux Saga 示例:

import { call, put, takeLatest } from 'redux-saga/effects';

function* fetchUserSaga(action) {
  try {
    const user = yield call(fetch, `/api/users/${action.payload}`);
    yield put({ type: 'FETCH_USER_SUCCESS', payload: user });
  } catch (error) {
    yield put({ type: 'FETCH_USER_FAILURE', payload: error.message });
  }
}

function* watchFetchUser() {
  yield takeLatest('FETCH_USER_REQUEST', fetchUserSaga);
}

39. Redux Toolkit(推荐用法)

import { createSlice, configureStore } from '@reduxjs/toolkit';

// 创建 slice
const counterSlice = createSlice({
  name: 'counter',
  initialState: { value: 0 },
  reducers: {
    increment: (state) => {
      state.value += 1; // 使用 immer,可以"修改"state
    },
    decrement: (state) => {
      state.value -= 1;
    },
    incrementByAmount: (state, action) => {
      state.value += action.payload;
    }
  }
});

export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;

// 创建 store
const store = configureStore({
  reducer: {
    counter: counterSlice.reducer
  }
});

40. MobX 与 Redux 对比

对比表格:

特性ReduxMobX
编程范式命令式、函数式声明式、响应式
数据流单向数据流响应式数据流
状态修改通过 dispatch action直接修改 observable
学习曲线较陡峭平缓
样板代码较多较少
DevTools强大良好
适用场景大型项目、复杂状态中小型项目、简单状态
性能优化手动优化自动追踪依赖

MobX 示例:

import { makeObservable, observable, action, computed } from 'mobx';
import { observer } from 'mobx-react-lite';

class TodoStore {
  todos = [];

  constructor() {
    makeObservable(this, {
      todos: observable,
      addTodo: action,
      toggleTodo: action,
      completedCount: computed
    });
  }

  addTodo(text) {
    this.todos.push({ id: Date.now(), text, completed: false });
  }

  toggleTodo(id) {
    const todo = this.todos.find(t => t.id === id);
    if (todo) todo.completed = !todo.completed;
  }

  get completedCount() {
    return this.todos.filter(t => t.completed).length;
  }
}

const todoStore = new TodoStore();

const TodoList = observer(() => (
  <div>
    <button onClick={() => todoStore.addTodo('New Todo')}>Add</button>
    <p>Completed: {todoStore.completedCount}</p>
  </div>
));

十、React Hooks

41. useState

定义: useState 是 React 最常用的 Hook,用于在函数组件中添加状态。

// 基本用法
function Counter() {
  const [count, setCount] = useState(0);
  return (
    <button onClick={() => setCount(count + 1)}>
      Count: {count}
    </button>
  );
}

// 复杂状态
function UserForm() {
  const [form, setForm] = useState({
    name: '',
    email: '',
    age: 0
  });

  const handleChange = (e) => {
    const { name, value } = e.target;
    setForm(prev => ({
      ...prev,
      [name]: value
    }));
  };

  return (
    <form>
      <input name="name" value={form.name} onChange={handleChange} />
      <input name="email" value={form.email} onChange={handleChange} />
    </form>
  );
}

// 函数式初始化(只执行一次)
function ExpensiveComponent({ initialId }) {
  const [data, setData] = useState(() => {
    return computeExpensiveValue(initialId);
  });
}

常见误区:

// ❌ 错误:直接修改 state
const [arr, setArr] = useState([]);
arr.push(1); // 不会触发重新渲染

// ✅ 正确:创建新对象
setArr([...arr, 1]);
setArr(prev => [...prev, 1]);

42. useEffect

定义: useEffect 用于处理副作用(数据获取、订阅、手动 DOM 操作等)。

// 1. 无依赖数组:每次渲染后执行
useEffect(() => {
  console.log('每次渲染后执行');
});

// 2. 空依赖数组:只在挂载和卸载时执行
useEffect(() => {
  console.log('挂载时执行');
  return () => {
    console.log('卸载时执行');
  };
}, []);

// 3. 有依赖数组:依赖变化时执行
useEffect(() => {
  console.log('count 变化时执行:', count);
}, [count]);

// 4. 异步数据获取
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    let isMounted = true; // 防止内存泄漏

    const fetchUser = async () => {
      try {
        const response = await fetch(`/api/users/${userId}`);
        const data = await response.json();
        if (isMounted) {
          setUser(data);
        }
      } catch (error) {
        console.error('Failed to fetch user:', error);
      }
    };

    fetchUser();

    return () => {
      isMounted = false; // 清理函数
    };
  }, [userId]);

  return user ? <div>{user.name}</div> : <p>Loading...</p>;
}

常见陷阱:

// ❌ 错误:遗漏依赖
useEffect(() => {
  console.log(count);
}, []); // count 变化时不会更新

// ✅ 正确:添加依赖
useEffect(() => {
  console.log(count);
}, [count]);

// ❌ 错误:无限循环
useEffect(() => {
  setCount(count + 1);
}); // 没有依赖数组

// ✅ 正确:使用条件
useEffect(() => {
  if (count < 10) {
    setCount(count + 1);
  }
}, [count]);

43. useCallback 与 useMemo

定义:

  • useCallback:记忆函数,避免每次渲染创建新函数
  • useMemo:记忆值,避免重复计算
// useCallback - 记忆函数
function TodoList({ todos, onToggle }) {
  const handleToggle = useCallback((id) => {
    onToggle(id);
  }, [onToggle]);

  return todos.map(todo => (
    <TodoItem key={todo.id} todo={todo} onToggle={handleToggle} />
  ));
}

// useMemo - 记忆计算结果
function ExpensiveComponent({ items, filter }) {
  const filteredItems = useMemo(() => {
    console.log('Filtering items...');
    return items.filter(item => item.type === filter);
  }, [items, filter]);

  return (
    <ul>
      {filteredItems.map(item => <li key={item.id}>{item.name}</li>)}
    </ul>
  );
}

对比表格:

Hook返回值用途何时使用
useCallback函数避免子组件不必要的渲染传递给优化过的子组件的回调
useMemo避免重复计算计算密集型操作
// 等价关系
useCallback(fn, deps) === useMemo(() => fn, deps)

44. useRef

定义: useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数。

用途:

  1. 访问 DOM 元素
  2. 保存不触发重新渲染的值
// 1. 访问 DOM 元素
function TextInput() {
  const inputRef = useRef(null);

  const focusInput = () => {
    inputRef.current.focus();
  };

  return (
    <>
      <input ref={inputRef} type="text" />
      <button onClick={focusInput}>Focus</button>
    </>
  );
}

// 2. 保存可变值(不触发渲染)
function Timer() {
  const [count, setCount] = useState(0);
  const intervalRef = useRef(null);

  const startTimer = () => {
    intervalRef.current = setInterval(() => {
      setCount(c => c + 1);
    }, 1000);
  };

  const stopTimer = () => {
    clearInterval(intervalRef.current);
  };

  useEffect(() => {
    return () => clearInterval(intervalRef.current);
  }, []);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={startTimer}>Start</button>
      <button onClick={stopTimer}>Stop</button>
    </div>
  );
}

45. useReducer

定义: useReducer 是 useState 的替代方案,适用于复杂状态逻辑。

function todoReducer(state, action) {
  switch (action.type) {
    case 'ADD':
      return [...state, { id: Date.now(), text: action.payload, completed: false }];
    case 'TOGGLE':
      return state.map(todo =>
        todo.id === action.payload
          ? { ...todo, completed: !todo.completed }
          : todo
      );
    case 'DELETE':
      return state.filter(todo => todo.id !== action.payload);
    default:
      return state;
  }
}

function TodoApp() {
  const [todos, dispatch] = useReducer(todoReducer, []);
  const [input, setInput] = useState('');

  const handleSubmit = (e) => {
    e.preventDefault();
    if (input.trim()) {
      dispatch({ type: 'ADD', payload: input });
      setInput('');
    }
  };

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <input value={input} onChange={e => setInput(e.target.value)} />
        <button type="submit">Add</button>
      </form>
      <ul>
        {todos.map(todo => (
          <li key={todo.id} onClick={() => dispatch({ type: 'TOGGLE', payload: todo.id })}>
            {todo.text} {todo.completed && '✓'}
          </li>
        ))}
      </ul>
    </div>
  );
}

46. useLayoutEffect

定义: useLayoutEffect 与 useEffect 类似,但在所有 DOM 变更之后同步执行。

与 useEffect 的区别:

特性useEffectuseLayoutEffect
执行时机渲染后异步执行DOM 变更后同步执行
阻塞渲染
适用场景数据获取、订阅测量 DOM、同步更新 DOM
// useLayoutEffect 用于测量 DOM
function Tooltip({ target }) {
  const [position, setPosition] = useState({ top: 0, left: 0 });
  const tooltipRef = useRef(null);

  useLayoutEffect(() => {
    if (tooltipRef.current && target) {
      const rect = target.getBoundingClientRect();
      setPosition({
        top: rect.top - 10,
        left: rect.left + rect.width / 2
      });
    }
  }, [target]);

  return (
    <div ref={tooltipRef} style={{ position: 'absolute', ...position }}>
      Tooltip content
    </div>
  );
}

47. 自定义 Hook

定义: 自定义 Hook 是提取组件间共享逻辑的函数,以 use 开头。

// 自定义 Hook:获取窗口尺寸
function useWindowSize() {
  const [size, setSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight
  });

  useEffect(() => {
    const handleResize = () => {
      setSize({
        width: window.innerWidth,
        height: window.innerHeight
      });
    };

    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);

  return size;
}

// 自定义 Hook:数据请求
function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        setLoading(true);
        const response = await fetch(url);
        const json = await response.json();
        setData(json);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, [url]);

  return { data, loading, error };
}

// 使用
function UserProfile({ userId }) {
  const { data, loading, error } = useFetch(`/api/users/${userId}`);
  const { width } = useWindowSize();

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error}</p>;

  return (
    <div>
      <h1>{data.name}</h1>
      <p>Window width: {width}</p>
    </div>
  );
}

48. Hooks 使用规则

两条铁律:

  1. 只在最顶层调用 Hooks:不能在循环、条件或嵌套函数中调用
  2. 只在 React 函数中调用 Hooks:不能在普通 JavaScript 函数中调用
// ❌ 错误:在条件语句中
function Component({ condition }) {
  if (condition) {
    const [state, setState] = useState(0); // 违反规则
  }
}

// ✅ 正确:在最顶层
function Component({ condition }) {
  const [state, setState] = useState(0);
  
  if (condition) {
    // 使用 state
  }
}

// ❌ 错误:在普通函数中
function regularFunction() {
  const [state, setState] = useState(0); // 违反规则
}

// ✅ 正确:在 React 函数中
function Component() {
  const [state, setState] = useState(0);
}

原理: React 通过调用顺序来关联 Hook 和组件,顺序必须保持一致。


49. React 18 新增 Hooks

// useTransition:标记非紧急更新
function Search() {
  const [inputValue, setInputValue] = useState('');
  const [results, setResults] = useState([]);
  const [isPending, startTransition] = useTransition();

  const handleChange = (e) => {
    setInputValue(e.target.value);
    startTransition(() => {
      // 非紧急更新:过滤列表
      setResults(filterResults(e.target.value));
    });
  };

  return (
    <div>
      <input value={inputValue} onChange={handleChange} />
      {isPending && <p>Loading...</p>}
      <ul>{results.map(r => <li key={r.id}>{r.name}</li>)}</ul>
    </div>
  );
}

// useDeferredValue:延迟更新值
function Search({ query }) {
  const deferredQuery = useDeferredValue(query);
  
  return (
    <div>
      <SearchResults query={deferredQuery} />
    </div>
  );
}

// useId:生成唯一 ID(用于 SSR hydration)
function Form() {
  const id = useId();
  return (
    <div>
      <label htmlFor={id}>Name</label>
      <input id={id} type="text" />
    </div>
  );
}

// useSyncExternalStore:同步外部数据源
function useOnlineStatus() {
  return useSyncExternalStore(
    (callback) => {
      window.addEventListener('online', callback);
      window.addEventListener('offline', callback);
      return () => {
        window.removeEventListener('online', callback);
        window.removeEventListener('offline', callback);
      };
    },
    () => navigator.onLine,
    () => true // SSR 时的默认值
  );
}

十一、React 原理

50. Virtual DOM

定义: Virtual DOM 是真实 DOM 的轻量级 JavaScript 对象表示。

结构:

// JSX
<div className="app">
  <h1>Hello</h1>
</div>

// Virtual DOM 对象
{
  type: 'div',
  props: {
    className: 'app',
    children: [
      {
        type: 'h1',
        props: {
          children: 'Hello'
        }
      }
    ]
  }
}

优势:

  1. 减少 DOM 操作:批量更新,减少重排重绘
  2. 跨平台:可以渲染到不同平台(Web、Native、Canvas)
  3. 可预测:数据驱动视图
  4. Diff 算法优化:高效计算最小更新

工作流程:

State 变化
  ↓
创建新的 Virtual DOM 树
  ↓
与旧的 Virtual DOM 树进行 Diff
  ↓
计算最小 DOM 操作
  ↓
批量更新真实 DOM

51. Diff 算法

三大策略:

  1. Tree Diff:同层级比较,不跨级比较
  2. Component Diff:相同组件类型复用,不同组件类型替换整棵树
  3. Element Diff:通过 key 识别同层级元素
// Tree Diff
<div>                    <div>
  <Header /><p>  // 直接替换整个子树
</div>                   </div>

// Component Diff
<Component />     →      <Component />  // 复用组件实例
<Header />        →      <Footer />     // 替换组件

// Element Diff(列表)
<li key="A">A</li>       <li key="B">B</li>
<li key="B">B</li>   →   <li key="A">A</li>
                         <li key="C">C</li>
// 通过 key 识别,移动 A 和 B,插入 C

Diff 算法流程:

旧节点 A          新节点 A'
    ↓                ↓
比较类型是否相同
    ↓
  ┌─相同─┐      ┌─不同─┐
  ↓              ↓
复用节点        替换节点
  ↓              ↓
更新 props      删除旧节点
  ↓              创建新节点
比较 children
  ↓
遍历子节点,递归 Diff

52. Fiber 架构

定义: Fiber 是 React 16 引入的新协调引擎,实现了异步可中断的渲染。

为什么需要 Fiber?

React 15 的问题:

  • 递归更新是同步的,不能中断
  • 大量组件更新会导致长时间阻塞主线程
  • 造成掉帧、卡顿

Fiber 的解决方案:

  • 将渲染工作拆分成小单元
  • 可以暂停、恢复、重用或丢弃工作
  • 根据优先级调度任务

Fiber 节点结构:

function FiberNode(tag, pendingProps, key, mode) {
  // 实例相关
  this.tag = tag;
  this.key = key;
  this.type = null;
  this.stateNode = null;

  // Fiber 树相关
  this.return = null;      // 父节点
  this.child = null;       // 子节点
  this.sibling = null;     // 兄弟节点
  this.index = 0;

  // 更新相关
  this.pendingProps = pendingProps;
  this.memoizedProps = null;
  this.updateQueue = null;
  this.memoizedState = null;

  // 副作用相关
  this.effectTag = NoEffect;
  this.nextEffect = null;
  this.firstEffect = null;
  this.lastEffect = null;

  // 优先级相关
  this.expirationTime = NoWork;
  this.alternate = null;   // 指向另一棵树的对应节点
}

双缓冲机制:

内存中有两棵 Fiber 树:
Current Tree(当前屏幕显示的)
WorkInProgress Tree(正在构建的)

渲染完成后,指针切换:
root.current = workInProgress;

53. 时间切片与优先级调度

时间切片(Time Slicing):

主线程时间线:
|-- 5ms 渲染 --|-- 事件处理 --|-- 5ms 渲染 --|-- 用户输入响应 --|
     切片1          中断          切片2          及时响应

优先级模型:

优先级类型示例
Immediate立即用户交互
UserBlocking用户阻塞输入框
Normal普通数据更新
Low通知
Idle空闲预加载

54. 并发模式

定义: 并发模式允许 React 同时准备多个版本的 UI。

// startTransition 标记非紧急更新
function TabContainer() {
  const [tab, setTab] = useState('home');
  const [isPending, startTransition] = useTransition();

  const switchTab = (newTab) => {
    // 紧急更新:立即切换
    setTab(newTab);
    
    // 非紧急更新:可以中断
    startTransition(() => {
      renderTabContent(newTab);
    });
  };

  return (
    <div>
      {isPending && <Spinner />}
      <TabContent tab={tab} />
    </div>
  );
}

55. React 合成事件原理

事件绑定机制:

React 事件委托到 root 节点(React 17 之前是 document)
  ↓
事件触发时,React 创建 SyntheticEvent 对象
  ↓
根据组件树构建事件路径(捕获 → 目标 → 冒泡)
  ↓
按顺序执行事件处理器
  ↓
事件池回收 SyntheticEventReact 16

React 17 的变化:

  • 事件不再委托到 document,而是委托到 root 容器
  • 多个 React 版本可以共存
  • 移除了事件池

十二、React 性能优化

56. React.memo

定义: React.memo 是高阶组件,用于优化函数组件的渲染。

// 默认浅比较 props
const TodoItem = React.memo(function TodoItem({ todo }) {
  return <li>{todo.text}</li>;
});

// 自定义比较函数
const TodoItem = React.memo(
  function TodoItem({ todo, onToggle }) {
    return <li onClick={() => onToggle(todo.id)}>{todo.text}</li>;
  },
  (prevProps, nextProps) => {
    // 返回 true 表示不需要重新渲染
    return prevProps.todo.id === nextProps.todo.id &&
           prevProps.todo.text === nextProps.todo.text;
  }
);

57. PureComponent

定义: PureComponent 自动对 props 和 state 进行浅比较。

class TodoItem extends React.PureComponent {
  render() {
    return <li>{this.props.todo.text}</li>;
  }
}

// 注意:只进行浅比较
// ❌ 如果父组件传递了新创建的对象/数组,仍会重新渲染
<TodoItem todo={{ ...todo, completed: !todo.completed }} />

58. 避免不必要的渲染

优化策略:

// 1. 使用 React.memo 包裹组件
const ExpensiveComponent = React.memo(({ data }) => {
  return <div>{/* 复杂渲染逻辑 */}</div>;
});

// 2. 使用 useCallback 记忆回调
function Parent() {
  const handleClick = useCallback(() => {
    console.log('clicked');
  }, []);

  return <Child onClick={handleClick} />;
}

// 3. 使用 useMemo 记忆计算结果
function List({ items, filter }) {
  const filteredItems = useMemo(() => 
    items.filter(item => item.type === filter),
    [items, filter]
  );

  return <ul>{filteredItems.map(item => <li key={item.id}>{item.name}</li>)}</ul>;
}

// 4. 状态下沉(State Colocation)
// ❌ 将状态提升到不必要的父组件
function App() {
  const [isOpen, setIsOpen] = useState(false);
  return (
    <div>
      <Modal isOpen={isOpen} />
      <Content /> {/* 不需要知道 isOpen */}
    </div>
  );
}

// ✅ 状态下沉到需要的组件
function ModalWithTrigger() {
  const [isOpen, setIsOpen] = useState(false);
  return (
    <>
      <button onClick={() => setIsOpen(true)}>Open</button>
      <Modal isOpen={isOpen} />
    </>
  );
}

59. 懒加载与代码分割

// React.lazy 配合 Suspense
import { lazy, Suspense } from 'react';

const HeavyComponent = lazy(() => import('./HeavyComponent'));

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <HeavyComponent />
    </Suspense>
  );
}

// 路由级别的代码分割
import { lazy } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';

const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Dashboard = lazy(() => import('./pages/Dashboard'));

function App() {
  return (
    <BrowserRouter>
      <Suspense fallback={<Loading />}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
          <Route path="/dashboard" element={<Dashboard />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
}

60. 列表优化

// 1. 正确的 key 选择
<TodoList todos={todos} />
// 使用唯一 ID 作为 key

// 2. 虚拟列表(长列表优化)
import { FixedSizeList } from 'react-window';

function VirtualList({ items }) {
  const Row = ({ index, style }) => (
    <div style={style}>
      {items[index].name}
    </div>
  );

  return (
    <FixedSizeList
      height={600}
      itemCount={items.length}
      itemSize={35}
      width={300}
    >
      {Row}
    </FixedSizeList>
  );
}

// 3. 分页加载
function PaginatedList() {
  const [items, setItems] = useState([]);
  const [page, setPage] = useState(1);
  const [loading, setLoading] = useState(false);

  const loadMore = async () => {
    if (loading) return;
    setLoading(true);
    const newItems = await fetchItems(page);
    setItems(prev => [...prev, ...newItems]);
    setPage(prev => prev + 1);
    setLoading(false);
  };

  return (
    <div onScroll={handleScroll}>
      {items.map(item => <Item key={item.id} item={item} />)}
      {loading && <p>Loading more...</p>}
    </div>
  );
}

十三、其他 React 特性

61. Refs 与 forwardRef

// 创建 Ref
function TextInput() {
  const inputRef = useRef(null);

  useEffect(() => {
    inputRef.current.focus();
  }, []);

  return <input ref={inputRef} />;
}

// forwardRef:转发 ref 到子组件
const FancyInput = React.forwardRef((props, ref) => (
  <input ref={ref} className="fancy" {...props} />
));

// 使用
function Form() {
  const inputRef = useRef(null);

  return (
    <FancyInput ref={inputRef} placeholder="Type here" />
  );
}

// useImperativeHandle:自定义暴露的方法
const FancyInput = React.forwardRef((props, ref) => {
  const inputRef = useRef(null);

  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    },
    clear: () => {
      inputRef.current.value = '';
    }
  }));

  return <input ref={inputRef} {...props} />;
});

62. Portals

定义: Portals 将子元素渲染到父组件 DOM 层次结构之外的 DOM 节点。

import { createPortal } from 'react-dom';

function Modal({ isOpen, children }) {
  if (!isOpen) return null;

  return createPortal(
    <div className="modal-overlay">
      <div className="modal-content">
        {children}
      </div>
    </div>,
    document.getElementById('modal-root')
  );
}

// 使用
function App() {
  const [showModal, setShowModal] = useState(false);

  return (
    <div>
      <button onClick={() => setShowModal(true)}>Show Modal</button>
      <Modal isOpen={showModal}>
        <h2>Modal Title</h2>
        <p>Modal content</p>
        <button onClick={() => setShowModal(false)}>Close</button>
      </Modal>
    </div>
  );
}

适用场景:

  • 模态框(Modal)
  • 提示框(Tooltip)
  • 下拉菜单(Dropdown)

63. Error Boundaries

定义: Error Boundaries 捕获子组件树中的 JavaScript 错误,显示备用 UI。

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
  }

  static getDerivedStateFromError(error) {
    return { hasError: true, error };
  }

  componentDidCatch(error, errorInfo) {
    console.error('Error caught by boundary:', error, errorInfo);
    // 可以发送到错误监控服务
    logErrorToService(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return (
        <div>
          <h1>Something went wrong.</h1>
          <details>{this.state.error.message}</details>
        </div>
      );
    }

    return this.props.children;
  }
}

// 使用
function App() {
  return (
    <ErrorBoundary>
      <MainContent />
    </ErrorBoundary>
  );
}

// 函数组件中使用(使用 react-error-boundary 库)
import { ErrorBoundary } from 'react-error-boundary';

function App() {
  return (
    <ErrorBoundary
      fallback={<ErrorFallback />}
      onReset={() => window.location.reload()}
    >
      <MainContent />
    </ErrorBoundary>
  );
}

64. Fragment

定义: Fragment 允许在不添加额外 DOM 节点的情况下组合子元素列表。

// 方式 1:完整语法
function Table() {
  return (
    <React.Fragment>
      <th>Header 1</th>
      <th>Header 2</th>
    </React.Fragment>
  );
}

// 方式 2:简写语法(推荐)
function Table() {
  return (
    <>
      <th>Header 1</th>
      <th>Header 2</th>
    </>
  );
}

// 带 key 的 Fragment
function Glossary({ items }) {
  return (
    <dl>
      {items.map(item => (
        <React.Fragment key={item.id}>
          <dt>{item.term}</dt>
          <dd>{item.definition}</dd>
        </React.Fragment>
      ))}
    </dl>
  );
}

65. StrictMode

定义: StrictMode 用于突出显示应用程序中的潜在问题。

检查项:

  1. 识别不安全的生命周期
  2. 警告使用旧版字符串 ref API
  3. 警告使用废弃的 findDOMNode API
  4. 检测意外的副作用
  5. 检测过时的 context API
  6. 确保可复用的 state
function App() {
  return (
    <React.StrictMode>
      <Router>
        <AppContent />
      </Router>
    </React.StrictMode>
  );
}

十四、React 18 新特性

66. Automatic Batching

定义: React 18 自动批处理所有状态更新,包括异步回调中的更新。

// React 18 之前
setTimeout(() => {
  setCount(c => c + 1); // 触发渲染
  setFlag(f => !f);     // 触发渲染
}, 1000);
// 两次渲染

// React 18
setTimeout(() => {
  setCount(c => c + 1);
  setFlag(f => !f);
}, 1000);
// 一次渲染(自动批处理)

// 退出批处理
import { flushSync } from 'react-dom';

flushSync(() => {
  setCount(c => c + 1); // 立即渲染
});
// 此时 count 已更新

67. 服务端组件(Server Components)

定义: 服务端组件在服务器上运行,不发送到客户端。

// 服务器组件(.server.js)
async function Note({ id }) {
  const note = await db.notes.get(id); // 直接访问数据库
  return (
    <div>
      <h1>{note.title}</h1>
      <MarkdownRenderer source={note.body} />
    </div>
  );
}

// 客户端组件(标记 'use client')
'use client';

function Counter() {
  const [count, setCount] = useState(0);
  return (
    <button onClick={() => setCount(c => c + 1)}>
      Count: {count}
    </button>
  );
}

优势:

  • 零 bundle 大小
  • 直接访问后端资源
  • 自动代码分割
  • 流式 SSR

十五、服务端渲染(SSR)

68. SSR 原理与优势

SSR 流程:

客户端请求
  ↓
服务器执行 React 组件
  ↓
生成 HTML 字符串
  ↓
发送 HTML 到客户端
  ↓
客户端 hydration(绑定事件)

优势:

  1. SEO 友好:搜索引擎可以抓取内容
  2. 首屏加载快:用户更快看到内容
  3. 社交分享:社交平台的爬虫可以获取内容
// Next.js 示例
export async function getServerSideProps(context) {
  const data = await fetchData();
  return { props: { data } };
}

function Page({ data }) {
  return <div>{data.title}</div>;
}

export default Page;

十六、React 测试

69. React Testing Library

import { render, screen, fireEvent } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

test('counter increments', async () => {
  render(<Counter />);
  
  const button = screen.getByRole('button', { name: /increment/i });
  const count = screen.getByText(/count: 0/i);
  
  await userEvent.click(button);
  
  expect(screen.getByText(/count: 1/i)).toBeInTheDocument();
});

test('form submission', async () => {
  const handleSubmit = jest.fn();
  render(<LoginForm onSubmit={handleSubmit} />);
  
  await userEvent.type(screen.getByLabelText(/email/i), 'test@example.com');
  await userEvent.type(screen.getByLabelText(/password/i), 'password123');
  await userEvent.click(screen.getByRole('button', { name: /submit/i }));
  
  expect(handleSubmit).toHaveBeenCalledWith({
    email: 'test@example.com',
    password: 'password123'
  });
});

十七、React TypeScript

70. 类型定义

// 组件 Props 类型
interface UserCardProps {
  name: string;
  age: number;
  email?: string;
  hobbies?: string[];
  onClick?: (id: string) => void;
}

const UserCard: React.FC<UserCardProps> = ({ 
  name, 
  age, 
  email,
  hobbies = [],
  onClick 
}) => {
  return (
    <div onClick={() => onClick?.(name)}>
      <h2>{name}</h2>
      <p>Age: {age}</p>
    </div>
  );
};

// Hook 类型
function useFetch<T>(url: string): { data: T | null; loading: boolean; error: string | null } {
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    fetch(url)
      .then(res => res.json())
      .then(data => {
        setData(data);
        setLoading(false);
      })
      .catch(err => {
        setError(err.message);
        setLoading(false);
      });
  }, [url]);

  return { data, loading, error };
}

// Ref 类型
function TextInput() {
  const inputRef = useRef<HTMLInputElement>(null);
  
  const focus = () => {
    inputRef.current?.focus();
  };

  return <input ref={inputRef} />;
}

// 事件类型
function Form() {
  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
  };

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    console.log(e.target.value);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input onChange={handleChange} />
    </form>
  );
}

十八、React vs Vue/Angular 对比

71. React vs Vue

对比表格:

特性ReactVue
类型UI 库渐进式框架
语法JSX模板语法
状态管理Redux/ContextVuex/Pinia
路由React RouterVue Router
响应式手动更新(setState)自动追踪(Proxy)
学习曲线较陡平缓
生态系统大而分散官方维护
性能优秀优秀
适用场景大型应用、跨平台中小型应用、快速开发

72. React vs Angular

特性ReactAngular
类型UI 库完整框架
语言JavaScript/TypeScriptTypeScript
数据绑定单向双向
学习曲线中等陡峭
包大小
适用场景灵活组合企业级应用

十九、高频面试题精选

73. 谈谈你对 React 的看法,以及它的工作原理

看法:

  1. 组件化思想:将 UI 拆分为独立可复用的组件
  2. 声明式编程:描述 UI 应该是什么样子,而不是如何更新
  3. 单向数据流:数据流动可预测,易于调试
  4. 生态系统丰富:大量第三方库和工具

工作原理:

用户操作 → 触发事件 → 更新 State
  ↓
React 检测到 State 变化
  ↓
创建新的 Virtual DOM 树
  ↓
Diff 算法对比新旧树
  ↓
计算最小 DOM 操作
  ↓
Fiber 调度器按优先级执行更新
  ↓
批量更新真实 DOM

74. 组件化开发在 React 中的体现与优势

体现:

  1. 单一职责:每个组件负责一个功能
  2. 可复用:组件可以在多处使用
  3. 可组合:小组件组合成大组件
  4. 独立测试:每个组件可以单独测试

优势:

  • 提高开发效率
  • 便于维护
  • 代码复用
  • 团队协作友好

75. 常见问题解决

1. setState 后立即获取不到最新值

// ❌ 错误
const handleClick = () => {
  setCount(count + 1);
  console.log(count); // 旧值
};

// ✅ 正确:使用 useEffect
useEffect(() => {
  console.log(count); // 新值
}, [count]);

2. 子组件不更新

// 检查点:
// 1. props 是否正确传递
// 2. 是否使用了 React.memo 且比较函数有问题
// 3. state 更新是否创建了新的对象/数组

3. useEffect 无限循环

// ❌ 错误
useEffect(() => {
  setCount(count + 1); // 没有依赖数组
});

// ✅ 正确
useEffect(() => {
  setCount(count + 1);
}, [count]); // 添加依赖