关于javascript:从小学数学聊前端框架设计

大家好,我是卡·小学生·颂。

很开心还有不到10天小学就放暑假了,到时候打农药被人喷了就能说:

“我妈只准我放假玩,手有点儿生”

说回前端。

其实前端框架是个很简略的货色,大部分框架的工作原理能够用一个小学常识解释分明。

本文会从这个常识动手,逐渐谈到前端框架更新粒度背地的衡量。

看完本文,岂但能播种一个扫视不同框架的视角,也能理解React Hooks产生的起因。

可不要再瞧不起咱们小学生哦!

自变量、因变量与响应式更新

这个小学常识就是:自变量因变量

对于如下等式:

2x + 1 = y

x自变量y的值受x影响,是因变量

在很多框架与状态治理库中,同样存在自变量与因变量。

Vue3中的自变量:

const x = value(1);
// 取值
console.log(x.value);
// 赋值
x.value = 2;

MobX的自变量:

const x = observable({data: 1});
// 取值
console.log(x.data);
// 赋值
x.data = 2;

React的自变量:

const [x, setX] = useState(1);
// 取值
console.log(x);
// 赋值
setX(2);

这些框架(或库)的自变量由getter(取值)与setter(赋值)两局部形成。

有了自变量,当然也有因变量。咱们能够按有无副作用辨别。

Vue的因变量:

// 无副作用
// 赋值
const y = computed(() => x.value * 2 + 1);
// 取值
console.log(y.value);

// 有副作用
watch(() => document.title = x.value);

MobX的因变量:

// 无副作用
const y = computed(() => x.data * 2 + 1);
console.log(y.get());

// 有副作用
autorun(() => document.title = x.data);

React的因变量:

// 无副作用
const y = useMemo(() => x * 2 + 1, [x]);
console.log(y);

// 有副作用
useEffect(() => document.title = x, [x]);

有了自变量因变量,再配合形容视图的形式,就能形容组件UI

比方在React中,通过JSX形容视图:

const [x, setX] = useState(21);
const y = useMemo(() => x * 2 + 1, [x]);

return <p>我的战绩是 0/{x}/{y}</p>;

再加上各种使用户能够操纵自变量的事件,如给p减少onClick

<p onClick={() => setX(x + 1)}>我的战绩是 0/{x}/{y}</p>;

最初再加上大量辅助的钩子函数,如:组件产生谬误时的钩子函数

就组成一个性能齐备的组件。

这就是所有细粒度更新框架的底层共通之处:

通过事件驱动自变量扭转,进而最终驱动视图(或副作用)变动

面向对象之痛

在咱们初学编程时,都学过一个概念 —— 面向对象(下文简称OO),也很容易接受一个设定 —— OO能够进步可读性且易保护。

起因是:OO是对事实世界的模仿。比方:

能够继承哺乳动物的属性,这就是个OO模型

然而实际操作起来却大失所望。

回忆你学习ReactClass组件时,在OO简略的表象背地,是简单的生命周期概念,轻易问你几个问题:

  • shouldComponentUpdate的原理是?
  • componentWillReceiveProps什么时候触发?
  • getDerivedStateFromPropsderivedState是什么意思?

好在React团队也意识到这个问题,并着手做出扭转。

扭转的后果,就是Hooks

应用Hooks的函数组件与Class组件最大的区别是:

从领有生命周期的实例自变量、因变量与视图的映射关系转变

如果承受了这个设定,想想当初支流的学习Hooks的形式(甚至React官网也是如此),竟然是:

用生命周期函数来类比Hooks执行机会

是不是还蛮搞笑的。

戴着脚镣跳舞的React

现实是美妙的,可是React底层并不是一个细粒度框架。

这就造成在实现自变量因变量时会有诸多限度,比方:

Hooks调用程序不能变(不能写在条件语句中)

再比方,不晓得你发现一个细节没:

React实现因变量时须要第二个参数显式指出本人的自变量是谁。比方:

const y = useMemo(() => x * 2 + 1, [x]);
useEffect(() => document.title = x, [x]);

反观其余框架(或库)就不须要。比方Vue

const y = computed(() => x.value * 2 + 1);
watch(() => document.title = x.value);

为什么会有这些限度呢?我用两个比喻来解释。

方才聊到,在细粒度框架中,交互流程能够概括为:

用户触发事件 -> 自变量扭转 -> 因变量扭转 -> 映射到视图变动

就像一个画家在画画,画的每一笔对应一个自变量变动,再最终对应画面变动。

React的更新机制大体概括为:

用户触发事件 -> 触发更新 -> 虚构DOM全量比照 -> 将比照后果映射为视图操作

就像一个人拿相机拍一张照片,再拿这张照片和上次拍的照片找不同,最初把不同的中央更新了。

当调用this.setState(或useStatesetter),并不是画下一笔,而是按下快门。

怎么能从一张新照片中发现自变量呢?所以React只能拿新老照片比照。

净整些奇怪的

社区早有人意识到这个问题,于是Mobx诞生了。他带来纯正的细粒度更新能力。

然而,这个能力是建设在React更新机制之上,就像:

一个画家,拿画笔在画板上一戳,戳到什么呢?戳到相机快门了。

咔嚓拍了张照片,画家再拿照片与老照片比照,将比照后果再画到画布上。

所以有人吐槽:用React+Mobx为啥不间接用Vue

然而,Vue自身也依赖虚构DOM,粒度并不是最细的。

更精确的说法应该是:用React+Mobx为啥不间接用SolidJS

呐,过几天咱们来聊聊纯正的细粒度更新框架(SolidJS)的实现原理吧。

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理