大家好,我是卡·小学生·颂。
很开心还有不到 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
模型
然而实际操作起来却大失所望。
回忆你学习 React
的Class
组件时,在 OO
简略的表象背地,是简单的 生命周期 概念,轻易问你几个问题:
shouldComponentUpdate
的原理是?componentWillReceiveProps
什么时候触发?getDerivedStateFromProps
中derivedState
是什么意思?
好在 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
(或useState
的setter
),并不是画下一笔,而是按下快门。
怎么能从一张新照片中发现 自变量
呢?所以 React
只能拿新老照片比照。
净整些奇怪的
社区早有人意识到这个问题,于是 Mobx
诞生了。他带来纯正的 细粒度 更新能力。
然而,这个能力是建设在 React
更新机制之上,就像:
一个画家,拿画笔在画板上一戳,戳到什么呢?戳到相机快门了。
咔嚓拍了张照片,画家再拿照片与老照片比照,将比照后果再画到画布上。
所以有人吐槽:用 React
+Mobx
为啥不间接用Vue
?
然而,Vue
自身也依赖 虚构 DOM
,粒度并不是最细的。
更精确的说法应该是:用 React
+Mobx
为啥不间接用SolidJS
?
呐,过几天咱们来聊聊纯正的细粒度更新框架(SolidJS
)的实现原理吧。