乐趣区

关于javascript:前端框架性能与灵活性的取舍

大家好,我卡颂。

针对 前端框架,长期存在着各种纷争。其中争执比拟大的是上面两项:

  • 性能之争
  • API 设计之争

比方,各大新兴框架都会掏出 benchmark 证实本人优良的运行时性能,在这些 benchmarkReact通常是垫底的存在。

API 设计上,Vue爱好者认为:“更多的 API 束缚了开发者,不会因为团队成员程度的差别造成代码品质较大的差别”。

React 爱好者则认为:“Vue大量的 API 限度了灵活性,JSX yyds”。

上述探讨归根结底是框架 性能 灵活性 的取舍。

本文将介绍一款名为 legendapp 的状态治理库,他与其余状态治理库设计理念上有很大不同。

React 中正当应用legendapp,能够极大晋升利用的运行时性能。

但本文的目标并不仅仅是 介绍一个状态治理库 ,而是与你一起感触 随着性能进步,框架灵活性产生的变动

欢送退出人类高质量前端框架群,带飞

React 的性能优化

React性能的确不算太好,这是不争的事实。起因在于 React 自顶向下的更新机制。

每次状态更新,React都会从根组件开始深度优先遍历整棵组件树。

既然遍历形式是固定的,那么如何优化性能呢?答案是 寻找遍历时能够跳过的子树

什么样的子树能够跳过遍历呢?显然是 没有发生变化的子树

React 中,变动 次要由上面 3 个因素造成:

  • state
  • props
  • context

他们都可能扭转UI,或者触发useEffect

所以,一棵子树中如果存在上述 3 个因素的扭转,可能会发生变化,也就不能跳过遍历。

变动 的角度,咱们再来看看 React 中的性能优化 API,对于上面 2 个:

  • useMemo
  • useCallback

他们的实质是 —— 缩小 props 的变动。

对于上面 2 个:

  • PureComponent
  • React.memo

他们的实质是 —— 让比拟 props 的形式从 全等比拟 变为 浅比拟

状态治理库能做的优化

理解了 React 的性能优化,咱们再来看看状态治理库能为 性能优化 做些什么呢。

性能瓶颈次要产生在更新时,所以性能优化的方向次要有两个:

  • 缩小不必要的更新
  • 缩小每次更新时要遍历的子树

Redux 语境下的 useSelector 走的就是第一条路。

对于后一条路,缩小更新时遍历的子树 通常意味着 缩小上文介绍的 3 因素的变动

PS:黄玄开发的 React Forget,是一个 能够产生等效于 useMemo、useCallback 代码的编译器 ,目标就是缩小三要素中props 的变动。

状态治理库在这方面能施展的中央很无限,因为不论状态治理库如何奇妙的封装,也无奈覆盖 他操作的其实是一个 React 状态 这一事实。

比方,尽管 MobxReact带来了 细粒度更新 ,但并不能带来与Vue细粒度更新 相匹配的性能,因为 Mobx 最终触发的是自顶向下的更新。

legendapp 的思路

本文要介绍的 legendapp 也走的是第二条路,但他的理念蛮特地的 —— 如果缩小 3 因素的数量,那不就能缩小 3 因素的变动么?

举个极其的例子,如果一个宏大的利用中一个状态都没有,那更新时整棵组件树都能被跳过。

上面是个 Hook 实现的计数器例子,useInterval每秒触发一次回调,回调中会触发更新:

function Counter() {const [count, setCount] = useState(1)

  useInterval(() => {setCount(v => v + 1)
  }, 1000)

  return <div>Count: {count}</div>
}

依据 3 因素法令,Counter中蕴含名为 countstate,且每秒发生变化,则更新时 Counter 不会被跳过(体现为 Counter 每秒都会render)。

上面是应用 legendapp 革新的例子:

function Counter() {const count = useObservable(1)

  useInterval(() => {count.set(v => v + 1)
  }, 1000)

  return <div>Count: {count}</div>
}

在这个例子中,应用 legendapp 提供的 useObservable 办法定义状态count

Counter只会 render 一次,后续即便 count 变动,Counter也不会render

在线 Demo

这是如何办到的呢?

legendapp 源码中,useObservable办法代码如下:

function useObservable(initialValue) {return React.useMemo(() => {// ... 一套相似 Vue 的细粒度更新机制}, []);
}

通过包裹依赖项为空的 React.useMemouseObservable 返回的理论是个 永远不会变的值

既然返回的不是 state,那Counter 组件中就不蕴含 3 因素(statepropscontext)中的任何一个,当然不会 render 了。

咱们将这个思路推广开,如果整个利用中所有状态都通过 useObservable 定义,那不就意味着整个利用都不存在state,那么更新时整棵组件树不都能跳过了么?

也就是说,legendappReact 原有更新机制根底上,实现了一套基于 细粒度更新 的残缺更新流程,最大限度解脱 React 的影响。

legendapp 的原理

接下来咱们再聊聊 legendapp 状态更新的实现。

在传统的 React 例子中:

function Counter() {const [count, setCount] = useState(1)

  useInterval(() => {setCount(v => v + 1)
  }, 1000)

  return <div>Count: {count}</div>
}

count变动,造成 Counter 组件 renderrendercount是新的值,所以返回的 divcount是新的值。

而在 legendapp 例子中,Counter只会 render 一次,count如何更新呢?

function Counter() {const count = useObservable(1)

  useInterval(() => {count.set(v => v + 1)
  }, 1000)

  return <div>Count: {count}</div>
}

实际上,useObservable返回的 count 并不是一个数字,而是一个叫做 Text 的组件:

const Text = React.memo(function ({ data}) {// 省略外部实现});

Text 组件中,会监听 count 的变动。

count 变动后,会通过外部定义的 useReducer 触发一次 React 更新。

尽管 React 的更新是自顶向下遍历整棵组件树,然而整个利用中只有 Text 组件中存在状态且发生变化,所以除 Text 组件外其余子树都会被跳过。

性能与易用性的取舍

当初咱们晓得在 legendapp 中文本节点如何更新。

JSX 非常灵活,除了文本节点,还有比方:

  • 条件语句

如:

isShow ? <A/> : <B/>
  • 自定义属性

如:

<div className={isFocus ? 'text-blue' : ''}></div>

这些模式的变动该如何监听,并触发更新呢?

为此,legendapp提供了自定义组件Computed

<Computed>
  <span
    className={showChild.get() ? 'text-blue' : ''}
  >
    {showChild.get() ? 'true' : 'false'}
  </span>
</Computed>

对应的 React 语句:

<span className={showChild ? 'text-blue' : ''}>
  {showChild ? 'true' : 'false'}
</span>

Computed相当于一个容器,会监听 children 中的状态变动,并触发 React 更新。

文本节点对应的 Text 组件能够类比为 被 Computed 包裹的文本内容

<Computed>{文本内容}</Computed>

除此之外,还有些更具语意化的标签(实质都是 Computed 的封装),比方用于条件语句的Show

<Show if={showChild}>
  <div>Child element</div>
</Show>

对应的 React 语句:

{showChild && (<div>Child element</div>)}

还有用于数组遍历的 <For/> 组件等。

到这一步你应该发现了,尽管咱们利用 legendapp 进步了运行时性能,但也引入了如 ComputedShow 等新的API

你是违心框架更灵便、有更多想象力,还是违心就义灵活性,取得更高的性能?

这就是本文想表白的 性能与易用性的取舍

总结

用过 Solid.js 的同学会发现,引入 legendappReactAPI 上曾经有限靠近 Solid.js 了。

事实上,当 Solid.js 抉择联合 React细粒度更新,并在性能上作出优化的那一刻起,就决定了他的最终状态就是如此。

legendapp + React曾经在运行时做到了很高的性能,如果想进一步优化,一个可行的方向是 编译时优化

如果朝着这个路子继续前进,在不舍弃 虚构 DOM的状况下,就会与 Vue3 有限靠近。

如果更极其点,舍弃了 虚构 DOM,那么就会与 Svelte 有限靠近。

每个框架都在性能与灵活性上作出了取舍,以讨好他们的指标受众。

退出移动版