前端框架概况总结

277 阅读8分钟

前言

在了解了下 React, Vue 这些优秀框架的源码后,对这些框架做个大概的总结。

这里只讨论基础框架, 那些 UmiJs, NuxtJs 等这类暂不讨论。就基础框架而言,我个人知道的主流框架有 Angular, React, Vue, Solidjs, Svelte

在国内用的最多的,应该就是 React, Vue 了。主要也是围绕这 2 个框架进行讨论。

框架对比介绍

能力有限只会对 React, Vue 框架进行讨论。并不全面,主要是一个个人向的记录,边学边加。(doge)

1. Template or Jsx?

首先个人感觉 TemplateJsx 其实是一家人,拿 Vue 来举例, 它的模板是将AST 转换成 VNode 的生成函数,内部是通过 语法解析(parse), 转换(transform), 生成代码(codegen) 几个步骤,而我们的 Jsx 也是利用 Babel Parse 基于 AST,在通过以上几个步骤来解析的。

但重点在于,TemplateJsx 在使用方面上的不同。

Jsx 的特点便是灵活简单容易上手,不需要记太多 API

// Jsx代表框架 -- React
export default () => {
  const list = [1, 2]
  // 下面这段就是用 jsx 创建的循环
  return (
    <>
      {list.map((v) => (
        <span key={v}>No.{v}</span>
      ))}
    </>
  )
}

模板Template,它有着自己的一套约束,虽然在灵活度上就显得不足,但模板对于框架开发者而言更加友好,使得开发者可以创建各种语法协助构建 DOM

<!-- Template代表框架 -- Vue -->
<template>
  <span v-for="item in [1, 2]">No.{{ item }}</span>
</template>

2. 响应式逻辑

Vue :

const list = reactive([1, 2])
list.push(3) // 触发 list 的 set ,进而触发 trigger

const count = ref(0)
count.value = 3 // 触发 Ref类实例 里的 set value() 访问器,进而触发 trigger

Vue 大家比较熟知, Vue2.x 是通过 Object.defineProperty 给对象的每个属性绑定上一个 get/set 访问器。

Vue3.xreactive 主要是通过 Proxy 进行代理, 而 ref 是通过 Class 访问器, 这里的 Class 指的是Ref 类实例内部创建了一个 valueget/set 访问器, 具体可以看我 《Vue 源码之【响应式】》 的文章。

React :

const [count, setCount] = useState(0)
setCount(1) // 触发

ReactV16 开始就采用 Fiber 架构,且在 V16.8 开始可以使用 Hook 进行 函数式组件 的状态处理。

这里只简单介绍下大概流程: 用户调用 setState 去修改数据之后,会触发更新,从当前位置开始顺着 Fiber链 向上修改 lane 优先级。然后开始向下调和,找到触发更新的那个 Fiber 进行 rerender ,函数组件的 rerender 会重新调用 函数,使得重新渲染后值就改变。

Solid :

const [count, setCount] = createSignal(0)
setCount(count() + 1) // 触发局部更新

Solidjs 的响应式数据用法和 React Hooks 类似,但实现却和 React 不同,框架整体采用的思路跟 Vue 类似,不过不同的是,他内部是通过函数闭包的方式创建了 gettersetter。具体有想了解的小伙伴可以找找。我也还没弄透,简单写一个大概...

function getter() {
  return this.value
}

const createSignal = (value) => {
  const obj = {
    value,
  }

  const setter = (newValue) => {
    obj.value = newValue
  }

  return [getter.bind(obj), setter]
}

const [count, setCount] = createSignal(1)

3. 更新颗粒度

更新的颗粒度直接与性能挂钩,就目前来说,颗粒度可以划分为三级:应用级,组件级,节点级 颗粒度越细腻,理论上来说性能也就越好。

React 属于应用级框架,之所以性能一直被鞭尸,也是由于其每次 rerender 的压力非常大。从 Fiber 链表向下调和,再在目标节点处开始 render,所以经常会有 子组件渲染 是因为 父组件 被渲染了 这种尴尬的地方……

Vue 属于组件级框架,其渲染是跟组件挂钩的,当响应式数据被更新时,进而会触发到其组件的更新方法(componentUpdate),但是 Vue 不会随便去触发子组件渲染,而是会去用 shouldUpdateComponent 去对比组件前后属性,props 等是否有变化,如果没有变化,就不会再继续走了。

有意思的是, Vue1.x 也是节点级的应用,但后面由于 Object.defineProperty 绑定对象的开销特别大,容易导致内存过大,所以从 2.x 开始就放弃 节点级 采用 组件级的方式来优化。

SolidSvelte 属于节点级框架,所谓节点级,即用户更新时,非响应式数据的部分都不动,只单独处理响应式数据那块逻辑。具体细节,还是需要读者去查询一下,篇幅太长,不做过多了解(doge,我也不是特别了解……)

4. 虚拟 DOM

这块其实和上面是连着的,虚拟 DOM 开发的初衷,就是为了减少 DOM 的操作次数。然后你会发现,SolidSvelte 都是属于 无虚拟 DOM 的框架。这便是得力于二者的节点级更新粒度的优势,想象下:我都知道我要更新的是哪个节点了,我又何必去增加 JS 开销弄一个虚拟 DOM 去找这个节点……

组件级应用级 都需要虚拟 DOM,便是由于 一个组件 或者 一个应用被重新更新,其内部非常多节点,他并不知道具体更新的是哪一个节点,所以需要去比较前后哪些节点发生了变化,然后用算法计算出如何操作节点更节省开销

5. Typescript 支持

ReactTs 写起来非常舒服,不管是组件泛型还是导入第三方类型,相当舒服。

Vue3 我前段时间尝试开发过 Ts + SFC 的组件,为了良好的类型,我去兼容第三方类型,但是 Vue3.2 及以下版本中,defineProps 的泛型类型参数仅限于字面量类型或对本文件接口的引用 导致类型写得特别蛋疼,不过在最近, Vue3.3 已经支持了。只能说我当时那会确实尴尬,还有就是 Vue3 的第三方框架的类型特别难以看懂……他有 2 套类型,一个动态运行时声明类型,还有一个就是 泛型声明 ,你如果做组件,想做好兼容,可能需要导出 2 套类型……

当然还是很期待 Vue3 和 Ts 越来越香~

6. 开发体验对比

React 开发优势
  1. 函数就是组件,可以随处导出导入组件,且在一个文件里,可以同时写多个组件。

  2. 万物皆props,哪怕是一个 React 组件 ,你也可以当 props 去传递。

  3. 因为组件就是函数,所以组件里调用自己,就是递归自己,非常简单。

React 开发槽点
  1. useEffect 依赖容易出现 BUG,应该有不少人遇到过类似的……

  2. 目前没有最合适的 KeepAlive 的解决方法(网上有,我也自己写过,但是就怕遇到奇奇怪怪的 bug,且有些用 display: none伪缓存方案包括我自己写的那个,还是存在一些问题)

  3. 当业务多了,结合其他第三方框架容易导致页面渲染 BUG。

  4. 想要获取子组件的属性不太容易,需要调用 forwardRef ,这个 forwardRef 的类型又很奇怪,写起来很难受

  5. 由于 state 并不是最新的,但是我们又需要给后端最新的值,导致很多时候,需要用到 refstate 配合……但明明是同一个的状态,我举个场景大家看看能不能 get 到:

一个弹窗,显示后左边是 Tree,Tree 上有 Search 搜索框,右边是分页 Table,表格上也有 Search 搜索框

然后我右边输入搜索内容的时候,然后我在左边点击切换 Tree,我需要更新掉这个搜索框内容 和 分页

但是由于点击 Tree 的时候,要去调用 分页 接口,传入不同的 TreeId ,

但是由于 Search 和 分页 是在点击之后调用 setState 修改的,就导致……后端那边的还是之前的值,

当然也有解决方法: 用 ref 或者 在点击Tree的时候,给 分页接口 主动传递 空字符串 和 初始分页状态

有朋友问,为啥不写在 useEffect 中? 主要是不好控制,业务多了就不好控制……哎嗨

所以我现在主要是用 refupdate 来控制。把数据存到 ref 里,在通过主动调用 update 去触发更新。
Vue 开发优势
  1. 响应式非常灵活,是和组件完全解耦的,可以单独写在任何地方,你甚至可以把他拿来做全局状态管理器,这点 React 就不行, hooks 必须在组件内部……

  2. KeepAlive 缓存舒服,虽然有些内存上的问题,但是很多后台项目真的很需要这个……

  3. 数据更新很直观,没有压力,降低用户心智负担(对比 React 槽点 5)

Vue 开发槽点
  1. Vue语法糖特别多,一不留神可能就又出了新的 API

  2. 下传的种类非常多 props ,还有 emits, attrs, slotsSFC 里,每个都有独立的 defineXxxx 的 API

  3. 且插槽真的坑爹,所以导致我想封装那种灵活度高的组件都特别不容易