@fuiste/optics

中文文档
2.0.0发布于 2026/04/11MITTypeScriptES Module零依赖近 30 天下载 7274

类型安全、功能性光学(光学、棱镜、转录等),用于不可改变的数据

中文使用文档
文档目录23 节
  1. 安装
  2. 发展
  3. 快开始
  4. 镜头( 所需数据)
  5. 棱镜( 可选数据)
  6. Iso (不可逆地绘图)
  7. 倾斜( 多值)
  8. Getter (计算只读值)
  9. 折叠( 只读取许多值)
  10. 组成
  11. 多步骤链
  12. 组合器
  13. guard ——类型护卫棱镜
  14. at ——记录密钥访问
  15. index ——阵列元素访问
  16. each 横向阵列
  17. API 参考
  18. 类型
  19. 工厂
  20. 独立职能
  21. 工具类型
  22. 行为说明
  23. 最佳做法

图像

类型安全,用于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) 仍然支持兼容性.

版本与发布记录

@fuiste/optics 共发布 5 个版本,首次发布于 2025/09/19;过去 12 个月发布了 1 个版本。

版本发布日期距今
2.0.0latest2026/04/117 个月前
1.0.12025/09/241.1 年前
1.0.02025/09/191.2 年前
0.1.12025/09/191.2 年前
0.1.02025/09/191.2 年前

关于 @fuiste/optics 的常见问题

@fuiste/optics 是什么?

类型安全、功能性光学(光学、棱镜、转录等),用于不可改变的数据

如何安装 @fuiste/optics?

在项目目录运行 npm install @fuiste/optics,或使用 pnpm add / yarn add / bun add。

@fuiste/optics 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@fuiste/optics 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@fuiste/optics 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=20.19.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@fuiste/optics 可以商用吗?

@fuiste/optics 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@fuiste/optics 的下载量有多少?

根据 npm 官方下载统计,@fuiste/optics 在 2026/10/16 – 2026/11/14 这 30 天内被下载 7,274 次,日均约 242 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@fuiste/optics 还在维护吗?

最新版本 2.0.0 发布于 2026/04/11(7 个月前);至今共发布 5 个版本,其中过去 12 个月发布了 1 个;首次发布于 2025/09/19。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。