许多初学者常常会问“我须要学习哪个框架?”以及“学习框架前须要把握多少 JS 或者 TS?”有数带有主观色调的文章都在宣传作者首选框架或库的劣势,而不是向读者展现其背地的概念以做出更理智的决定。所以让咱们先解决第二个问题
学习框架前须要把握多少 JS 或者 TS
尽可能多地去学以让更好的你了解它们所基于的概念。你将须要理解根本数据类型、函数、根本运算符和文档对象模型 (DOM),这是 HTML 和 CSS 在 JS 中的示意。除此之外的所有也都 OK,但并不严格要求某个精通框架或库。
如果你是一个完完全全的老手,JS for cats 应该是一个不错的入门材料。继续学习,直到你感到自信为止,而后继续前进,直到你再次感到自信为止。当把握了足够的 JS / TS 常识后,你就能够开始学习框架。其余的常识你能够并行学习。
哪些重要概念
- State(状态)
- Effects(副作用)
- Memoization(记忆化)
- Templating and rendering(模板与渲染)
所有古代框架都从这些概念中派生出它们的性能
state
State 只是为你的应用程序提供能源的数据。它可能在全局级别,实用于应用程序的大部分组件,或实用于单个组件。让咱们写一个计数器的简略例子来阐明一下。它保留的计数是 state。咱们能够读取 state 或者写入 state 以减少计数
最简略的示意通常是一个变量,其中蕴含咱们的状态所蕴含的数据:
let count = 0;
const increment = () => { count++;};
const button = document.createElement('button');
button.textContent = count;
button.addEventListener('click', increment); document.body.appendChild(button);
但这个代码有个问题:相似调用 increment
办法一样去批改 count
的值,并不会主动批改 button 的文案。咱们须要手动去更新所有的内容,但这样的做法在简单场景下代码的可维护性 & 扩展性都不是很好。
让 count
自动更新依赖它的应用方的能力称之为 reactivity(响应式)。这是通过订阅并从新运行应用程序的订阅局部来更新的。
简直所有的古代前端框架和库都领有让 state 变成 reactivity 的能力。基本上能够分为 3 种解决方案,采纳其中至多一种或者多种混用来实现这个能力:
- Observables / Signals(可察看的 / 信号)
- Reconciliation of immutable updates(协调不可变的更新)
- Transpilation(转译)
这些概念还是间接用英文表白比拟贴切 🤣
Observables / Signals(可察看的 / 信号)
Observables 基本上是在读取 state 的时候通过一个订阅办法来收集依赖,而后在更新的时候触发依赖的更新
const state = (initialValue) => ({
_value: initialValue,
get: function() {
/* 订阅 */;
return this._value;
},
set: function(value) {
this._value = value;
/* 触发更新 */;
}
});
knockout 是最早应用这个概念的框架之一,它应用带有 / 不带参数的雷同函数进行写 / 读拜访
这种模式最近有开始有框架通过 signals 来实现,比方 Solid.js 和 preact signals;雷同的模式也在 Vue 和 Svelte 中应用到。RxJS 为 Angular 的 reactive 层提供底层能力,是这一模式的延长,超过了简略状态。Solid.js 用 Stores(一些通过 setter 办法来操作的对象)的形式进一步形象了 signals
Reconciliation of immutable states(协调不可变的更新)
不可变意味着如果对象的某个属性产生扭转,那么整个对象的援用就会产生扭转。所以协调器做的事件就包含通过简略的援用比照就判断出对象是否产生了扭转
const state1 = {todos: [{ text: 'understand immutability', complete: false}],
currentText: ''
};
// 更新 currentText 属性
const state2 = {
todos: state1.todos,
currentText: 'understand reconciliation'
};
// 增加一个 todo
const state3 = {
todos: [state1.todos[0],
{text: 'understand reconciliation', complete: true}
],
currentText: ''
};
// 因为不可变性,这里将会报错
state3.currentText = 'I am not immutable!';
如你所见,未变更我的项目的援用被从新应用。如果协调器检测到不同的对象援用,那么它将从新运行所有的组件,让所有的组件的 state(props, memos, effects, context)都应用最新的这个对象。因为读取拜访是被动的,所以须要手动指定对响应值的依赖。
很显然,你不会用下面这种形式定义 state。要么你是从一个曾经存在的属性结构 state,要么你会应用 reducer
来结构 state。一个 reducer 函数就是接管一个 state 对象而后返回一个新的 state 对象。
react 和 preact 就应用这种模式。它适宜与 vDOM 一起应用,咱们将在稍后形容模板时探讨它。
并不是所有的框架都借助 vDOM 将 state 变成实现响应式。例如 Mithril.JS 要不是在 state 批改后触发对应的生命周期事件,要不是手动调用 m.redraw()
办法,才可能触发更新
Transpilation(转译)
Transpilation 是在构建阶段,重写咱们的代码让代码能够在旧的浏览器运行或者赋予代码其余的能力;在这种状况下,转译则是被用于把一个简略的变量批改成响应式零碎的一部分。
Svelte 就是基于转译器,该转译器还通过看似简略的变量申明和拜访为他们的响应式零碎提供能力
另外,Solid.js 也是应用 Transpilation,但 Transpilation 只应用到模版上,没有应用到 state 上
Effects
大部分状况下,咱们须要做的更多是操作响应式的 state,而很少须要操作基于 state 的 DOM 渲染。咱们须要治理好副作用,这些副作用是因为视图更新之外的状态变动而产生的所有事件(尽管有些框架把视图更新也当作是副作用,例如 Solid.js)
记得之前 state 的例子中,咱们成心把订阅操作的代码留空。当初让咱们把这些留空补齐来解决副作用,让程序可能响应更新
const context = [];
const state = (initialValue) => ({_subscribers: new Set(),
_value: initialValue,
get: function() {const current = context.at(-1);
if (current) {this._subscribers.add(current); }
return this._value;
},
set: function(value) {if (this._value === value) {return;}
this._value = value;
this._subscribers.forEach(sub => sub());
}
});
const effect = (fn) => {const execute = () => {context.push(execute);
try {fn(); } finally {context.pop(); }
};
execute();};
下面代码基本上是对 preact signals 或者 Solid.js 响应式 state 的简化版本,它不蕴含错误处理和简单状态解决(应用一个函数接管之前的状态值,返回下一个状态值),但这些都是很容易就能够加上的
这容许咱们使后面的示例具备响应性:
const count = state(0);
const increment = () => count.set(count.get() + 1);
const button = document.createElement('button');
effect(() => {button.textContent = count.get();
});
button.addEventListener('click', increment);
document.body.appendChild(button);
☝ 能够尝试运行一下下面 Effect 的两个代码块的例子,源代码地址在 这里
在大多数状况下,框架容许在不同生命周期,让 Effect 在渲染 DOM 之前、期间或之后运行。
Memoization
Memoization 意味着缓存 state 值的计算结果,并且在后果的依赖产生扭转的时候进行更新。它基本上是一种返回派生(derived)state 的 Effect
在某些会从新运行其组件函数的框架中,如 react 和 preact,容许在它所依赖的状态没有扭转时防止这部分组件从新渲染
对于其余框架,状况恰恰相反:它容许你抉择局部组件进行响应式更新,同时缓存之前的计算
对于咱们简略的响应零碎,memo 大略是这样实现
const memo = (fn) => {
let memoized;
effect(() => {if (memoized) {memoized.set(fn());
} else {memoized = state(fn());
}
});
return memoized.get;
};
Templating and rendering
当初有了原始的、派生的和缓存模式的 state,咱们想把它展现给用户。在咱们的例子中,咱们间接操作 DOM 来增加按钮和更新按钮的内容文案。
为了晋升开发体验,简直所有的古代框架都反对 DSL 来在代码中编写相似于所需输入的内容。尽管有不同的格调,比方 .jsx
,.vue
,.svelte
文件,但这所有都归结为用相似于 HTML 的代码来示意 DOM。所以基本上是
<div>Hello, World</div>
// 在你的 JS 代码中
// 变成你的 HTML:
<div>Hello, World</div>
你能够能会问:“在哪里搁置我的 state ?”。十分好的问题,大部分的状况 下,{}
用于在属性和节点中表白动静内容。
最罕用的 JS 模板语言扩大无疑是 JSX。在 react 中,它被编译为存粹的 JavaScript 语言,容许创立对于 DOM 的虚构示意,也就是常常被提到的「虚构文档对象」或者简称为 vDOM。
这是基于创立 JS 对象比拜访 DOM 快得多的前提,所以如果你能够用创立 JS 对象替换拜访 DOM,那么你就能够节省时间
然而,如果你的我的项目在任何状况下都没有大量的 DOM 批改或者只是创立不须要批改的对象;那么下面这个计划的长处就会变成毛病,那这个时候就须要应用 memoization 来将毛病的影响降到最小。
// 1. 源代码
<div>Hello, {name}</div>
// 2. 转译成 js 代码
createElement("div", null, "Hello,", name);
// 3. 执行 js 后返回的对象
{"$$typeof": Symbol(react.element),
"type": "div",
"key": null,
"ref": null,
"props": {"children": "Hello, World"},
"_owner": null
}
// 4. 渲染 vdom
/* HTMLDivElement */<div>Hello, World</div>
JSX 不仅仅用在 react,也用在了 Solid.js。例如,应用 Solid 转译器更彻底地扭转代码
// 1. 源代码
<div>Hello, {name()}</div>
// 2. 转译成 js 代码
const _tmpl$ = /*#__PURE__*/_$template(`<div>Hello, </div>`, 2);
(() => {const _el$ = _tmpl$.cloneNode(true),
_el$2 = _el$.firstChild;
_$insert(_el$, name, null);
return _el$;
})();
// 3. 渲染 vdom
/* HTMLDivElement */<div>Hello, World</div>
尽管转译之后的代码一开始看到会感觉挺吓人,但它更容易解释其中代码的逻辑。首先,模版的动态局部被创立进去;而后,创立进去的对象被克隆并创立一个新的实例,新的实例蕴含被增加的动静局部,以及将动静局部的更新与 state 的更新关联起来。
Svelte 在转译的时候做的工作更多,不仅仅解决了模版,还解决了 state
// 1. 源代码
<script>
let name = 'World';
setTimeout(() => { name = 'you';}, 1000);
</script>
<div>Hello, {name}</div>
// 2. 转译成 js 代码
/* 生成自 Svelte v3.55.0 版本 */
import {
SvelteComponent,
append,
detach,
element,
init,
insert,
noop,
safe_not_equal,
set_data,
text
} from "svelte/internal";
function create_fragment(ctx) {
let div;
let t0;
let t1;
return {c() {div = element("div");
t0 = text("Hello,");
t1 = text(/*name*/ ctx[0]);
},
m(target, anchor) {insert(target, div, anchor);
append(div, t0);
append(div, t1);
},
p(ctx, [dirty]) {if (dirty & /*name*/ 1) set_data(t1, /*name*/ ctx[0]);
},
i: noop,
o: noop,
d(detaching) {if (detaching) detach(div);
}
};
}
function instance($$self, $$props, $$invalidate) {
let name = 'World';
setTimeout(() => {$$invalidate(0, name = 'you');
},
1000
);
return [name];
}
class Component extends SvelteComponent {constructor(options) {super();
init(this, options, instance, create_fragment, safe_not_equal, {});
}
}
export default Component;
// 3. 执行 JS 代码
/* HTMLDivElement */<div>Hello, World</div>
当然也有例外,在 Mithril.js 中,尽管能够应用 JSX,但激励你编写 JS 代码
// 1. 源代码
const Hello = {
name: 'World',
oninit: () => setTimeout(() => {
Hello.name = 'you';
m.redraw();}, 1000),
view: () => m('div', 'Hello,' + Hello.name + '!')
};
// 2. 执行 JS 代码
/* HTMLDivElement */<div>Hello, World</div>
有的人会感觉这样做的开发体验不太好,但有的人更心愿对本人的代码有更多的控制权。这取决于他们想要解决的是哪一类的问题,短少 transpilation 这个步骤也可能成为长处。
许多其余框架也容许在不进行 transpilation 的状况下应用,只管很少有人这样举荐。
“当初我应该学习什么框架或者库?”
我有一些好消息和一些坏消息要通知你
坏消息是:没有银弹。没有任何一个框架是在所有层面都优于其余框架的。任何一个框架都有它的长处和斗争。React 有它的 hook 规定,Angular 不足简略的 signals,Vue 的向后兼容性问题,Svelte 的伸缩性不太好,Solid.js 禁止解构,Mithril.js 不是真正的响应式,等等
好消息是:没有谬误抉择 —— 除非我的项目的要求的确受到限制,无论是在捆绑包大小还是性能方面。每个框架都能够实现工作。有些人可能须要解决他们的设计决策,这可能会使你的速度变慢,但无论如何你都可能取得可行的后果。
话虽这么说,没有框架也可能是一个可行的抉择。许多我的项目都被适度应用 JavaScript 毁坏了,而带有一些交互性的动态页面也能够实现这项工作。
当初你曾经理解了这些框架和库所利用的概念,请抉择最适宜你当前任务的计划。不要为下个我的项目的框架选型而感到放心。你不须要学习所有的内容。
如果你尝试一个新的框架,我发现最有帮忙的事件之一就是关注它的社区,无论是在社交媒体、Discord、github 还是其余中央。他们能够通知你哪些办法适宜他们的框架,这将帮忙你更快地取得更好的解决方案。
冲吧,你能够有集体爱好!
如果你的次要指标是待业,我倡议学习 React 或者 Vue。如果你想要轻松的获取性能和管制体验,请尝试 Solid.js
但请记住,所有其余抉择都同样无效。你不应该因为我这么说就抉择一个框架,而应该应用最适宜你的框架。
如果你看完了整篇文章,感激你的急躁期待。心愿对你有所帮忙。在这里发表你的评论,祝你有美妙的一天 🌹
参考文章
- [](https://dev.to/lexlohr/concep…)https://dev.to/lexlohr/concep…
- [](https://github.com/zidanDirk/…)https://github.com/zidanDirk/…