React 17 新特性

React 17 的主要变化和升级要点

问题

React 17 带来了哪些改变?

解答

React 17 是一个”垫脚石”版本,没有新增面向开发者的功能,主要为未来的渐进式升级做准备。

1. 新的 JSX 转换

不再需要手动引入 React:

// React 16 及之前
import React from 'react';

function App() {
  return <div>Hello</div>;
}

// React 17 之后
// 无需引入 React,编译器自动处理
function App() {
  return <div>Hello</div>;
}

编译后的代码变化:

// 旧转换(React.createElement)
import React from 'react';
function App() {
  return React.createElement('div', null, 'Hello');
}

// 新转换(jsx-runtime)
import { jsx as _jsx } from 'react/jsx-runtime';
function App() {
  return _jsx('div', { children: 'Hello' });
}

2. 事件委托变更

事件不再挂载到 document,而是挂载到 React 根容器:

const rootNode = document.getElementById('root');
ReactDOM.render(<App />, rootNode);

// React 16: 事件挂载到 document
// React 17: 事件挂载到 rootNode

这解决了多个 React 版本共存时的事件冲突问题:

// React 16 的问题
document.addEventListener('click', (e) => {
  e.stopPropagation(); // 会阻止所有 React 事件
});

// React 17 不受影响,因为事件在根容器上

3. 移除事件池

// React 16: 事件对象会被复用,异步访问需要 e.persist()
function handleClick(e) {
  console.log(e.type); // 'click'
  
  setTimeout(() => {
    console.log(e.type); // null(事件对象已被清空)
  }, 100);
}

// 需要这样写
function handleClick(e) {
  e.persist(); // 保留事件对象
  setTimeout(() => {
    console.log(e.type); // 'click'
  }, 100);
}

// React 17: 直接访问即可
function handleClick(e) {
  setTimeout(() => {
    console.log(e.type); // 'click'
  }, 100);
}

4. useEffect 清理函数异步执行

useEffect(() => {
  // 副作用逻辑
  
  return () => {
    // React 16: 同步执行清理
    // React 17: 异步执行清理(屏幕更新后)
  };
}, []);

5. 渐进式升级支持

允许页面中同时运行多个 React 版本:

// 主应用使用 React 17
import ReactDOM from 'react-dom';
ReactDOM.render(<App />, document.getElementById('root'));

// 某个独立模块可以使用 React 18
import { createRoot } from 'react-dom/client';
const root = createRoot(document.getElementById('widget'));
root.render(<Widget />);

6. 更好的错误堆栈

组件堆栈信息更完整,可以点击跳转到源码位置:

Warning: Each child in a list should have a unique "key" prop.
    at Item (http://localhost:3000/src/Item.js:5:3)
    at List (http://localhost:3000/src/List.js:10:5)
    at App (http://localhost:3000/src/App.js:8:3)

关键点

  • JSX 转换:无需手动 import React,编译器自动注入
  • 事件委托:从 document 改为根容器,支持多版本共存
  • 事件池移除:异步访问事件对象不再需要 e.persist()
  • 清理函数异步化useEffect 清理在屏幕更新后执行
  • 渐进式升级:为 React 18 及未来版本的平滑迁移铺路