田苏
田生是一个轻量级,以推为主的框架-不可知析的州立管理图书馆.
它借用了最初设计和实施的理念和API 斯维尔特商店
并延长他们的寿命 computed 财务报告和审定财务报表 batch。 。 。 .
主要特点:
- 具有可表达性和非常灵活的API(功能和类)的小概念表面;
- 可用于创建“本地”(模块级或组件级),协作商店;
- 能够处理不可移动和可变的数据;
- 产生绝对最低锅炉板的紧凑码.
执行明智,这是一个很小的(1300 LOC)图书馆,没有任何外部依赖.
安装
您可以安装此选项, 将 Tansu 添加到您的工程中 @amadeus-it-group/tansu 使用您最喜欢的软件包管理器的软件包, 例如:
yarn add @amadeus-it-group/tansunpm install @amadeus-it-group/tansu
使用量
看看这个 Tansu API 文档。 。 。 .
API管理您反应状态的功能部分可分为三个不同的组别:
- 基地存储 :
writable - 已计算商店 :
derived, (中文(简体) ),computed, (中文(简体) ),readable - 公用事业:
batch, (中文(简体) ),asReadable, (中文(简体) ),asWritable
可写
书面:基本建筑
A 类 writable 作为“储存”的基础要素——一个容器,旨在封装一个值,允许观察和修改其状态。 您可以使用 set 或者说 update 方法。 。 .
当值发生变化时收到通知 subscribe() 方法,与回调功能相配,可以被使用.
基本用途
import {writable} from "@amadeus-it-group/tansu";
const value$ = writable(0);
const unsubscribe = values$.subscribe((value) => {
console.log(`value = ${value}`);
});
value$.set(1);
value$.update((value) => value + 1);
输出 :
value = 0
value = 1
value = 2
设置和拆解
可写文件的第二个参数允许在至少一个订阅者订阅或没有更多订阅者时收到通知.
import {writable} from "@amadeus-it-group/tansu";
const value$ = writable(0, () => {
console.log('At least one subscriber');
return () => {
console.log('No more subscriber');
}
});
const unsubscribe = values$.subscribe((value) => {
console.log(`value = ${value}`);
});
value$.set(1);
unsubscribe();
输出 :
At least one subscriber
value = 0
value = 1
No more subscriber
来源
A 类 derived 存储根据作为参数提供的一个或多个其他存储计算其值.
由于它的价值来自其他商店,它是一个只读的商店,没有任何 set 或者说 update 方法。 。 .
单行店
import {writable, derived} from "@amadeus-it-group/tansu";
const value$ = writable(1);
const double$ = derived(value$, (value) => value * 2);
double$.subscribe((double) => console.log('Double value', double));
value$.set(2);
输出 :
Double value 2
Double value 4
多个仓库
import {writable, derived} from "@amadeus-it-group/tansu";
const a$ = writable(1);
const b$ = writable(1);
const sum$ = derived([a$, b$], ([a, b]) => a + b);
sum$.subscribe((sum) => console.log('Sum', sum));
a$.set(2);
输出 :
Sum 2
Sum 3
同步集
A 类 derived 可以直接使用设定方法来操纵其值,而不是依赖所提供函数的返回值.
这种灵活性允许您在更新可观测值之前管理同步操作或应用过滤逻辑.
import {writable, derived} from "@amadeus-it-group/tansu";
const a$ = writable(0);
const asynchronousDouble$ = derived(a$, (a, set) => {
const plannedLater = setTimeout(() => set(a * 2));
return () => {
// This clean-up function is called if there is no listener anymore,
// or if the value of a$ changed
// In this case, the function passed to setTimeout should not be called
// (if it was not called already)
clearTimeout(plannedLater);
};
}, -1);
const evenOnly$ = derived(a$, (a, set) => {
if (a % 2 === 0) {
set(a);
}
}, <number | undefined>undefined);
asynchronousDouble$.subscribe((double) => console.log('Double (asynchronous)', double));
evenOnly$.subscribe((value) => console.log('Even', value));
a$.set(1);
a$.set(2);
输出 :
Double (asynchronous) -1
Even 0
Even 2
Double (asynchronous) 4
计算
A 类 computed 储存是衍生商店的另一变种,具有以下特性:
-
隐性依赖: 与衍生商店不同,不要求明确声明依赖关系.
-
动态依赖听觉 : 依附依据其用法确定. 这意味着不积极使用的依赖性不会自动被"征集"到,优化资源利用.
切换映射
这种订阅/不订阅依赖的能力可以自然地创建切换地图.
import {writable, computed} from "@amadeus-it-group/tansu";
const switchToA$ = writable(true);
const a$ = writable(1);
const b$ = writable(0);
const computedValue$ = computed(() => {
if (switchToA$()) {
console.log('Return a$');
return a$();
} else {
console.log('Return b$');
return b$();
}
});
computedValue$.subscribe((value) => console.log('Computed value:', value));
a$.set(2);
switchToA$.set(false);
a$.set(3);
a$.set(4);
switchToA$.set(true);
输出 :
Return a$
Computed value: 1
Return a$
Computed value: 2
Return b$
Computed value: 0
Return a$
Computed value: 4
何时 switchToA$.set(false) 订阅 a$ 已取消,这意味着随后的更改将改为 a$ 将不再触发计算。只有在切换ToA$回到真实状态时才再次进行计算.
可读
与斯维尔特商店类似,此函数生成一个无法外部修改值的商店.
import {readable} from '@amadeus-it-group/tansu';
const time = readable(new Date(), (set) => {
const interval = setInterval(() => {
set(new Date());
}, 1000);
return () => clearInterval(interval);
});
生成 vs 计算
虽然推导和计算可能看起来相似,但它们显示出不同的特征,可以根据使用案例对有效性产生重大影响:
-
属地申报:
computed:不需要明确宣布依赖性,为代码构成提供了更大的灵活性.derived:要求明确宣布依赖关系.
-
性能 :
computed: 仅根据上次运行中涉及的商店的变化,通过重新运行该函数来提高性能.derived:每次依赖的存储更改时重运行该函数.
-
同步状态 :
computed: 无法管理同步状态.derived: 能够处理与set方法.
-
跳过值排放 :
computed: 不提供跳过排放值的机制.derived: 允许通过选择不调用所提供值来跳过值的释放set方法.
-
设置和拆分 :
computed:缺乏明确的设置和拆分方法.derived: 支持设置和拆卸方法,允许添加或去除DOM收听器等动作.- 当最后的收听器取消订阅,然后再次订阅时,由于它的设置-teardown功能,所衍生的函数会被重新运行. 相比之下,一个计算提供了最后一个值而不重算 如果依赖性在此期间没有改变.
虽然 computed 感觉更直观 在许多用例, derived 在下列情况中表现突出: computed 特别是管理同步状态和对价值排放进行更严格的控制.
根据具体要求仔细选择它们可以提高应用程序中国家管理的有效性.
获得值
获得商店价值有三种方法:
import {writable, get} from "@amadeus-it-group/tansu";
const count$ = writable(1);
const unsubscribe = count$.subscribe((count) => {
// Will be called with the updated value synchronously first, then each time count$ changes.
// `unsubscribe` must be called to prevent future calls.
console.log(count);
});
// A store is also a function that you can call to get the instant value.
console.log(count$());
// Equivalent to
console.log(get(count$));
[注] 获取即时值意味着商店上的订阅和未下架:
- 在设置/设置函数时,了解可能很重要.
- 在同一范围, 宁愿将值存储一次到本地变量中, 而不是调用
store$()数次.当在反应语境中(即在计算语境中)被调用时,获得值服务以了解并"倾听"附属商店.
批次
与其他像有信号的Angular或有runes的Svelte这样的图书馆相反,我们维持了商店更改和他们在Tansu的订阅同步的制约,因为订阅的回调是同步执行的(通常被引用为"效果").
虽然这些框架可以接受推迟这些呼吁,因为其目标事先众所周知(以优化其最终表述),但田秀的情况并非如此,因为其目标要适应任何情况.
同步订阅的问题在于它可以创建"滑翔". 用户和计算出的存储回调次数太多,可产生不正确的中间值.
Svelte 商店解决了 钻石依赖问题,但它并不匹配所有使用大小写.
举一例曰:
import {writable, derived} from '@amadeus-it-group/tansu';
const firstName = writable('Arsène');
const lastName = writable('Lupin');
const fullName = derived([firstName, lastName], ([a, b]) => `${a} ${b}`);
fullName.subscribe((name) => console.log(name)); // logs any change to fullName
firstName.set('Sherlock');
lastName.set('Holmes');
console.log('Process end');
输出 :
Arsène Lupin
Sherlock Lupin
Sherlock Holmes
Process end
全数 名称商店相继经历了不同的状态,包括一个不一致的状态,因为 Sherlock Lupin 不存在! 即使它可以被视为仅仅是一个中间状态,它是 基础 州管理层只管理一致的数据,以防止出现问题并优化代码.
在坦苏 batch 可供推迟 同步 订户打电话,事实上是受抚养人 derived 或者说 computed 计算以解决各种多重依赖性问题.
前面的例子是这样解决的:
import {writable, derived, computed, batch} from '@amadeus-it-group/tansu';
const firstName = writable('Arsène');
const lastName = writable('Lupin');
const fullName = derived([firstName, lastName], ([a, b]) => `${a} ${b}`);
// note that the fullName store could alternatively be create with computed:
// const fullName = computed(() => `${firstName()} ${lastName()}`);
fullName.subscribe((name) => console.log(name)); // logs any change to fullName
batch(() => {
firstName.set('Sherlock');
lastName.set('Holmes');
});
console.log('Process end');
输出 :
Arsène Lupin
Sherlock Holmes
Process end
[注]
- 获取商店的直接值( 使用前面提到的三种方法中的任何一种: 调用)
subscribe与将同步调用值的订阅器,使用get或称商店为函数)总是根据所有附属商店的最新价值提供值(即使这需要重算批次内计算出或衍生出的计算值)- 所有给订阅者的呼叫(不包括订阅过程中的第一次同步呼叫)被推迟到批次结束
- 如果一个订阅者已经得到该批内新值的通知(例如,当它是该批内登记的新订阅者时),如果该值保持不变,则在该批后期将不会再次得到通知. 订阅者只有在店面价值自其最后一次调用后有所变化时才会被引用.
batch可以在内部调用batch。订阅者的呼叫在第一批的末尾同步进行.
可读
asReadable 返回只披露订阅该商店所需的基本要素的新商店。 它还包括作为参数通过的任何其他方法.
这是有用的,并被广泛用来组成一个自定义商店:
- 第一个参数是可写存储,
- 第二个参数是可读存储返回的扩展对象.
import {writable, asReadable} from "@amadeus-it-group/tansu";
function createCounter(initialValue: number) {
const store$ = writable(initialValue);
return asReadable(store$, {
increment: () => store$.update((value) => value + 1),
decrement: () => store$.update((value) => value - 1),
reset: () => store$.set(initialValue)
});
}
const counter$ = createCounter(0);
counter$.subscribe((value) => console.log('Value: ', value));
counter$.increment();
counter$.reset();
counter$.set(2); // Error, set does not exist
输出 :
Value: 0
Value: 1
Value: 0
(Error thrown !)
可写入
asWritable 几乎与一个 asReadable,关键区别在于执行 可写符号 接口.
当您想要将您的计算存储连接到原存储时, 或者执行自定义时, 会有帮助 set 方法。 这个 set 方法可以在第二个参数中或对象内直接传递,类似于在 asReadable。 。 。 .
例如:
import {writable, asWritable} from "@amadeus-it-group/tansu";
const number$ = writable(1);
const double$ = computed(() => number$() * 2);
const writableDouble$ = asWritable(double$, (doubleNumber) => {
number$.set(doubleNumber / 2);
});
/* equivalent to:
const writableDouble$ = asWritable(double$, {
set: (doubleNumber) => number$.set(doubleNumber / 2)
});
*/
writableDouble$.subscribe((value) => console.log('Value: ', value));
writableDouble$.set(2); // Nothing is triggered here, as number$ is already set with 1
writableDouble$.set(4);
输出 :
Value: 2
Value: 4
纳入框架
田生与斯维尔特框架合作得很好
田生被设计为并保持与斯维尔特完全相容.
田生与角生态系统合作得很好
以下是一个使用 Tansu 商店的角构件的例子:
import { Component } from "@angular/core";
import { AsyncPipe } from '@angular/common';
import { Store, computed, get } from "@amadeus-it-group/tansu";
// A store is a class extending Store from Tansu
class CounterStore extends Store<number> {
constructor() {
super(0); // initialize store's value (state)
}
// implement state manipulation logic as regular methods
increment() {
// create new state based on the current state
this.update(value => value + 1);
}
reset() {
// replace the entire state with a new value
this.set(0);
}
}
@Component({
selector: "my-app",
template: `
<button (click)="counter$.increment()">+</button> <br />
<!-- store values can be displayed in a template with the standard async pipe -->
Counter: {{ counter$ | async }} <br />
Double counter: {{ doubleCounter$ | async }} <br />
`,
standalone: true,
imports: [AsyncPipe]
})
export class App {
// A store can be instantiated directly or registered in the DI container
counter$ = new CounterStore();
// One can easily create computed values by specifying a transformation function
doubleCounter$ = computed(() => 2 * get(this.counter$));
}
这个例子虽然相当少,但表明大部分的Tansu API带有Angular.
- 工作与标准
async烟斗出盒子 - 在任何级别(模块或组件注射器)的依赖性注射容器中均可注册储存
- 存储可以很容易地与 rxjs 一起使用,因为它们执行
InteropObservable接口 - 反之,rxjs可观测到(或任何执行
InteropObservable界面)与 Tansu(例如 Tansu) 很容易使用computed或者说derived) (中文(简体) ).
对项目的贡献
请检查一下 D. 发 展 在本地开发机器上建立和测试项目的文件.