前端未来框架的选择

前言

我们熟知的三大框架,从vue的响应式的virtual dom处理,到react的fiber架构下的异步更新渲染的virtual dom处理,再到angular的独立设计的dom更新策略、依赖注入等。接下来我会简要描述前端未来框架及发展。

三大框架的特性

声明式、虚拟DOM、组件化

react、angular、vue三个框架的相同点都是声明式在模板中定义我们的循环和显示等等,我们无需命令式的修改dom。我们不再考虑原来的dom更新策略,我们使用了虚拟dom和真实dom的对比,使其在大场景的按需更新节点是非常有用的。同时框架都面向了组件化的开发,我们可以更高效的解决代码复用的问题。

前端框架趋势

image.png

我们可以看出趋势很明显的3个编译阶段的框架,Svelte、Solid、Qwik。

Svelte 前端框架

Svelte的语言写法是很像原生js的,性能跟原生js无差别,Svelte解决了框架在运行时层面的虚拟dom比对在复用节点没有那么需要时的性能问题,我们可以直接在编译阶段的真实dom处理。最核心的就是静态分析的编译器,让我们知道哪个变量变更,按需更新节点即可。我们看下Svelte的具体用法:

<script>
  let count = 0;
  const increment = () => {
    count += 1;
  };
</script>

<button on:click={increment}>
  count is {count}
</button>

我们看看编译之后的内容是什么

image.png

我们可以看到block里面,其实p函数就是更新值、c函数就是创建dom,d就是卸载dom。本质Svelte其实就是帮助我们做到了声明式的处理。那么我们如何监听到数据的变更,Svelte其实是通过编译器去检测变量的赋值、更新、变化。一旦检测到变化会立马执行相应的变更的代码片段。

Solid 前端框架

Solid的写法更像是react,最大的区别就是没有虚拟dom,同时像react一样的单向数据流。我们通过vite简单启动个Solid的模板项目,本地服务跑起来后我们能看到编译之前是这样的

import { createSignal } from 'solid-js'
import './App.css'

function App() {
  const [count, setCount] = createSignal(0)

  return (
    <div class="card">
      <button onClick={() => setCount((count) => count + 1)}>
        count is {count()}
      </button>
      <p>
        hello solid !
      </p>
    </div>
  )
}

export default App

image.png

编译之后我们很明显发现通过template传入了静态的html字符串然后返回真实dom,然后可以快速匹配到不同的节点位置,将click事件绑定到el2身上,在$insert中插入文本节点。

那么solid是怎么监听然后更新模板的dom元素呢,答案是我们代码的createSignal方法

信号 Signal

Solid的createSignal是一种函数式编程范式,其基本原理是返回一个数组,包含一个getter函数和一个setter函数。其主要用途是实现响应式数据流,即当setter函数更新数据时,会自动触发与之相关的视图更新。

具体来说,createSignal会创建一对getter和setter函数,getter函数用于获取当前数据值,setter函数用于更新数据值,并通知所有监听该数据的视图进行更新。Solid在内部使用了一些技术(如ES6的Proxy)来实现数据的响应式更新,保证了视图和数据的实时同步。

除了createSignal外,Solid还提供了一些响应式编程相关的API,如createMemo、createEffect等,用于实现类似Vue.js的单向数据流和响应式UI更新。

Qwik 全栈框架

Qwik主要解决了在服务端渲染的dom,在客户端也要修改它返回渲染的dom(这个过程为水合也就是hyration),客户端同时会去比对服务端的dom结构,并对代码做重新渲染,那么同一代码片段渲染了两次的性能损耗是很大的问题。

Qwik的做法是将全部的渲染逻辑放在了服务端,那么我们在页面如何做交互呢,控制服务端返回的字符串渲染动态的的dom呢,答案是Qwik直接编译出不同的代码片段,我们每一个行为,比如你定义的click事件,他会单独生成一个js文件,用户在触发这个事件后,我们可以直接请求服务端获取click的代码片段。所以Qwik会产出大量的交互的js文件,所以他是细粒度的按需加载。

我们看下具体的用法:

import { component$, useSignal } from '@builder.io/qwik'
import './app.css'

export const App = component$(() => {
  const count = useSignal(0)

  return (
    <>
      <h1>Vite + Qwik</h1>
        <button onClick$={() => count.value++}>count is {count.value}</button>
    </>
  )
})

image.png

我们在渲染静态dom的入口看看,这是一个类似react的createElement的编译函数。我们可以看到这个事件方法的文件是懒加载的,我们点击按钮后就会返回App_component__Fragment_div_button_onClick_Atxt02dBaJI这个事件方法的文件。

image.png

那么Qwik是如何修改变更的变量并把订阅的变量更新呢,然后同步渲染视图呢?答案其实也是跟solid的signal思路是一样的,都是通过proxy来监听,但是在useLexivcalScope中我们会得到序列化的json,里面会存有count变量,并定义了依赖集合subMap, 通过proxy代理count,一旦count写入变化,就会触发set并更新subMapp里面的依赖变量,然后更新视图。

所以Qwik项目一般主页会请求完整的html结构,和交互js。所以性能是要比nextjs等ssr框架是要更好的。

前端的未来——编译器

我们可以看到大量的框架在走向编译的阶段,而不是运行时。也就是我们用构建换运行时的时间,同时我们也在构建阶段做优化。这样我们完全可以使用rust、go等设计的编译器,带来更快的性能提升。比如之前的swc、rspack、esbuild。

猜你喜欢

转载自juejin.im/post/7253610755126427705