以前 网络轨道:Rethinking the Modern Web & Introducing Web-Native Development!
现在让我们谈谈反应性!
在过去的几年中,我花了很多时间在前端思考反应性!在我们在该空间中所取得的一切中,今天的反应性状态仍然有很多不足之处:
-
反应性启动 - 在拦截和反映变化的各种机制中,您会发现,自身的变化检测有 no easy easy 在JavaScript中退出!从语言级别的原语(代理和访问者)到自定义原语,更改检测继续了一个棘手的游戏!
-
语法和心理模型 - 在整体语言和编程范式中,仍然存在 no no en emgologonic 和精神上有效< /em>方式表达反应性逻辑! (当我们尝试使用功能对正常逻辑建模时,这里经常很严格;或试图通过专用DSL进行编译器!)
这些挑战极大地影响了我们的工作,有时以不太明显的方式影响了,鉴于有多少前端范式空间这种有用的魔法现在占据了,因此,多少它说明了我们编写的代码和our overall tooling budget!对于界限如此固有的范式,重新访问各种痛苦点就不可能了!
为此,我刺了一个刺:
- 重新探讨了JavaScript(即反应性原语)中变化检测的基本思想,以及围绕整个反应性概念(即语法和心理模型)的整体语言和编程范式,以进行某些“清洁器”魔术!
- 考虑到这个想法似乎如何遭受微薄的语言支持,在每种情况下都重新探讨了所有语言支持,因此,在大多数情况下,在JavaScript的顶部仍然将其作为单独的语言外部化。 !
挺身而出!这是一篇很长的文章 - 有3个主要部分:
- Re-Exploring Change Detection(引入观察者API)
- Re-Exploring the Language of Reactivity(介绍反射功能)
- The Duo and the Prospect for Reactivity (Covering usecases)
第1/3节
重新探索变更检测
更改检测是JavaScript中反应性的第一个挑战!
总体想法涉及检测和响应对JavaScript对象的更改以驱动同步 - 有时数据绑定,但通常,任何取决于对象状态的功能。在JavaScript中,过去和现在都以多种不同的方式与此联系。
这与信号和钩子无关,因为 - 范式不匹配:
那些“功能”基原始人是设计了Functional Programming范式的设计,其中强调了不变的数据和纯函数。另一方面,对象可观察性与Object-Oriented范式更加紧密相关,其中可变状态和对象相互作用是这种想法。尽管前者基本上代表了一个中间事件系统,但后者涉及对象级变化通过天然机制进行检测。
与此同时,在本文下半年的后期,我们将涉及信号和挂钩。
Immutability是一种编程原理,将数据视为不变。在这里,变化的想法是由函数建模的,该函数可以重新考虑整个数据的转换管道,其中每个转换都会创建一个新实例,并且可观察性发生在事件级别,嗯,而不是在fine-fine-粒度水平。另一方面,可熔性包含变化的概念,其中对象被修改到位。也比较: Mustability与不变性
从历史上看,某些技术在这里度过了一天!例如,dirty-checking是Angular的技术。定制的pub/sub机制是Backbone Models和Ember Models的想法。这些当时甚至其他方法现在已经对新的,更直接的解决方案不满意!
配件和代理;棘手的部分
今天,object accessors(自ES5)和proxies(自ES6以来)极大地彻底改变了问题空间!这些原语构成了在程序级别检测变化的唯一方法:
:配件
const person = {
// name accessors
get name() { ... },
set name(value) { ... },
// age accessors
get age() { ... },
set age(value) { ... },
};
代理
// Original object
const $person = {
name: 'John',
age: 30,
};
// Proxy wrapper
const person = new Proxy($person, {
get(target, key) { ... },
set(target, key, value) { ... }
});
使我们能够在点语法后面隐藏 :
person.name = 'James';
person.age = 40;
提供一个干净的界面,通常您通常需要中介get()
/set()
机制使这些界面变得如此漂亮,以至于您绝对想使用它们!但是,还有其他细节要浏览,最终您会得到意外的样板和一个棘手的实现!
让我们在这里补充说:Decorators! (Axel Rauschmayer博士在其最新的第3阶段语法中采用deep dive。 更改检测以外的语法糖,而不是现有可能性!因此,如果问题是对象观察,我对装饰师最不兴奋!
登录处有一个灵活性问题
这可能是使用它们的最大威慑力量:每次工作和不支持即时属性的僵化性!这要求您围绕对象在运行时改变的所有可能方式进行预先设计决策 - 对于许多动态应用程序而言,这一假设太不现实了;如果您尝试过,会很快失败!
登录是Vue 2中的主要反应性机制,这可能是查看其真实生活限制的最佳场所 - 其中最大的可能是他们无法检测新的财产添加!对于they didn't hesitate to move away有机会!
的框架,这成为一个足够大的问题
代理在那里有一个身份问题
事实证明,对于每个“代理”实例,您实际上必须考虑两个不同的对象身份,,这对于依赖类型检查(x instanceof y
)和平等检查(x === y
)<的代码非常有问题。 /strong>。哎呀,由于其不同的身份,即使有两个具有相同对象的代理实例也不等于!
正如David Bruant可以让我在这里做的那样,让我们添加一些骇客,您可以得到正确的“实例”工作:via the koude4 trap。 (但是我想这并没有使整个想法变得更加棘手,但更多!)
也许让我们参考the concept of membranes和Salesforce's implementation of the idea上的Tom van Cutsem-其中您可以将所有引用对同一目标返回相同的代理实例,从而使参考文献通过“平等检查”! (但是我想膜不是您在这里真正要做的!)
所有这些可能是与代理有关反应性查看问题的另一种方法: Tricky ,无简单的方法!!
内部陷阱的整个想法
在这些内部方法启用的通信模型和围绕反应性的典型模型之间,它是 gap :这些原始人不会带来常规订阅模型,其中任何人都可以订阅更改课程!他们只是为您提供了一种方法,以 内部陷阱 - 但不是从外部观察的方法 - .subscribe()
方法!但是,从这里开始,这一直是一个棘手的游戏:
-
棘手的话,如果您要采用那些内部方法作为通用的“观察者”!在访问者的情况下,如果不重新定义属性,则无法拥有另一个“观察者”,因此强>无意中置换了现有的!在代理的情况下,虽然您无论如何都无法取代陷阱,因为这些陷阱在实例时间(即由构造函数闭合),但您也将没有另一个“观察者”,而这次,这一次,在同一对象上创建另一个代理实例,因此无意间最终获得了多个冗余代理,它们每个都在跟踪非常不同的交互 - 每次都会为您提供一个漏水,可旁路的跟踪机制!
配件
Object.defineProperty(person, 'age', { set() { /* observer 1 */ } }); // Singleton Error: A new observer inadvertently breaks the one before Object.defineProperty(person, 'age', { set() { /* observer 2 */ } });
代理
let $person1 = new Proxy(person, { /* observer 1 */ }); goEast($person1); // Singleton Error: A new observer inadvertently creates redundancy let $person2 = new Proxy(person, { /* observer 2 */ }); goWest($person2);
事实证明,这些内部方法根本不会将添加到反应性系统!直到您实际构建一个,您就没有一个!
-
从这些内部方法到“反应性系统”!您需要建立一个内部方法可以与之交谈的事件的机制,而没有发生的事件,如果没有,就无法发生这种机制必须浏览新的技术细节!那里有很多陷阱!例如,无意间引入破坏性更改是公共对象界面 - 例如
window.document
- 污染/修补他们的命名空间与setter和getters或类似.subscribe()
方法的东西!
配件
Object.defineProperty(window.document, 'custom', { set() { /* observer 1 */ } });
const person = { // Namespace pollution _callbacks: [], subscribe(callback) { this._callbacks.push(callback), }, // Accessors get name() { ... }, set name(value) { ... }, };
代理
const _callbacks: [], function subscribe(callback) { _callbacks.push(callback); } const $person = new Proxy(person, { get(target, key) { // Namespace pollution return key === 'subscribe' ? subscribe : target[key]; }, set(target, key, value) { ... } });
事实证明,从“内部方法”到“反应性系统”没有直接/整洁的路径!
“魔术对象”的整个想法
作为这些技术启用的反应性形式的叙述,其含义是根据这些魔法对象创建的最终模型的含义:一个系统,其中可观察性是 object objocts pea 或换句话说,对象级关注 !这带有许多复杂性和颗粒级的多点失败!
在您建筑的颗粒状层面上是一个关注的问题,“反应性”现在倾向于控制您的工作的每个细节:对象必须是专门构建的,以实现可观察性 和现有一个翻新; 您所做的一切,您必须是关于反应性的 explicit !而且由于很难获得,并且很容易失去,您还必须意识到每个对象如何通过应用程序流动;在那里脚枪和瓶颈:
-
棘手地考虑在各个互动点!给定功能依赖于数据和反应性捆绑在一起并一起传递在一起的系统,这种情况很容易发生,以至于后者在运输中丢失了不知不觉中!在任何相当大的代码库中,这都是一个大问题!
配件
// Using the accessor-based ref() function in Vue.js import { ref, watchEffect } from 'vue'; const count = ref(0); // Reactivity gained watchEffect(() => { console.log('Count value changed:', count.value); }); function carelessHandling(count) { delete count.value; count.value = 2; } carelessHandling(count); // Reactivity lost
事实证明,任何基于“魔法对象”的反应性系统都易于在颗粒级上失败!
-
在处理对象树时很棘手!您无法在没有某种形式的递归转换的情况下获得反应性, ,但所有这些仍然会受到您的假设。对象树是A 静态结构!这次事情变得非常棘手,因为实际上,假设并非总是如此!
代理
// Using the depth-capable reactive() function in Vue.js import { reactive } from 'vue'; // Creating a reactive object tree const reactiveTree = fetch(resourceUrl).then(res => res.json()).then(json => { return reactive(json); }); // Problem reactiveTree.entries.push({ ... });
事实证明,从缝合代理或登录器中获得的任何反应性通常都是无法预测的和 rickety !!
思考问题的一种方式
代理和登录器构成了今天唯一的本机 基于突变的方法反应性编程 - 并不是特别是因为它们完全反映了所需的方法,而是因为它们是只有本地方式!但是,这不是设计不良的语言功能的情况,这与用户酶不匹配相反!代理和访问者在更广泛的元编程主题中具有完美的用途,而反应性编程代表了一个用户录酶, ;或换句话说:的东西不是很大的用户酶!
例如,代理人在更广泛的元编程中可以做的事情,在设计中进行了大量的技术和学术工作(从ES-lab's original design document可以看出),这就是这样,这就是这样背景它们似乎是反应性的银色子弹!但是事实证明,仅在其中的一个子集上 - 通常是三个内部陷阱的东西:
get
,set
,deleteProperty
- 其余的未在代理设计中完全捕获!
虽然对象可观察性作为一种技术可能与代理和访问者的用途酶具有重叠,但并不是这些设计的主要内容,相反,这些原始人并不是成为问题案例的理想选择!如果我要从数学上代表这一代表,那将是25%的重叠,75%不匹配:
这使我们渴望找到解决问题的东西!事实证明,这不是我们第一次撞墙!
重新发现先前的艺术并确定问题
这是历史帮助我们的地方!
在残留物中,这是对象可观察性的设计先例,等待重新发现:koude18 API,该API于2015年11月贬值!它可能已经领先于时代,但是在泥泞的8年中,有一些时间教会了我们: 平台需要本机对象可观察性,可以让事情与其他不同的事物共存用途!
考虑此API中的方法:
Object.observe(object, callback);
请注意,现在,跟踪机制是与对象本身分开的 ,而且每个对象都是通用的! (这很像从koude19和koude20升级到koude21!)这基本上可以通过Pirinives !
解决我们今天的所有问题- 现在,我们可以将所有这些“魔术对象”想法放在身后:可观察性不必是对象级功能(或对象级问题)!
- 我们不必再对反应性明确。或受到关注的管辖!
- 它不必获得或丢失;或与数据一起传递!
- 现在,一切都可以随时起作用;不再有前期工作,也没有处理内部陷阱!
- 不再有修补对象,也不会转换对象树!
- 现在,我们可以在代码库中与真实对象进行交互:不再依赖中介功能或包装器!
- 不再考虑两个不同的对象身份或围绕它攻击!
- 不再处理泄漏,旁路跟踪机制!
那不是钉吗?我们为什么不应该进一步探索
多年来,我自己围绕着对象可观察性挣扎,我发现今天有必要重新探索这项先前的艺术,作为一个新项目:观察者API !
引入观察者API
这是一种新的努力,可以重新探索沿Object.observe()
线的对象可观察性,但是这一次,采用更全面的方法!它可以在上取得了飞跃,而不是 相关但不明智的API,例如Object.observe()
,“代理陷阱” API和Reflect Api(这是互补的API)代理)!
这是即将提出的建议!
概述
作为对其原始形式的改进-koude18,对象可观察性API被重新想象!
虽然以前的API生活在全局“对象”对象上,但新想法是将其放在全局“反映”对象上,就好像是难题中缺少的部分之一! (但这要受到这里考虑的一切是否仍然属于反射API的范围。另一个是将其放在一个名为Observer
的新的反射式对象上,这是这次的反应性API!
虽然发生了这种情况,但如今,观察者API的原型以Observer
的形式存在,除其他专用API外,还具有每种反射方法的超集。这里的大部分代码基于此Observer
名称空间。
观察者api | 反射api |
---|---|
apply() |
apply() |
construct() |
construct() |
observe() |
- |
set() |
set() |
setPrototypeOf() |
setPrototypeOf() |
与反射API完全兼容,观察者API今天可以用作反射的倒入替换。
开始,这是observe()
方法:
签名
// Observe all properties
Observer.observe(object, callback);
// Observe a list of properties
Observer.observe(object, [ propertyName, ... ], callback);
// Observe a value
Observer.observe(object, propertyName, inspect);
handler
function callback(mutations) {
mutations.forEach(inspect);
}
function inspect(m) {
console.log(m.type, m.key, m.value, m.oldValue, m.isUpdate);
}
从这里开始,需要其他功能!我们在这里讨论其中的一些并链接到其余。
具有路径可观察性
Object.observe()
急需的功能是可观察到的!最初将其排除在外的空隙需要大量的样板才能填充。当时有几个多填充物(例如聚合物的observe-js)牢记这一点。当然,这在新的API中占有一席之地!
这次,而不是遵循基于字符串的“路径”方法-level1.level2
-路径由数组表示 - Path
数组实例:
const path = Observer.path('level1', 'level2');
一个数组允许我们支持自己中有点的属性名称。通过使用观察者的Path
数组实例,我们能够区分普通的“属性列表”数组 - 如前所述 - 和实际的“路径”数组。
这里的想法是在给定树的路径上观察“值”:
// A tree structure that satisfies the path above
const object = {
level1: {
level2: 'level2-value',
},
};
Observer.observe(object, path, (m) => {
console.log(m.type, m.path, m.value, m.isUpdate);
});
object.level1.level2 = 'level2-new-value';
控制台
类型
路径
value
isupdate
set
[
level1
,level2
,] level2-new-value
true
,最初的树结构可以是什么:
// A tree structure that is yet to be built
const object = {};
const path = Observer.path('level1', 'level2', 'level3', 'level4');
Observer.observe(object, path, (m) => {
console.log(m.type, m.path, m.value, m.isUpdate);
});
现在,任何更改路径上“值”的操作都可以解决 - 通过树延伸或树截断 - 都会解雇我们的听众:
object.level1 = { level2: {}, };
控制台
类型
路径
value
isupdate
set
[
level1
,level2
,level3
,level4
,] undefined
false
同时,下一个完成了这棵树,听众在其观察路径上报告了一个值:
object.level1.level2 = { level3: { level4: 'level4-value', }, };
控制台
类型
路径
value
isupdate
set
[
level1
,level2
,level3
,level4
,] level4-value
false
如果您要找到审计步道中路径中发生突变发生的确切点,请使用事件的context
属性来检查父事件:
let context = m.context;
console.log(context);
再次提高一个级别,直到root事件:
let parentContext = context.context;
console.log(parentContext);
您可以观察到异步构建的树木!如果沿着路径遇到承诺,则暂停了进一步的访问,直到承诺解决:
object.level1.level2 = Promise.resolve({ level3: { level4: 'level4-new-value', }, });
文档
访问docs以获取完整的详细信息 - 包括Timing and Batching,full API Reference等。
该项目
观察者API正在开发为今天要使用的东西 - 通过多填充。多文件具有所有已记录的内容 - 有一些限制!
The Observer API
Motivation ⢠Overview ⢠Documentation ⢠Polyfill ⢠Getting Involved ⢠License
使用实用程序 - 首先通用反应性API观察并截取在任意JavaScript对象和阵列上的操作!该API重新探索了koude18 API的独特设计,并刺入了 相关的统一API ,但不同的 Object.observe()
,Reflect apis以及“陷阱” API(代理陷阱)!
观察者API是即将提出的建议!
动机
在JavaScript对象上跟踪突变历史上依赖于ES6 Proxies的“对象包装”技术,以及使用getters and setters的“属性混乱”技术。除了第一个如何提出对象身份问题,第二个问题是互操作性问题,编程模型中也存在太多的僵化,每种都可以启用!
在the introductory blog post 草稿
中广泛讨论了这一点我们找到了koude18 Api的对象可观察性的设计先例
在Github上与一颗星星一起显示支持!此外,此时欢迎所有形式的贡献。
第2/3节
重新探索反应性语言
虽然我们可能具有固定的对象可观察性,但我们仍然需要找到一种实际编写反应性逻辑的方法 - 在我们通常如何编写JavaScript程序上!这里有很大的区别,就像播放单个音符和创作歌曲之间的区别!
但是,尽管这与对象可观察性解决的问题构成了不同的问题,但它们已经相关了,这绝对是where this journey culminates!
来编写应用程序逻辑...
这是我们学会编写应用程序的方式(注意:在正常的,命令的JavaScript中):
let count = 5;
let doubleCount = count * 2;
console.log(doubleCount); // 10
反应性编程的挑战是找到一种表达相同的方法,以使逻辑...
count -> doubleCount -> console.log(doubleCount)
...即使更新了依赖关系链的一部分,也继续保持!
这种魔术本身没有正式的语言,但是它已经成为当今前端上最容易说的语言!但不幸的是,反应性是很难说话的语言:
这涉及编译器驱动的语法和全新的DSLs,从更一般的角度来看,将完全不同的编程编程范式转移到编写应用程序的转变:Functional Programming!对于普通开发人员来说,这是一个大问题: 范式shift 从“正常” javascript !
碰巧的是,这个想法没有简单的方法!当您看时: 总是反应性是语法和心理模型!仅,在这两个因素上,某些方法都可以认为比其他方法更好。
语法和心理模型的损失
输入反应性的典型语言:功能编程...
命令程序以字面术语编写,并且基于A 线性执行流量(对语句和控制流结构的顺序,逐线评估) ,功能编程需要一系列的更改检测原始图,以建模给定的应用程序逻辑!
鉴于我们在上下文中的示例程序,这是使用一组相同的原始图:
的“功能性”等效物在跨框架上看起来像反应
import { useState, useMemo, useEffect } from 'react';
// count
const [count, setCount] = useState(5);
// doubleCount
const doubleCount = useMemo(() => count * 2, [count]);
// console.log()
useEffect(() => {
console.log(doubleCount);
}, [doubleCount]);
// Update
setCount(10);
solidjs
import { createSignal, createMemo, createEffect } from "solid-js";
// count
let [count, setCount] = createSignal(5);
// doubleCount
let doubleCount = createMemo(() => count() * 2);
// console.log()
createEffect(() => {
console.log(doubleCount());
});
// Update
setCount(10);
vue.js cross
import { ref, computed, watchEffect } from 'vue';
// count
const count = ref(5);
// doubleCount
const doubleCount = computed(() => count.value * 2);
// console.log()
watchEffect(() => {
console.log(doubleCount.value);
});
// Update
count.value = 10;
苗条
import { writable, derived } from 'svelte/store';
// count
const count = writable(5);
// doubleCount
const doubleCount = derived(count, $count => $count * 2);
// console.log()
doubleCount.subscribe(value => {
console.log(value);
});
// Update
count.set(10);
mobx
import { observable, computed, autorun } from 'mobx';
// count
const count = observable.box(5);
// doubleCount
const doubleCount = computed(() => count.get() * 2);
// console.log()
autorun(() => {
console.log(doubleCount.get());
});
// Update
count.set(10);
我们在这里有什么?一种常见的反应性语言,以及问题案例: a 编程范式 shift !
传统javascript中的字面变量声明在功能方法中是一种非常不同的声明:
// Variable
let items = ['one', 'two'];
// Read/write segregation
const [items, setItems] = createSignal(['one', 'two']);
此外,传统的JavaScript是一个“可变”的世界,是功能性方法“不变”的世界(或者您最终会违抗反应性系统):
// Mutation world
items.push('three');
// Immutable world
setItems([...items(), 'three']);
和条件构造,循环等的故事相同!
现在,重点完全从编写惯用的JavaScript转变为遵循特定于范式的约束,此外,其他特定于实现的细节:
- 在信号家族中:that technique around fine-grained reactivity-您必须仔细制作,或“ group”,围绕数据流的逻辑“逻辑”才能真正保持细粒度。另外,that extra invokation syntax -
count()
,count.get()
,count.value
-与本来应该是文字变量的那样。 - 在钩子家族中(react,preact):the underestimated dependency array等
所有这些都在需要将应用程序逻辑的控制流和依赖图模拟所需的勤奋之上 - 通过将多个原始件拼凑在一起!任何去过那里的人都可以说明许多运动零件和大量抽象和间接 和 !
事实证明,这是任何“运行时”魔术的问题,因为必须忍受:语法噪声和其他符合人体工程学的开销 ,很难-grok执行模型和其他认知开销等!
这在整个框架中都是一个足够强的案例,每个人都必须用其他类型的魔术来支持他们的“功能性”魔法,这些魔法试图解决语法或精神模型! P>
解决语法
例如, VUE将帮助您automatically unwrap .vue
模板中使用的某些REF,以及在其他一些情况下。 (这个想法甚至使其成为一个更广泛的syntax transform项目!)同样,在React,work is underway中,put memoization and more在编译器后面!
svelte在这里脱颖而出,因为它充满了编译器的想法,让您在其“功能”核心之上以.svelte
模板的字面意义上的JavaScript编写整个逻辑:
<script>
let count = 0;
$: doubleCount = count * 2;
</script>
<main>
<button on:click={() => count += 1}>Double Count: {doubleCount}</button>
</main>
仅,它因“扭曲” - 魔法 - 侵入:从 comply comply comply , non-linear 执行模型,因此无视“ imperative”范式的一般期望,它似乎是通过!
例如,如下记录:
<script>
export let person;
// this will update `name` when 'person' changes
$: ({ name } = person);
// don't do this. it will run before the previous line
let name2 = name;
</script>
,您可以在该程序的自下而上的方式中再次看到这一点:
<script>
let count = 10;
$: console.log(count); // 20
// Update
$: count = 20;
</script>
命令性语法背后的非线性执行模型!因此,事实证明,当您面对代码时,您所看到的 ,但是 记载的内容!的确,Svelte脚本“与'Plain JavaScript'完全不同,但人们似乎正在接受这些脚本,甚至接受一些宣传它们。” -Ryan Carniato
尽管如此,Svelte的愿景强调了 plain ,文字语法语法 syntax 的强有力案例 - 每个“语法” Quest的标准实际上在他们面前! 仅,从当前的现实中可以看出,这不能使用 functional , non -linear core !
是合理的。解决心理模型
React长期以来一直站在他们认为“更简单”的渲染模型上:
在正常功能编程中,变化从函数转变为明确建模的功能,反应在其“钩子”方法中偏离了该想法的“钩子”方法,其中变化不会从函数转换为显式建模,但直接传播到“组件”并触发组件的完整“自上而下”重新渲染 - 给我们一个“自上而下”渲染模型!
这里的音调是:
“您不必考虑更新如何流过UI!只需将整个内容恢复,我们就足够快。” - Andrew Clark
和与信号有关:
“这就是为什么我不购买信号提供更好的DX/心理模型的论点。” -Andrew Clark
从这个角度来看,应保持高度保护:
“我认为你们应该在推动这些东西方面更具侵略性。扔掉反应编程模型并采用信号(或更糟糕的是,信号加上DSL)是一个巨大的倒退,大多数是人们还没有将其内化。” -Pete Hunt
当您看时,您可以在这里看到“线性”执行模型的愿景,很像常规程序的线性“自上而下”流!
仅,它本身就以“扭曲”(魔法)的方式违反直觉而受到影响:违背了对“ functional”范式的普遍期望,它似乎已经通过了!因此,事实证明,当您面对代码时,您所得到的不是您所看到的,但是 记录了,对于许多开发人员而言,这使信号成为信号方法更加糟糕的,其中“功能”是“函数”:
“这很有趣,我第一次使用信号不是为了perf,这是为了理解在哪里更容易发生的变化。再加上LSP引用更改。
似乎....最合理的“ -ThePrimeagen
可能是:
“反应思想的领导长期以来培养了'我们的模型更正确的叙事,几乎变成了教条。实际上,许多工程师对信号的心理模型变得更有生产力 - 在哲学层面上争论发生这种情况时毫无意义。” -Evan You
,整个想法似乎又有额外的useMemo/useCallback性能黑客攻击需要控制“重新渲染” MDOEL!
尽管如此,就线性执行模型而言,React的愿景强调了较少心理间接开销的强有力案例 - 我们希望有一天能够恢复的目标! 仅,这似乎不是函数原始人可以合理地提供的 - 从目前的现实中可以看出!
解决两者
鉴于没有人体工程学和精神间接费用的反应性一直在寻求反应性,新的bar 是 不能解决一个,而另一个 !我们现在可能不会在单一的含义中达到标记,但是直到more and more people发现编译器的力量并进行完整的移动!
“智能编译器将是下一个大转变。它需要发生。查看现代C编译器的优化和巫师。网络需要类似的复杂性,以便我们可以保持更简单的心理模型,但要获得优化的性能。” -Matt Kruse
他们能够采用一块代码并完全生成不同的零件,编译器给我们一个空白的支票来编写我们想要的代码,并恢复特定问题的等效代码,成为反应性编程的最佳前景!很快就反应性的语言,我们可能再也不会谈论“功能性”原语或“功能”核心!这只有一个地方:仅在简单的JavaScript中反应性;这次,语言的字面形式和线性流!
“它是一个构建一个编译器,该编译器了解组件中任意JS代码的数据流。
很难吗?是的!
有可能吗?绝对。,然后我们可以将其用于许多事物。
最终bar 用于反应性!,您看到的是svelte中的一半的视力:“ stytax”(假设一条完全探索到末端的路径)反应中的视力一半:“自上而下流”(假设一条完全探索到末端的路径);关于反应性语言的所有任务最终!
但是,如果 今天的“未来反应性”论文中的所有内容都可能 ,而没有编译步骤?
,这是给命令式JavaScript带来反应性并确定问题
您会意识到,“构建一个理解任意JS代码的编译器”的想法是将反应性带到“任意” JavaScript的代名词,,如果我们能够实现这一目标,我们可以将其视为本地人的反应性语言功能!现在这改变了一切,因为这将不再是编译器的事情!
我们现在可以跳到那个有趣的部分吗?
我们通过重新访问的重新访问来探讨这一点,从而使“任意” JavaScript 对此进行了反应性并解决此问题! Svelte是一个很好的起点:
在今天的Svelte中,反应性基于自定义.svelte
文件扩展名! (作为应用程序中的A 反应性编程上下文的想法!)
calculate.svelte
<script>
// Code here
</script>
但是,如果我们可以将该反应性编程上下文带到函数...
该怎么办
...对于一个方便的构建块,可以很容易地组成其他东西?
function calculate() {
// Code here
}
接下来是您表达反应性的方式!
在今天的Svelte中,依赖于美元标志$
标签 - 是另一种手动依赖性管道,尽管微妙:
<script>
let count = 10;
// Reactive expressions
$: doubleCount = count * 2;
$: console.log(doubleCount); // 20
</script>
但是,如果我们简单地将每个表达式视为 反应性...
...已经进入了反应性编程环境吗?
function calculate() {
let count = 10;
// Reactive expressions
let doubleCount = count * 2;
console.log(doubleCount); // 20
}
最后,执行模型!
今天在“功能上”更新Svelte中的传播,因此,在任何方向上,到程序的任何部分:
<script>
let count = 10;
// Reactive expressions
$: doubleCount = count * 2;
$: console.log(doubleCount); // 40
// Update
$: count = 20;
</script>
但是,如果我们可以获取更新以传播“自上而下”该程序...
...实际上恢复了实际命令的“自上而下”线性流?
function calculate() {
let count = 10;
// Reactive expressions
let doubleCount = count * 2;
console.log(doubleCount); // 20
// Update
count = 20;
}
好吧,现在请注意,最后一行中对count
的更新不会反应!这使我们提出了一个问题:对此模型中的任何事情有任何反应吗?答案在于“依赖关系”在普通程序中的含义!
传统上,程序以顺序的线性流程运行,并沿其引用范围中的“先验”标识符创建“依赖关系”!这意味着陈述应该真正只能响应范围上发生的变化
,而不是在范围 (因为今天的情况可能是在Svelte中)对于功能范围而言,这将是:更改“外部”范围 - 直到全局范围!function calculate(count) { // External dependency
// Reactive expressions
let doubleCount = count * 2;
console.log(doubleCount); // 20
}
let value = 10;
calculate(value);
count
现在是从“ up”/“ offer”函数范围中的依赖性 - 通过该函数范围内的反应性!但是那会是什么样?
想象该功能在哪里可以“静态”反映其外部依赖性的更新:
// An update
value = 20;
// A hypothetical function
reflect('count'); // "count" being what the function sees
并非特别是它们是参数,尤其是在它们的依赖性方面:
let count = 10; // External dependency
function calculate() {
// Reactive expressions
let doubleCount = count * 2;
console.log(doubleCount); // 20
}
calculate();
// An update
count = 20;
// A hypothetical function
reflect('count'); // "count" being what the function sees
这将我们带到了我们的目的地 - 我们已经检查了所有框的这一点;其中:
- “反应编程上下文”不是某些文件,而是一个功能 - 方便的新“反应性原始”!
- 反应性不是一些特殊的语言或特殊的惯例,而是 JavaScript - 如果您尝试过,您唯一到达的地方!
- 执行模型并不是我们无视字面语法的语义的“功能性”,而是“线性” - 只是命令式JavaScript的工作方式!
现实生活中的所有这些都是反射函数!
引入反射功能
反射函数是一种新型的JavaScript函数,可以在语言的命令式形式中启用细粒的反应性编程 - 其中完全在您自己的代码的依赖关系图上绘制了反应性! P>
这是即将提出的建议! (在JavaScript中引入命令性反应性编程(IRP)!)
概述
反射函数具有区别语法:双星符号。
function** calculate() {
// Function body
}
有关详细信息,请参见Formal Syntax。
功能主体是应静态反映其外部依赖性变化的任何常规代码:
let count = 10; // External dependency
function** calculate(factor) {
// Reactive expressions
let doubled = count * factor;
console.log(doubled);
}
返回值是一个由两部分组成的数组,它既包含函数的实际返回值,又包含特殊的reflect
功能,以获取函数以反映更新:
let [ returnValue, reflect ] = calculate(2);
console.log(returnValue); // undefined
控制台
加倍
returnValue
20
undefined
reflect()
函数仅采用已更改的外部依赖项的字符串表示:
count = 20;
reflect('count');
控制台
加倍
40
路径依赖项以数组符号表示。并且可以立即反映多个依赖性,如果它们立即改变:
count++;
this.property = value;
reflect('count', [ 'this', 'property' ]);
改变传播
反应性具有反射函数,其中存在依赖项“提高”响应范围!这是这样的心理模型:
┌─
a更改发生在函数范围的外部
└─
被传播到函数中,然后自我传播 ─┐
函数主体内部的变化自我传播 降低了范围,但只能重新运行那些取决于特定变化的表达式,并降低依赖关系图!
下面是一个很好的观察方法:具有score
作为外部依赖关系的反射函数,绘制了“反射线”以显示该变量的依赖项图,或者换句话说,是确定性的更新路径那个依赖性:
let score = 40;
function** ui() {
let divElement = document.createElement('div');
// >>─────────┐
let tense = score > 50 ? 'passed' : 'failed';
// └─>────────────────────────────────────┐
let message = `Hi ${ p.firstName }, you ${ tense } this test!`;
// │
let sp│anElement = document.createElement('span');
// └─>──────────────┐
let fullMessage = [ message, ' ', 'Thank you!' ].join( '' );
// └─>─────────────────────────────┐
let broadcast = { [ p.username ]: fullMessage };
// │
let se│ctionElement = document.createElement('section');
// ├─>─────────────────────────────────────────┐
let br│oadcastInstance = new BroadcastMessage( broadcast );
// └─>───────┐ └─>──────────┐
console.log( broadcast, broadcastInstance );
document.body.append(divElement, spanElement, sectionElement);
}
let [ returnValue, reflect ] = ui();
事实证明,如果您着手考虑自己的代码,这将是您将要采取的非常心理的模型!一切都起作用,任何人都将如何预测 it !
加上,有一个笨拙的吹牛:同一算法转化为“像素完美”的细粒反应性水平,您永远无法手动建模;那意味着的精度不再,同样 性能 - 通过手动优化,您永远无法实现;但是,一切都没有工作!
文档
有关Formal Syntax,Heuristics,Flow Control和Functions,API的详细信息,请访问docs。
该项目
反射功能正在开发为当今可以通过多填充的东西。 Polyfill具有专门的编译器和一个小型运行时,可以一起启用所有反射功能,但有很多例外。
webqit / reflex-functions
一种新型的JavaScript函数
Motivation ⢠Overview ⢠Documentation ⢠Polyfill ⢠Getting Involved ⢠License
反射函数是一种新型的JavaScript函数,可以在语言的命令中启用精细的反应性编程 - 其中完全在您的依赖关系图上绘制了反应性自己的代码!
这是一个即将提出的建议! (在JavaScript中引入命令性反应性编程(IRP)!)
动机
反应性在宿主上依赖许多运行时技术和编译器魔法,需要大量的手动工作,总体而言,构成了基本的范式转变,向我们构建应用程序的方式。方法经常因惯用语言的习惯使用,对表现造成损失,并以棘手的运行时间行为使我们的大脑陷入困境!
在the introductory blog post 草稿
中广泛讨论了这一点我们意识到我们可以将反应性的语言求解到普通的“ javaScript” - 在 cliralal 形式中,并且�
Div>在Github上与一颗星星一起显示支持!此外,此时欢迎所有形式的贡献。
第3/3节
二人组和反应的前景
驱动前端的魔法使所有功能更加强大,完全直观!
从我坐着的地方,我可以看到观察者API为当今的“内部陷阱”和整个“魔术对象”的想法的大量用户酶所做的大量猛增! (可能是that "revolution" that never took off的时候了!)
,我们当然需要对反应性逻辑的语言支持!想一想围绕JavaScript-XML样DSL的不同建议 - 当然,这不能很好地转化为本机ecmascript功能!并考虑一下今天我们必须依靠编译器来实现这一想法!
现在,我们可以将“任意”的JavaScript通过反射功能反应,其中大量可以在本质上满足!在我们依赖工具的每个领域中,我们只需使用JavaScript 就可以重新获得多少我感到非常兴奋!
!我认为,在每种情况下,都会使用多填充来实验这些新想法,将为我们提供有关问题的新知识! 目前,我很高兴开始对话!
现在,您是否知道这两个反应性基原始人可以单独或一起解决不太可能的问题?在这里您可以找到早期示例:
如果您发现这是一个有趣的想法,请随时分享!
致谢
- 非常感谢David Bruant,Andrea Giammarchi和其他人的一切范围从iNdighs到批评。
参考
- What is reactivity?- vue.js定义
- A Brief History of Reactivity -amiåi i i i i我有一个叙事
- koude18 -Web.dev
- On the design of the ECMAScript Reflection API -ES -LAB
- What is a membrane? -Tom Van Cutsem
- Observable Membrane- Salesforce
- A Hands-on Introduction to Fine-Grained Reactivity -Ryan Carniato
- The Quest for ReactiveScript -Ryan Carniato
- Rethinking Reactivity -Rich Harris
- React Forget - React
- Reactivity Transform - Vue.js