2024 React 变天!可编译的 React 马上来了?

大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!

今天看到了 Brad Westfall 一篇文章《React Will Be Compiled》深入解释了为何 React 要引入自动编译来解决缓存问题,觉得非常有意思,特地分享给大家。

1.React 的三个阶段

React 代码重用经历了以下三个阶段:

对于使用类组件的开发者来说,当想要抽象和重用代码时(比如:状态 state、生命周期函数等)第一个想到的就是 HOC(High Order Component) 和 Render Props 等不太理想的模式(继承基类在 React 中也不推荐)。

之所以需要这么做,本质上是因为 React 缺少抽象相关的原语(Primitive),比如:组合(Composition)。 因此,React 团队逐渐将目光从类转向函数组合。

函数组件又称为无状态组件,因为不能拥有状态或其他生命周期。 React 团队将函数组件视为提供代码复用所需原语的一种方式。比如下面示例就是一个函数组件:

import React from 'react';

function App() {
  const greeting = 'Hello Function Component!';

  return 

{greeting}

; } export default App;

2018 年 React 团队引入了 Hooks,定制 Hooks 从此演变为代码重用的原语(Primitive)。通过将所有代码混合到一个函数中可能会为开发者提供组合功能,但这仅仅是一种权衡,因为开发者从此需要考虑缓存(memoize),而类组件选择为开发者抹平了。

在类组件中,render 方法将其代码与其他生命周期方法隔离,这意味着重新渲染不会对不属于 render 函数的代码产生影响。 这可能不是一个设计决策,但更多的是类的工作方式的一个特征,而且也非常重要。

2.React 缓存(Memoized React)

2.1 React.memo 包裹组件还不够

假如创建一个带有 submit 方法的类组件,则该方法不需要 “记忆”,而函数组件却相反:

function App() {
  const [state, setState] = useState()
  function onSubmit() {
    // Submit logic
  }
  return 
}

对于函数组件来说,每次重新渲染时该函数都会重新创建全新的函数,即重新分配内存。 而这在类组件中则不会发生,因为其是一个与 render 阶段完全分开的方法。

接下来一起重构上面的代码,将 form 组件单独抽取出来:

function App() {
  const [state, setState] = useState()
  function onSubmit() {
    // Submit logic
  }
  return 
} const Form = ({onSubmit}) { // ... }

此时,onSubmit 在每次渲染时都会生成一个全新的函数,但这不是问题。

同时,App 的重新渲染也会导致 Form 的重新渲染,因为 onSubmit 属性发生变化了。事实上,这里不论 onSubmit 是否变化,函数组件都会重新渲染,因为父组件重新渲染同时也没有显式指定子组件缓存。

开发者可以用 React.memo() 阻止某些场景下 App 重新渲染时 Form 的重新渲染,比如下面的示例:

const Form = React.memo(({onSubmit}) {
  // ...
})

此时 Form 组件只有在特定 prop 发生变化时才会重新渲染,而不是每次 App 组件重新渲染都会渲染。那么 React 又如何判断 prop 是否变化?

首先介绍下 JavaScript 提供的几种不同的值比较运算:

function sameValueZero(x, y) {
  if (typeof x === "number" && typeof y === "number") {
    // x and y are equal (may be -0 and 0) or they are both NaN
    return x === y || (x !== x && y !== y);
  }
  return x === y;
}

React 依赖于严格的相等性检查(strict equality checks)来了解变量是否发生变化,即使用 === 和 Object.is() 来比较,对于 JS 的基础数据类型没什么问题,因为是比较变量的值。而对于引用类型则不一样,因为实际上比较的是内存分配地址,比如:{} ==={} 始终返回 false。

对于上面的代码,因为函数组件每次都会重新创建新的 onSubmit 方法,因此仅仅通过 React.memo() 方法处理 Form 组件还远远不够,即 Form 组件依然会重新渲染!

2.2 React.memo(fn) + useCallback 避免函数变化还不够

下面继续重构代码,使用 useCallback 包裹 onSubmit 方法:

function App() {
  const [state, setState] = useState()

  const onSubmit = useCallback(() => {
    // Submit logic
  }, [])

  return 
}

以上代码示例使用 useCallback 来缓存 onSubmit 方法,从而防止每次都生成全新的方法。

缓存 onSubmit 通常不是必需的,但是当 Form 被缓存并接收 onSubmit 作为 prop 时则有必要。

2.3 React.memo(fn) + useCallback + 依赖约束

为了验证 Hooks 依赖数组对组件重新渲染的重要性,假如有以下代码:

function App() {
  const [state, setState] = useState()
  const settings = {}
  const onSubmit = useCallback(() => {
    const x = settings.x
    // ...
  }, [])
  // ...
}

settings 对象会在 App 每次渲染中重新创建。这本身并不是问题,但如果你很了解 React,就会知道在这种情况下 linter 会要求将 settings 放入 useCallback 的依赖数组中。

const settings = {}
const onSubmit = useCallback(() => {
  const x = settings.x
  // 当 settings 变化时候重新生成 onSubmit
}, [settings])

那么问题来了,“为什么要重新生成 onSubmit?” 在 React 中有很多情况,当依赖数组改变时,useCallback 和 useMemo 等 Hooks 确实需要重新缓存并为返回值创建一个新值。 linter 只是不知道在当前情况下,开发者实际上永远不希望 onSubmit 重新生成。

请记住,linter 几乎总是正确的,以上示例只是展示 linter 确实会存在和开发者考虑不一致的情况。如果按照 linter 将 settings 放入依赖项数组中,将会发生以下情况:

此时,开发者可以做的就是继续使用 useMemo 来包裹 setting 对象。

总之,函数组件将更多的控制权交给了开发者。为了避免组件的重新渲染,React.memo(fn) 、 useCallback 、依赖约束缓存等轮番上阵,对于初学者来说简直是灾难。

import {useMemo} from 'react';
import {filterTodos} from './utils.js'

export default function TodoList({todos, theme, tab}) {
    // 使用 useMemo
  const visibleTodos = useMemo(
    () => filterTodos(todos, tab),
    [todos, tab]
  );
  return (
    
      

Note: filterTodos is artificially slowed down!

    {visibleTodos.map(todo => (
  • {todo.completed ? {todo.text} : todo.text }
  • ))}
); }

3. 多考虑 React 依赖缓存

React 中处理依赖数组可能会非常麻烦, linter 可能会告诉开发者将内容放入数组中,但实际上开发者可能并不想要这么做,而且通常 linter 又是对的。

依赖数组是一种处理以下事实的方法,比如:所有代码都位于一个重新渲染的函数组件中,并且希望监视变量随时间的变化。 有时甚至会将对象、数组和函数放入依赖数组中,这时候就需要缓存。

function App() {
  const [misc, setMisc] = useState()
  const [darkMode, setDarkMode] = useState(false)
  const options = {darkMode}
  return 
}

function User({options}) {
  useEffect(() => {
    // 获取用户信息
  }, [options])
  // ...
}

当 App 中的 misc 发生变化时 options 也会变化,因此 useEffect 将再次运行,即使 Effect 与 misc 状态无关。 因此,最好将 options 变量包装在 useMemo 中。 当开发者这样做时,linter 会正确地要求将 darkMode 放入依赖项数组中:

const [darkMode, setDarkMode] = useState(false)
const options = useMemo(() => {
  return {darkMode}
//   当 misc 变化时候不会重新生成
}, [darkMode])

4. 聊的更多的 React

4.1 编译版本的 React

这是 JavaScript 一个术语,意味着编写的代码与浏览器中运行的代码不同。

与其他一些现代 JS 框架相比,React 给人的感觉是 “不太好”。 与我而言,“所见即所得” 规则将决定框架在上图的位置。 JSX 意味着 React 在某种程度上被编译了,但开发者编写的其他代码根本没有被 React 编译。

相比之下,Svelte 的编译量非常大,以至于它的创建者将其描述为甚至不再是 JavaScript。 Svelte 实际上更像是一种编程语言,因为编写的内容的语义与转换为 JavaScript 时获得的语义相差甚远。

// App.svelte 文件


Hello {name}!

React 团队宣布,React 将比以前编译的更多,但到底多多少不重要,重要的是为何要编译。

4.2 编译带来自动缓存

React 并没有放弃不变性(Immutability)而转向可观察性(Observability)。

React 仍然会有身份检查和依赖数组。 因此,编译 React 并不会让 React 感觉与其他框架相似,编译 React 是为了自动缓存而避免手动缓存,从而更好的使用函数组件和 Hooks。

总而言之,值得注意的是,自动缓存并不是凭空出现的。 自从黄玄在 React Conf 2021 上提出这个想法以来,已经讨论了 React 中的这一可能性三年了。几年来,其也曾一度成为 React 圈子里 Twitter 上的热门话题。

5.本文总结

本文主要和大家介绍 React 团队最近公告谈到的已编译的 React,即即将发布的 v19 版本,也可能是 2024 年底版本。同时,React v19 也带来了很多功能。因为篇幅问题,关于 编译 React 只是做了一个简短的介绍,但是文末的参考资料提供了大量优秀文档以供学习,如果有兴趣可以自行阅读。如果大家有什么疑问欢迎在评论区留言。

参考资料

https://reacttraining.com/blog/react-19-will-be-compiled

https://zh-hans.react.dev/reference/react/useMemo

https://gist.github.com/Rich-Harris/0f910048478c2a6505d1c32185b61934

https://www.robinwieruch.de/react-function-component/

https://javascript.plainenglish.io/re-render-a-functional-react-component-1d64fa9a0c60

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness

https://github.com/sveltejs/svelte

https://medium.jonasbandi.net/angular-vs-react-compilers-45b279a8f571?gi=bf6bd4ab0887

展开阅读全文

页面更新:2024-02-27

标签:都会   组合   数组   开发者   缓存   函数   组件   发生   代码   方法

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2008-2024 All Rights Reserved. Powered By bs178.com 闽ICP备11008920号-3
闽公网安备35020302034844号

Top