资讯专栏INFORMATION COLUMN

「State」之我见

Binguner / 388人阅读

摘要:而是假设我们创建的都是一种名为的首类元素,它应当可以作为函数的参数或返回值进行传递,而不仅仅只是传递其计算值,即满足其身为的特性可以被其它引用它的函数或对象观察到它的变化当然,目前中并不存在这样的首类元素。

原文:what-is-my-state

阅读前须知

本文献给对前端状态管理 state management 有思考的同学。

文章有涉及 函数式编程响应式编程 概念

原文是 slide,所以是言不成章的。本文为了通顺,加了一些过渡。还有,由于 slide 常用于演讲,所以文字说明不是很多。我补上了一些个人的理解(以引用块的样式),但也不是很多,有时候会再出文章解释一些术语,如 lensatom 等。

文中的 state 和「状态」是同义的,有时为了强调和更易于理解,保留了这一术语未翻译,读者请自行脑内替换。

本文中的「我」指原作者

口味调查

在我给出我的口味前,下面几个矛盾,你会怎么选择?

无状态 vs 状态化
程序是基于状态的,所以它不可能被完全地清除,但它必须被管理起来。

可变状态 vs 不可变状态
状态随着时间而变化,所以不可变状态这个说法是自相矛盾的。人们可以在状态的一个点上捕捉到不可变的值,但状态本身并不全部不可变。

全局状态 vs 局部状态
来自外部的、共享的、全局状态实际上优于被封装在内部的本地状态。这也是本篇文章要讨论的要点之一。

前情提要

这篇文章不会提出新发明。

Most papers in computer science describe how their author learned what someone else already knew. — Peter Landin

我们的讨论基于我在 Calmm 中的实践
Calmm 是一个用于编写响应式 UI 的框架。鼓励使用外部共享的状态,和持续可观察的属性(continuous observable properties)。

在赞美 Calmm 之前,我们需要达成一些共识

局部状态有毒

为什么不用事件

本文的目标

希望咱们能从一个崭新的角度讨论 state ?

State 什么是 state

has value,有值

has identity,有索引

can change over time, 随着时间会变化

状态管理难在哪里?

值、索引和时间相互交织,索引和时间尤其复杂。

追踪索引常常导致算法复杂化,比如 React 中的 key

随着时间变化,依赖于状态的一些计算会无效化

语言层面的局限

一般的语言 (比如 js)对 state 这种数据基本都不做原生支持。

这体现在,在这些语言中:

变量是可变的、对象上的字段也是可变的

根本上来说,是次类元素

无法组合

无法分形(decompose)

无法(随着时间)响应变化

什么叫次类元素?

这个说法对应于首类元素 first-class,它

无法通过函数返回

无法作为参数传递

演示局限

无法(随着时间)响应变化

let x = 1       // 创建了一个可变的 state
let y = 2
let sum = x + y // 获取了 state 的快照 snapshot,值为 3

x = 3

sum             // 值还是 3,sum 无法观察 x 赋值后的值,随之变化值为 5

state 不是语言中的 first-class 元素

function foo() {
  let x = 1 // 创建可变的 state
  bar(x)    // 无法将 state 作为参数传递,只能传递值,即 1

  x = 2     // 修改了 state ,但这对于函数 bar 来说是不可知的

  return x  // 也无法将 state 作为返回,只能返回值,即 2
}

如果你了解 js ,知道变量区分值类型和引用类型、形参实参的分别,那么就不会觉得上面的代码有任何奇怪的地方。甚至你会觉得如果 x 重新赋值后, sum 会随之变化为 5、对已经调用完毕的 bar 还能产生影响,那才太可怕了。

但其实上面的代码,并不是在挑战这些东西。而是假设我们创建的 x 都是一种名为 state 的首类元素,它应当可以

作为函数的参数或返回值进行传递,而不仅仅只是传递其计算值,即满足其身为 first-class 的特性

可以被其它引用它的函数或对象观察到它的变化

当然,目前 js 中并不存在这样的首类元素。

Make State Fun Again

neta Make American Great Again, 哈哈

我们试试在 js 中模拟出 State
下文代码都是 typescript

State Interface
interface State {
  get(): T;
  set(value: T): void;
}
构造首类元素 state

我们已经说过首类元素的特性了,可以作为函数的参数和返回值传递。

class Atom {
  constructor(value) {
    this.value = value
  }
  get() {
    return this.value
  }
  set(value) {
    this.value = value
  }
}

现在在组件中,我们就可以声明一个 state 来作为参数了。

Observable state
class Atom {
  constructor(value) {
    this.value = value
    this.observers = []
  }
  get() { return this.value }
  set(value) {
    this.value = value
    this.observers.forEach(observer => observer(value))
  }
  subscribe(observer) {
    observer(this.get())
    this.observers.push(observer)
  }
}

state 能独立于时间变化了(Independence from time)

可分形的 state

decomposable

class LensedAtom {
  constructor({getter, setter}, source) {
    this.getter = getter
    this.setter = setter
    this.source = source
  }
  get() {
    return this.getter(this.source.get())
  }
  set(value) {
    this.source.set(this.setter(value, this.source.get()))
  }
}

把 store state 作为一个整体,而其分片的元素作为组件的 state

可组合的 state
class PairAtom {
  constructor([lhs, rhs]) {
    this.lhs = lhs
    this.rhs = rhs
  }
  get() {
    return [this.lhs.get(), this.rhs.get()]
  }
  set([lhs, rhs]) {
    this.lhs.set(lhs)
    this.rhs.set(rhs)
  }
}

事务性

独立于存储

全局状态的场景 为什么说全局状态更好?

组件因此可以无状态、可以方便地组合

全局状态更容易检查

一切对全局状态的操作测试起来都很简单

全局状态是稳健的单一数据源

为什么不用局部状态

局部状态无法从外部访问

很难组合

只能间接地去测试局部状态

很容易变得散乱

常见的误解 流(streams)是无状态的

一般我们认为 stream 是无状态的,但是请看:

这是无状态的吗?

merge + scan 引入了局部状态

组织很容易变得散乱

时间变得很重要

不过,从好的方便来说:

它可观察

可以使得依赖更精确:可以方便地观察「是什么触发了这个 stream ?」。

但是没必要。

任何人都可以修改状态将会是一团糟

是的,在我们的方案里,任何人得到了一个 state 的分片,都可以修改它。
但是在 calmm 中,我们已经

(限定了)作用域
我们通过参数赋予组件一部分 state,组件只能修改这部分 state,而不是全部

(宣告了)意图
如果你把可变 state 传递给了组件,这相当于就宣告说,你允许在这个组件中修改 state

观察(了变化)
即使有人修改了 state,组件也能观察 state 的变化并随之应变。

课后思考

思考下,你到底想把 state 存储在哪里?

同时,你的组件如何持久化 state 呢?

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/91736.html

相关文章

  • 《CDN 我见》系列二:原理篇(缓存、安全)

    摘要:真正要做高性能的系统,不仅需要在数据结构与算法层面深入,更要从硬件操作系统文件系统底层原理等多个领域做更多的研究例如阿里云自研的系统使用了裸盘技术。 《CDN之我见》共由三个篇章组成,分为原理篇、详解篇和陨坑篇。本篇章适合那些从未接触过、或仅了解一些 CDN 专业术语,想深入了解和感受 CDN 究竟是什么的同学。本次由白金老师继续为大家分享《CDN之我见》系列二,主要讲解缓存是什么、工...

    maxmin 评论0 收藏0
  • 《CDN 我见》系列二:原理篇(缓存、安全)

    摘要:真正要做高性能的系统,不仅需要在数据结构与算法层面深入,更要从硬件操作系统文件系统底层原理等多个领域做更多的研究例如阿里云自研的系统使用了裸盘技术。 《CDN之我见》共由三个篇章组成,分为原理篇、详解篇和陨坑篇。本篇章适合那些从未接触过、或仅了解一些 CDN 专业术语,想深入了解和感受 CDN 究竟是什么的同学。本次由白金老师继续为大家分享《CDN之我见》系列二,主要讲解缓存是什么、工...

    rainyang 评论0 收藏0
  • 初级理解async实现我见

    摘要:在我们写项目代码的过程中,要经常请求接口数据,在某些异步请求数据之后,将得到的值进行处理。 在我们写项目代码的过程中,要经常请求接口数据,在某些异步请求数据之后,将得到的值进行处理。通俗的一句话就是,我要把这个值放到另一个函数中,按行数顺序处理,即同步的概念! 例子:第一步,涉及异步函数 假设我有一个函数abc, function abc(){ //异步方法,请求数据得到re...

    CastlePeaK 评论0 收藏0
  • Spring框架我见(五)——Spring Boot

    摘要:通过我们可以更轻松地入门,更简单的使用的框架。团队为了摆脱框架中各类繁复纷杂的配置,使用约定优于配置的思想,在基础上整合了大量常用的第三方库的开发框架。这里还要说的一点,的出现并不是单纯的为了简化开发,更是为做铺垫。 说完了Spring 我们来聊聊Spring的进阶版Spring Boot,如果你还不知道Spring Boot,那希望这篇文章能够为你指明方向。 Spring Boot ...

    张巨伟 评论0 收藏0
  • Spring框架我见(一)——工厂模式

    摘要:相对于工厂模式,抽象工厂模式生产的对象更加具体,也更加丰富,但相对编码也更加复杂。具体的抽象工厂模式的实现大家可以参考菜鸟教程。知道了工厂模式和抽象工厂模式的区别,请大家使用的时候应该根据具体的情况进行选择。 大家好,今天给大家分享一些Spring的学习心得,在讲Spring之前,先和大家分享Spring中核心的设计模式。 工厂模式 在聊概念之前我先问问大家:什么是工厂? 这个很简单,...

    venmos 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<