从小学数学聊前端框架设计
大家好,我是卡·小学生·颂。
很开心还有不到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
)的实现原理吧。