图像
类型安全,用于TypeScript中不可变数据的功能光学.
- 镜头 ——全面注重所需价值
- 棱镜 - 部分侧重于任择或工会价值
- 伊苏 ——两种类型之间总,不可逆的测绘
- 倾斜 —— 聚焦于零或以上的值( 如数组的所有元素)
- 盖特 ——只读总焦点(计算值/出值)
- 折叠 ——只读多焦点(摘录后不加修改)
所有光学作品都通过独立自由编曲 compose 函数。 四个人类基因组组合器—— guard, (中文(简体) ), at, (中文(简体) ), index,以及 each ——涵盖最常见的建筑模式.
安装
# npm
npm install @fuiste/optics
# pnpm
pnpm add @fuiste/optics
# yarn
yarn add @fuiste/optics
# bun
bun add @fuiste/optics
发展
- 软件包管理器 :
pnpm - 支持的节点.js:
>=20.19.0
快开始
镜头( 所需数据)
import { Lens } from '@fuiste/optics'
type Person = {
name: string
age: number
address: { street: string; city: string }
}
const nameLens = Lens<Person>().prop('name')
const person: Person = { name: 'John', age: 30, address: { street: '123', city: 'NYC' } }
nameLens.get(person) // 'John'
nameLens.set('Jane')(person) // { name: 'Jane', age: 30, address: { ... } }
nameLens.set((name) => name.toUpperCase())(person) // name == 'JOHN'
棱镜( 可选数据)
import { Prism } from '@fuiste/optics'
type Person = {
name: string
address?: { street: string; city: string }
}
const addressPrism = Prism<Person>().of({
get: (p) => p.address,
set: (address) => (p) => ({ ...p, address }),
})
addressPrism.get({ name: 'A' }) // undefined
addressPrism.set({ street: '456', city: 'LA' })({ name: 'A' })
// => { name: 'A', address: { street: '456', city: 'LA' } }
// Functional updater — no-op when absent
addressPrism.set((addr) => ({ ...addr, city: 'LA' }))({
name: 'A',
address: { street: '1', city: 'NYC' },
})
// => { name: 'A', address: { street: '1', city: 'LA' } }
Iso (不可逆地绘图)
import { Iso } from '@fuiste/optics'
const numberString = Iso<number, string>({ to: (n) => `${n}`, from: (s) => parseInt(s, 10) })
numberString.to(42) // '42'
numberString.from('7') // 7
倾斜( 多值)
import { Lens, compose, each } from '@fuiste/optics'
type Team = { members: string[] }
const membersLens = Lens<Team>().prop('members')
const eachMember = each<string>()
const allMembers = compose(membersLens, eachMember)
const team: Team = { members: ['Alice', 'Bob'] }
allMembers.getAll(team) // ['Alice', 'Bob']
allMembers.modify((n) => n.toUpperCase())(team)
// => { members: ['ALICE', 'BOB'] }
Getter (计算只读值)
import { Lens, Getter, compose } from '@fuiste/optics'
type Person = { firstName: string; lastName: string }
type Team = { lead: Person }
const fullName = Getter<Person, string>((p) => `${p.firstName} ${p.lastName}`)
const leadLens = Lens<Team>().prop('lead')
const leadName = compose(leadLens, fullName)
leadName.get({ lead: { firstName: 'Alice', lastName: 'Smith' } }) // 'Alice Smith'
// leadName has no `set` — it's read-only
折叠( 只读取许多值)
import { Fold } from '@fuiste/optics'
const words = Fold<string, string>((s) => s.split(' '))
words.getAll('hello world') // ['hello', 'world']
组成
所有光学都通过独立制作 compose(outer, inner) 函数。 返回类型自动确定 :
| 外 内 | 镜头 | 棱镜 | 伊苏 | 倾斜 | 盖特 | 折叠 |
|---|---|---|---|---|---|---|
| 镜头 | 镜头 | 棱镜 | 镜头 | 倾斜 | 盖特 | 折叠 |
| 棱镜 | 棱镜 | 棱镜 | 棱镜 | 倾斜 | 折叠 | 折叠 |
| 伊苏 | 镜头 | 棱镜 | 伊苏 | 倾斜 | 盖特 | 折叠 |
| 倾斜 | 倾斜 | 倾斜 | 倾斜 | 倾斜 | 折叠 | 折叠 |
| 盖特 | 盖特 | 折叠 | 盖特 | 折叠 | 盖特 | 折叠 |
| 折叠 | 折叠 | 折叠 | 折叠 | 折叠 | 折叠 | 折叠 |
拇指规则 :
- 任何东西 折叠 - 折叠(只读会传染)
- 盖特 + 部分光学-折叠; 盖特 + 光学合计 + Getter
- 倾斜 吸收其他可书写光学 + 倾斜
- 伊苏 透明: 其他光学类的获胜
- 连线 连线 →;其他一切与棱相通
import { Lens, Prism, Iso, compose } from '@fuiste/optics'
type Address = { street: string; city: string }
type Person = { name: string; address?: Address }
const addressPrism = Prism<Person>().of({
get: (p) => p.address,
set: (address) => (p) => ({ ...p, address }),
})
const cityLens = Lens<Address>().prop('city')
// Prism ∘ Lens => Prism
const cityPrism = compose(addressPrism, cityLens)
cityPrism.get({ name: 'A', address: { street: '1', city: 'NYC' } }) // 'NYC'
cityPrism.get({ name: 'A' }) // undefined
cityPrism.set('LA')({ name: 'A' }) // unchanged (missing path is a no-op)
// Lens ∘ Iso => Lens
type Model = { count: number }
const countLens = Lens<Model>().prop('count')
const numberString = Iso<number, string>({ to: (n) => `${n}`, from: (s) => parseInt(s, 10) })
const countAsString = compose(countLens, numberString)
countAsString.get({ count: 7 }) // '7'
countAsString.set('10')({ count: 7 }) // { count: 10 }
多步骤链
作曲可以被嵌入深道:
import { Lens, compose, each } from '@fuiste/optics'
type Company = { employees: Array<{ name: string; role: string }> }
const employeesLens = Lens<Company>().prop('employees')
const eachEmployee = each<{ name: string; role: string }>()
const empName = Lens<{ name: string; role: string }>().prop('name')
const allEmployeeNames = compose(compose(employeesLens, eachEmployee), empName)
allEmployeeNames.getAll(company) // ['Alice', 'Bob', ...]
allEmployeeNames.modify((n) => n.toUpperCase())(company)
组合器
guard ——类型护卫棱镜
从 TypeScript 类型守护创建棱镜。 比手工写作要多得多 Prism().of(...) 歧视工会.
import { guard, Lens, compose } from '@fuiste/optics'
type Circle = { type: 'circle'; radius: number }
type Square = { type: 'square'; side: number }
type Shape = Circle | Square
const circlePrism = guard<Shape, Circle>((s): s is Circle => s.type === 'circle')
circlePrism.get({ type: 'circle', radius: 5 }) // { type: 'circle', radius: 5 }
circlePrism.get({ type: 'square', side: 4 }) // undefined
// Compose with a lens
const circleRadius = compose(circlePrism, Lens<Circle>().prop('radius'))
circleRadius.get({ type: 'circle', radius: 5 }) // 5
circleRadius.set(10)({ type: 'circle', radius: 5 }) // { type: 'circle', radius: 10 }
at ——记录密钥访问
创建以密钥为重点的棱镜 Record<string, V>. 回归 undefined 键缺失时; 调用时设置/设置.
import { at, Lens, compose } from '@fuiste/optics'
type Config = { headers: Record<string, string> }
const headersLens = Lens<Config>().prop('headers')
const authHeader = at<string>('Authorization')
const configAuth = compose(headersLens, authHeader)
configAuth.get({ headers: { Authorization: 'Bearer x' } }) // 'Bearer x'
configAuth.get({ headers: {} }) // undefined
configAuth.set('Bearer y')({ headers: {} })
// => { headers: { Authorization: 'Bearer y' } }
index ——阵列元素访问
创建一个侧重于单一数组元素的棱镜. get 返回时 undefined 当指数离谱的时候 set 元素缺失时为无操作.
import { index } from '@fuiste/optics'
const second = index<number>(1)
second.get([10, 20, 30]) // 20
second.get([10]) // undefined
second.set(99)([10, 20, 30]) // [10, 99, 30]
second.set(99)([10]) // unchanged
each 横向阵列
创建一个横跨 a 的所有元素 ReadonlyArray<A>。 。 。 .
import { each, compose, Lens } from '@fuiste/optics'
const nums = each<number>()
nums.getAll([1, 2, 3]) // [1, 2, 3]
nums.modify((n) => n * 2)([1, 2, 3]) // [2, 4, 6]
API 参考
类型
type Lens<S, A> = {
_tag: 'lens'
get: (s: S) => A
set: (a: A | ((a: A) => A)) => (s: S) => S
}
type Prism<S, A> = {
_tag: 'prism'
get: (s: S) => A | undefined
set: (a: A | ((a: A) => A)) => (s: S) => S
}
type Iso<S, A> = {
_tag: 'iso'
to: (s: S) => A
from: (a: A) => S
}
type Traversal<S, A> = {
_tag: 'traversal'
getAll: (s: S) => ReadonlyArray<A>
modify: (f: (a: A) => A) => (s: S) => S
}
type Getter<S, A> = {
_tag: 'getter'
get: (s: S) => A
}
type Fold<S, A> = {
_tag: 'fold'
getAll: (s: S) => ReadonlyArray<A>
}
type Optic<S, A> = Lens<S, A> | Prism<S, A> | Iso<S, A> | Traversal<S, A> | Getter<S, A> | Fold<S, A>
工厂
// Lens factory — use .prop to focus on a property key
Lens<S>().prop<K extends keyof S>(key: K): Lens<S, S[K]>
// Prism factory — use .of to build from get/set
Prism<S>().of<A>({ get, set }): Prism<S, A>
// Direct constructors
Iso<S, A>({ to, from }): Iso<S, A>
Traversal<S, A>({ getAll, modify }): Traversal<S, A>
Getter<S, A>(get: (s: S) => A): Getter<S, A>
Fold<S, A>(getAll: (s: S) => ReadonlyArray<A>): Fold<S, A>
独立职能
// Universal composition — 36 overloads, result type inferred from inputs
compose<S, A, B>(outer: Optic<S, A>, inner: Optic<A, B>): Optic<S, B>
// Type-guard prism
guard<S, A extends S>(predicate: (s: S) => s is A): Prism<S, A>
// Record key prism
at<V>(key: string): Prism<Record<string, V>, V>
// Array index prism
index<A>(idx: number): Prism<ReadonlyArray<A>, A>
// Array element traversal
each<A>(): Traversal<ReadonlyArray<A>, A>
工具类型
InferSource<O extends Optic> // Extract the S from any optic
InferTarget<O extends Optic> // Extract the A from any optic
行为说明
Lens#set财务报告和审定财务报表Prism#set都接受一个值或(a) => a更新器功能并返回新对象。 原作从未变异.- 可书写光学在公共设置签名中保存了呼叫者的源形状,以与收缩状态相兼容.
Prism#get可能返回undefined在由构成的棱镜中,任何缺失的外分枝都会导致undefined。 。 。 .Prism#set在外分枝缺失的情况下 无 -op 默认。 函数更新器缺少时也是无操作的 .- 例外——棱镜-伊索:通过外棱镜提供具体价值
set即使当get返回时undefined,因为Iso总是可以构造中间值. 函数更新器缺失时仍为禁用 . Traversal#modify将函数应用于每个焦点元素。 对于通过缺失的棱柱分枝而构成的曲面,修改是没有的.- 当库能发现焦点值没有改变时,未更改的更新会保留引用身份.
Getter财务报告和审定财务报表Fold——他们没有set或者说modify。用只读光学制取任何光学效果.
最佳做法
- 小光学的构成优于一个大定制的取取取器/取取取器
- 使用
guard歧视的工会,而不是手工工会Prism().of - 使用
each+ 键compose用于散装阵列操作 - 使用
index()用于单个阵列元素和at()用于记录/映射密钥 - 使用
Getter对于不应固定的衍生值 - 将光学视为纯度: 绝不改变内部输入
set - 对于数组,偏好
each()用于所有元素或index(i)一个元素.Lens<T[]>().prop(index)仍然支持兼容性.