十五分钟读懂React 17 | 🏆 技术专题第六期征文

简介: 作为时下最火的前端框架之一,React每次发版都会带来创新的改变,如React最早提出虚拟DOM、React 16引入fiber架构,再到后来React 16.8提出令人耳目一新的Hooks,这些创新也是很多人推崇React的一个重要原因。然而,到了React 17,rc发布日志上竟然说这次版本最大的特点就是无新特性,从目前来说,这个日志是让很多人失望了。

作为时下最火的前端框架之一,React每次发版都会带来创新的改变,如React最早提出虚拟DOM、React 16引入fiber架构,再到后来React 16.8提出令人耳目一新的Hooks,这些创新也是很多人推崇React的一个重要原因。然而,到了React 17,rc发布日志上竟然说这次版本最大的特点就是无新特性,从目前来说,这个日志是让很多人失望了。


这么多人对这次发版失望,那React 17就真的没什么好说的吗?显然不是,至少我认为不是的,从长远来看,无论是项目角度,还是源码学习角度,作为一个资深reactress,我还是有很多东西要学习的。


首先面对用户的更改,React官网上说的很详细了。如果你是一个React开发者,并且不想永远停留在老版本,想深入了解React 17,想知道新版本对你开发的影响,那接下来我们来聊聊应该从哪些角度。


一、全新的 JSX 转换


React 17以前,React中如果使用JSX,则必须像下面这样导入React,否则会报错,这是因为旧的 JSX 转换会把 JSX 转换为 React.createElement(...) 调用。


import React from 'react';
export default function App(props) {
  return <div>app </div>;
}


网络异常,图片无法展示
|


当然,这并不完美,除了增加了学习成本,还有无法做到的性能优化和简化, 如createElement里还要动态做children的拼接、依赖于React的导入等等。


而React 17带来了改变,可以让我们单独使用 JSX 而无需引入 React。这是因为新的 JSX 转换不会将 JSX 转换为 React.createElement,而是自动从 React 的 package 中引入新的入口函数并调用。另外此次升级不会改变 JSX 语法,旧的 JSX 转换也将继续工作。


二、事件委托的变更


在 React 16 或更早版本中,React 会由于事件委托对大多数事件执行 document.addEventListener()。但是一旦你想要局部使用React,那么React中的事件会影响全局,如下面这个例子,当把React和jQuery一起使用,那么当点击input的时候,document上和React不相关的事件也会被触发,这符合React的预期,但是并不符合用户的预期。


网络异常,图片无法展示
|


令人开心的是,这次的React 17就解决了这个问题~,这次React 不再将事件添加在document 上,而是添加到渲染 React 树的根 DOM 容器中:


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


这种改变不仅方便了局部使用React的项目,还可以用于项目的逐步升级,如一部分使用React 18,另一部分使用React 19,事件是分开的,这样也就不会相互影响。当然这并不是鼓励大家在一个项目中使用多个React版本,而只是作为一种临时处理的过渡~


好了,如果你只是励志做个普通工程师,可以跳到下个小章节看了,如果是Reactress,继续往下看:


下图形象描述了这次的变更,图片来自React官网

react.docschina.org/blog/2020/1…


网络异常,图片无法展示
|


自从其发布以来,React 一直自动进行事件委托。当触发 DOM 事件时,React 会找出调用的组件,然后 React 事件会在组件中向上 “冒泡”。这被称为事件委托。除了在大型应用程序上具有性能优势外,它还使添加类似于 replaying events 这样的新特性变得更加容易。


事件委托,也就是我们通常提到的事件代理机制,这种机制不会把时间处理函数直接绑定在真实的节点上,而是把所有的事件绑定到结构的最外层,使用一个统一的事件监听和处理函数。当组件挂载或卸载时,只是在这个统一的事件监听器上插入或删除一些对象;当事件发生时,首先被这个统一的事件监听器处理,然后在映射表里找到真正的事件处理函数并调用。这样做简化了事件处理和回收机制,效率也有很大提升。


三、事件系统相关更改


除了事件委托这种比较大的更改,事件系统上还发生了一些小的更改,与以往不同,React 17中onScroll 事件不再冒泡,以防止出现常见的混淆


React 的 onFocusonBlur 事件已在底层切换为原生的 focusinfocusout 事件。它们更接近 React 现有行为,有时还会提供额外的信息。


捕获事件(例如,onClickCapture)现在使用的是实际浏览器中的捕获监听器。

这些更改会使 React 与浏览器行为更接近,并提高了互操作性。


注意:


尽管 React 17 底层已将 onFocus 事件从 focus 切换为 focusin,但请注意,这并未影响冒泡行为。在 React 中,onFocus 事件总是冒泡的,在 React 17 中会继续保持,因为通常它是一个更有用的默认值。请参阅 sandbox,以了解为不同特定用例添加不同检查。


四、去除事件池


在React 17 以前,如果想要用异步的方式使用事件e,则必须先调用调用 e.persist() 才可以,这是因为 React 在旧浏览器中重用了不同事件的事件对象,以提高性能,并将所有事件字段在它们之前设置为 null。如下面的例子:


function FunctionComponent(props) {
  const [val, setVal] = useState("");
  const handleChange = e => {
    // setVal(e.target.value);
    // React 17以前,如果想用异步的方式使用事件e,必须要加上下面的e.persist()才可以
    // e.persist();
    // setVal(data => e.target.value);
  };
  return (
    <div className="border">
      <input type="text" value={val} onChange={handleChange} />
    </div>
  );
}


但是这种使用方式有点抽象,经常会让对React不太熟悉的开发者懵掉,但是值得开心的是,React 17 中移除了 “event pooling(事件池)“,因为以前加入事件池的概念是为了提升旧浏览器的性能,对于现代浏览器来说,已经不需要了。因此,上面的代码中不使用e.persist();也能达到预期效果。


五、副作用清理时间


React 17以前,当组件被卸载时,useEffect和useLayoutEffect的清理函数都是同步运行,但是对于大型应用程序来说,这不是理想选择,因为同步会减缓屏幕的过渡(例如,切换标签),因此React 17中的useEffect的清理函数异步执行,也就是说如果要卸载组件,则清理会在屏幕更新后运行。如果你某些情况下你仍然希望依靠同步执行,可以用 useLayoutEffect


当然React 17中的useEffect的清理函数异步执行之后,有一个隐患:


useEffect(() => {
  someRef.current.someSetupMethod();
  return () => {
    someRef.current.someCleanupMethod();
  };
});


问题在于 someRef.current 是可变的,因此在运行清除函数时,它可能已经设置为 null。解决方案是在副作用内部存储会发生变化的值:


useEffect(() => {
  const instance = someRef.current;
  instance.someSetupMethod();
  return () => {
    instance.someCleanupMethod();
  };
});


我们不希望此问题对大家造成影响,我们提供了 eslint-plugin-react-hooks/exhaustive-deps 的 lint 规则(请确保在项目中使用它)会对此情况发出警告。


六、返回一致的 undefined 错误


在 React 16 及更早版本中,返回 undefined 始终是一个错误,当然这是React的预期,但是由于编码错误 ,forwardRefmemo 组件的返回值是undefined的时候没有做为错误,React 17中修复了这个问题。React中要求对于不想进行任何渲染的时候返回 null


七、原生组件栈


在 React 17 中,使用了不同的机制生成组件调用栈,该机制会将它们与常规的原生 JavaScript 调用栈缝合在一起。这使得你可以在生产环境中获得完全符号化的 React 组件调用栈信息。


八、移除私有导出


React 17删除了一些以前暴露给其他项目的 React 内部组件。特别是,React Native for Web 过去常常依赖于事件系统的某些内部组件,但这种依赖关系很脆弱且经常被破坏。


在 React 17 中,这些私有导出已被移除。据我们所知,React Native for Web 是唯一使用它们的项目,它们已经完成了向不依赖那些私有导出函数的其他方法迁移。


九、启发式更新算法更新


React 16开始替换掉了Stack Reconciler,开始使用启发式算法架构的的Fiber Reconciler。那么为什么要发生这个改变呢?


React的killer feature: virtual dom


  • React15.x - Stack Reconciler


  • React16   -  Fiber Reconciler


  • React17   - Fiber Reconciler (进阶版 - 优先级区间)


  1. 为什么需要fiber


对于大型项目,组件树会很大,这个时候递归遍历的成本就会很高,会造成主线程被持续占用,结果就是主线程上的布局、动画等周期性任务就无法立即得到处理,造成视觉上的卡顿,影响用户体验。


  1. 任务分解的意义


解决上面的问题


  1. 增量渲染(把渲染任务拆分成块,匀到多帧)


  1. 更新时能够暂停,终止,复用渲染任务


  1. 给不同类型的更新赋予优先级


  1. 并发方面新的基础能力


  1. 更流畅


网络异常,图片无法展示
|


网络异常,图片无法展示
|


React 17中更新了启发式更新算法,具体表现为曾经用于标记fiber节点更新优先级的expirationTime换成了为lanes,前者为普通数字,而后者则为32位的二进制,了解二进制运算的都比较熟悉了,这种二进制的lanes是可以指定几个优先级的,而不是像以前expirationTime只能标记一个。


之所以做这种改变,原因就是在于expirationTimes模型不能满足IO操作(Suspense),Suspense用法如下:


<React.Suspense fallback={<Loading />}>
   <Content />
</React.Suspense>


🏆 技术专题第六期|谈谈 React 17 的那些事!


相关文章
|
2月前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
219 2
|
2月前
|
Rust 前端开发 JavaScript
前端技术新探索:从React到WebAssembly的高效之路
前端技术新探索:从React到WebAssembly的高效之路
88 1
|
2月前
|
前端开发 JavaScript 开发者
探索现代Web前端技术:React框架入门
【10月更文挑战第9天】 探索现代Web前端技术:React框架入门
|
2月前
|
前端开发 数据安全/隐私保护
前端技术实战:React Hooks 实现表单验证
【10月更文挑战第1天】前端技术实战:React Hooks 实现表单验证
|
7月前
|
开发框架 Dart 前端开发
【Flutter前端技术开发专栏】Flutter与React Native的对比与选择
【4月更文挑战第30天】对比 Flutter(Dart,强类型,Google支持,快速热重载,高性能渲染)与 React Native(JavaScript,庞大生态,热重载,依赖原生渲染),文章讨论了开发语言、生态系统、性能、开发体验、学习曲线、社区支持及项目选择因素。两者各有优势,选择取决于项目需求、团队技能和长期维护考虑。参考文献包括官方文档和性能比较文章。
254 0
【Flutter前端技术开发专栏】Flutter与React Native的对比与选择
|
4月前
|
移动开发 前端开发 JavaScript
使用React Native进行跨平台移动开发:技术探索与实践
【8月更文挑战第10天】React Native以其跨平台、高性能、易学习等优势,在移动开发领域取得了显著的成果。通过合理使用React Native,开发者可以更加高效地开发出高质量、低成本的移动应用。然而,在享受React Native带来的便利的同时,我们也需要关注其潜在的挑战和限制,并通过不断学习和实践来提升我们的开发能力。
|
4月前
|
前端开发 Java Spring
Spring与Angular/React/Vue:当后端大佬遇上前端三杰,会擦出怎样的火花?一场技术的盛宴,你准备好了吗?
【8月更文挑战第31天】Spring框架与Angular、React、Vue等前端框架的集成是现代Web应用开发的核心。通过RESTful API、WebSocket及GraphQL等方式,Spring能与前端框架高效互动,提供快速且功能丰富的应用。RESTful API简单有效,适用于基本数据交互;WebSocket支持实时通信,适合聊天应用和数据监控;GraphQL则提供更精确的数据查询能力。开发者可根据需求选择合适的集成方式,提升用户体验和应用功能。
100 0
|
4月前
|
Java 前端开发 Spring
技术融合新潮流!Vaadin携手Spring Boot、React、Angular,引领Web开发变革,你准备好了吗?
【8月更文挑战第31天】本文探讨了Vaadin与Spring Boot、React及Angular等主流技术栈的最佳融合实践。Vaadin作为现代Java Web框架,与其他技术栈结合能更好地满足复杂应用需求。文中通过示例代码展示了如何在Spring Boot项目中集成Vaadin,以及如何在Vaadin项目中使用React和Angular组件,充分发挥各技术栈的优势,提升开发效率和用户体验。开发者可根据具体需求选择合适的技术组合。
92 0
|
4月前
|
缓存 前端开发 JavaScript
React.memo 与 useMemo 超厉害!深入浅出带你理解记忆化技术,让 React 性能优化更上一层楼!
【8月更文挑战第31天】在React开发中,性能优化至关重要。本文探讨了`React.memo`和`useMemo`两大利器,前者通过避免不必要的组件重渲染提升效率,后者则缓存计算结果,防止重复计算。结合示例代码,文章详细解析了如何运用这两个Hook进行性能优化,并强调了合理选择与谨慎使用的最佳实践,助你轻松掌握高效开发技巧。
106 0
|
4月前
|
前端开发 UED 开发者
React.lazy()与Suspense:实现按需加载的动态组件——深入理解代码分割、提升首屏速度和优化用户体验的关键技术
【8月更文挑战第31天】在现代Web应用中,性能优化至关重要,特别是减少首屏加载时间和提升用户交互体验。React.lazy()和Suspense组件提供了一种优雅的解决方案,允许按需加载组件,仅在需要渲染时加载相应代码块,从而加快页面展示速度。Suspense组件在组件加载期间显示备选内容,确保了平滑的加载过渡。
164 0