react 19:前瞻版本功能React compiler

react compier

这是什么?

应该大家有人看过黄玄老师21年在react conf上的react forget,没错就是那个,虽然好长时间没有消息,黄玄老师也离开meta跑去恋综去了,但是它没有凉,一切都在有序的发展验证中。看消息将会正式在不久之后的react 19中发布。

如果你没有看过当前的conf,也无所谓,这里给大家简单回顾一下。

拿我个人举例,我工作中vue和react均使用过较长的时间,但说最喜欢的还是react,尤其是hooks的出现,我喜欢react那简单极致的思维模型。

但是呢,总有一些恶心人的东西需要克服。比如我正常写一个有父子间通信的组件

parent

javascript 复制代码
import { FC, useState } from "react";
import Demo from './Demo'


const App: FC = () => {
  const [state, setState] = useState(0)
  const [num, setNum] = useState(0)

  return (<>
    <div>
      app: {state}
    </div>
    <button onClick={() => setNum(num + 1)}>子组件+</button>
    <button onClick={() => setState(state + 1)}>app组件+</button>
    <Demo sunNum={num} />
  </>);
}

export default App;

sun

typescript 复制代码
import { FC } from "react";

interface DemoProps {
    sunNum: number;
}
 
const Demo: FC<DemoProps> = ({ sunNum }) => {
    console.log('Demo render')

    return ( 
        <div>
            {sunNum}
        </div>
     );
}
 
export default Demo;

我可能理想状态只需要这样写,但是不幸的是。react对于状态的改变是没有记录感知的,他并不知道什么时候应该更新试图。所以只要当这个组件的state、props发生变化它就需要重新re-render。这本来是无可厚非的,但是如果随着组件逻辑的变化re-render成本或者频率的上升就难免引入性能问题。

如上面的代码示例,如果仅是父组件num状态的改变引发子组件re-render逻辑上我们还能忍受,但是非常不幸这里父组件发生任何一次re-render,都 不可避免的会造成子组件的re-render

无奈我们只能给子组件套上一层memo。

但是如果子组件中有相关方法函数呢,那么势必在re-render过程中发生函数的重新创建。再如果有些高成本的计算逻辑呢?

无奈,我们又需要给这些函数上套上恶心的usememo、useCallBack。同时还要思考依赖问题

那么本身好好的代码,可能就会变成这样

typescript 复制代码
import { FC, memo, useCallback, useMemo } from "react";

interface DemoProps {
    sunNum: number;
}

const Demo: FC<DemoProps> = memo(({ sunNum }) => {
    console.log('Demo render')

    const handleClick = useCallback(() => {
        console.log('click')
    }, [])

    const computeState = useMemo(() => sunNum + 1, [sunNum])

    return (
        <div onClick={handleClick}>
            {sunNum}
            {computeState}
        </div>
    );
})

export default Demo;

说好简单的思维模型了?这些额外的成本似乎本不应该出现在这里啊。

react forget的出现就是用来解决这里问题,那么它的解决思路是什么呢? 我们还是来思考一下这些memo函数的本质。简单理解无非是两个状态的前后比较然后决定是否使用缓存。体现在程序上面也无非是提前定义一些缓存变量,然后写一堆if判断逻辑。写这些东西固然麻烦,如果有一个编译器可以帮助我们自动生成这些东西不就完美了么。这便是forget的目的,forget的出现就是想帮我们实现一个这样的编译器。

当然forget的源码目前我还无法拿到,不过有一个老哥借助此思想写了一个简化版本的。我们这里拿来实验一下

代码仓库,感兴趣的可以去看看

我们把代码还原到最简状态

看一下效果

这里我们来看一下它帮忙生成的最终代码,因为这里我使用的vite所以写了一个简单的log插件用于测试

javascript 复制代码
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import forgetti from 'vite-plugin-forgetti';

function inspectForgetti() {
  return {
    name: 'inspect-forgetti',
    enforce: 'post', 
    transform(code, id) {
      console.log(`Before forgetti transform for ${id}:`, code);
      return null;
    },
    transformIndexHtml(html) {
      return html;
    }
  };
}

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    react(),
    forgetti({
      preset: 'react',
      filter: {
        include: 'src/**/*.{ts,js,tsx,jsx}',
        exclude: 'node_modules/**/*.{ts,js,tsx,jsx}',
      },
    }),
    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    // @ts-ignore
    inspectForgetti() 
  ],
})

来看一下Demo这个子组件的编译,可以看到大体逻辑和我们上面所说的基本一致

php 复制代码
import { useMemo as _useMemo } from "react";
import { $$cache as _$$cache } from "forgetti/runtime";
import { $$equals as _$$equals } from "forgetti/runtime";
import { memo as _memo } from "react";
import { $$memo as _$$memo } from "forgetti/runtime";
const _Demo = _$$memo(_memo, "_Demo", (_values) => /* @__PURE__ */ jsxDEV("div", { children: _values[0] }, void 0, false, {
  fileName: "/Users/gongzhen/code/2024/forget/src/Demo.tsx",
  lineNumber: 6,
  columnNumber: 52
}, this));
const Demo = ({
  sunNum
}) => {
  let _cache = _$$cache(_useMemo, 5);
  0 in _cache ? _cache[0] : _cache[0] = console.log("Demo render");
  let _equals = _$$equals(_cache, 1, sunNum), _value2 = _equals ? _cache[1] : _cache[1] = sunNum, _value3 = _equals ? _cache[2] : _cache[2] = [_value2], _equals2 = _$$equals(_cache, 3, _value3), _value4 = _equals2 ? _cache[3] : _cache[3] = _value3;
  return _equals2 ? _cache[4] : _cache[4] = /*@forgetti jsx*/
  /* @__PURE__ */ jsxDEV(_Demo, { v: _value4 }, void 0, false, {
    fileName: "/Users/xxx/code/2024/forget/src/Demo.tsx",
    lineNumber: 23,
    columnNumber: 62
  }, this);
};
_c = Demo;
export default Demo;

如果感兴趣最好看一下黄玄老师的react conf forget

看react官方最近的博客,可能即使最终发布也并不能完美的处理任何问题。因为本身对这种编译类型的工具它本身便对代码有一定的规则要求的,但有时我们的需求代码因为各种问题我们自己本身都需要非常小心的去触碰,何况是它呢。

虽然它可能最后并不是非常完美,但肯定的是起码能给我们带来更多效率上的提升。「这也本身是定制化和自动化长期以来的纠结之处」,启动react19给我们能带来惊喜吧

相关推荐
浮华似水17 分钟前
简洁之道 - React Hook Form
前端
正小安2 小时前
如何在微信小程序中实现分包加载和预下载
前端·微信小程序·小程序
_.Switch4 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
一路向前的月光4 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   4 小时前
vite学习教程06、vite.config.js配置
前端·vite配置·端口设置·本地开发
长路 ㅤ   4 小时前
vue-live2d看板娘集成方案设计使用教程
前端·javascript·vue.js·live2d
Fan_web4 小时前
jQuery——事件委托
开发语言·前端·javascript·css·jquery
安冬的码畜日常4 小时前
【CSS in Depth 2 精译_044】第七章 响应式设计概述
前端·css·css3·html5·响应式设计·响应式
莹雨潇潇5 小时前
Docker 快速入门(Ubuntu版)
java·前端·docker·容器
Jiaberrr5 小时前
Element UI教程:如何将Radio单选框的圆框改为方框
前端·javascript·vue.js·ui·elementui