React应用的五类状态

react

我不知道你是怎样,总之当我开始写React程序的时候,我纠结了很久我的状态怎么处理。不管我怎么组织setState调用,总是觉得事情有点不对。

也许这正是为什么我遇到Redux后倍感兴奋的原因。Redux为我提供了一个唯一的地方存储我的状态。理论上听起来很棒。

但随后我就意识到,把它们放到一起并没有真正让它们变得容易使用。

接下来我发现,我需要多个地方而不是一个地方来保存我的状态。除此之外,我还需要一个系统来把它们放到正确的地方。

对我来讲,这个系统来自于把我的状态分成五类。这样,解决问题“这个状态和其他状态有什么关系?”就转化成了解决问题“这个状态属于哪一类状态?” 显然,这个问题的解决就简单多了。

好了,现在我们就可以进行处理了。React应用有五类状态。每类状态遵循一定的规则。每种状态用特定的方式跟其他状态交互只要它符合特定的规则即可。而且基于这些规则,有很多种方案都可以存储这些状态,都能说得通。

好了,闲话少说,上干货,这五类状态是:

  • 数据 Data
  • 通讯 Communication
  • 控制 Control
  • 会话 Session
  • 位置 Location

这五类状态中,”数据(Data)”状态其实真的需要一个更合适的命名。不过它也是最好解释的一类状态。我们就先从它开始讲解。

数据 – Data

数据状态覆盖了你的应用程序临时存储的大千世界的信息。换句话说,它覆盖了你的业务数据。

现在比如你正在开发一套很酷炫的发票应用。数据状态的例子对于你的这个应用来讲就会包含来自服务器的这些内容:发票,联系信息,收据等等。而且,所有你的这些数据都来自外部世界,极大可能你在获取这些数据的时候都得使用某种手段的标识信息,比如GET /invoice?id='0099309'或者类似的其他什么东西。

事实上,我打算把数据状态的规则神圣地定义成这样来记忆:
接收到每部分数据都会有一个类型和一个准确地定义接受了什么样的数据的选择器。

Every piece of received Data has a type, and a selector which exactly specifies which data was received.

基于此事实,很容易设计出一个存储数据的Redux仓库(store);最小情况下,它包含将一个类型和一个id映射到一个接收到的对象的方法。也会包含接收到对象时更新仓库的指令(action)。

不错,现在我们有一个从应用程序的任何地方使用connect都可以访问的数据仓库了。另外,只要你的数据符合数据状态的规则,你甚至给它加上索引或者定制一些高阶组件都没问题。

当然,数据不会像变魔术那样凭空出现在一个应用中,我们需要请求获取它并等到请求成功或者失败返回。好,这就到了我们讲解通讯状态Communication state的时候了。

通讯状态 – Communication state

这类状态覆盖了看似简单然而却多少有点让人头痛的信息比如加载spinner和错误信息。它也覆盖了你可能还没意识到你已经进入到应用程序中的状态,比如那些当你给一个HTTP请求传递一个回调函数时引入到你的应用中的状态。

上面的解释有点复杂了。简单点儿说,我们还是先忘掉通讯状态是干嘛的,现在来想想它是什么。实际上,通讯状态的规则是这样的:

通讯状态是对其他服务的未完成的请求的状态。

Communication state is the status of any not-yet-complete requests to other services.

这句话意味着下面这几类都是通讯状态:

  • 预期接收到的数据的类型和选择器 (type/selector)
  • 类型,选择器,和你在目标数据上的操作请求带来的任何预期的变化
  • 任何没有按照你的计划出现的错误信息

这样定义通讯状态有几大好处:

1. 你可以使用一个简单地管理一个Request对象数组的Redux reducer存储通讯状态。

2.你现在可以使用一个操作数据状态和通讯状态的纯函数计算Redux仓库中你的任意数据的状态(提取,更新,或者其他任何玩意儿)。

3.严肃地讲,你再也不用写这样的代码了setState({ saving: true/false })

所以,通讯状态和数据状态都可以被实现成由Redux管理的独立的仓库。它们都可以被你的整个应用程序使用connect来访问。在这里我开始看到了一个模式–也许每种类型的状态都适合做成整个应用程序都可以使用的某类Reducer?要真是这么简单,那就他好了。

控制状态 – Control state

跟上面两类状态不同,控制状态不代表应用程序环境。相反,它指用户输入到应用程序中的状态。表单输入,选择项之类的。

什么东西?是的,你可能注意到了,表单和选择项是完全不同类型的信息。选择项很可能的形式是表示id的一个字符串,而表单可能是很大的嵌套对象。但这就是控制状态—形状不规则,没什么模式可循。

不过幸运的是,它确实也遵循了其他的一些模式。你知道,控制状态一般总是跟某一个视图,屏幕或者容器组件(container component)。这样,我们就得到了控制状态的规则:

控制状态是那些跟特定容器组件相关的状态,既不保存在屏幕的URL中,也不保存在HTML5 History API中。

Control State is state which is specific to a given container component, and which is not stored in the screen’s URL or in the HTML5 History API.

很好,现在我们有了一个可以工作的模式了。现在我们需要使用这个模式写我们视图的Redux reducer吗?想一下,一旦你有答案,用鼠标点击或者悬停到下面的黄色框上检查一下。

你没有答案。(译注:原文中,作者在这里做了一个动态效果,这里展示一个黄色框,当鼠标悬停或者点击时,显示该文字。这里因为CSDN编辑器的原因,我没办法翻译该效果,所以直接展示这些文字。)

你确实想了一会儿吗?那有点对不起了。但是我认为这里确实有需要着重指出的一个点,并且Redux的作者Dan Abramov本人也这么认为:

不要使用Redux除非使用React原生的机制解决不了。

(Don’t use Redux until you have problems with vanilla React)

还记得数据状态,通讯状态是为什么有必要对整个应用程序可用吗?控制状态仅仅需要对特定的视图可用。这也就是说,setState,React原生的机制,就很完美了。

事实上,刚才我掩盖了重要的一点,这里让我把它说的更透彻一些:

你没有必要使用同一个方式保存每类状态,真的。

你有外形可预见并且需要对整个应用程序可用的状态吗?那就使用Redux管理它。那么,如果状态外形不可预见并且仅局限于某个视图呢?那就是用setState。可能你还会有一些状态有可能在应用程序的各处都可用,但是外形又不可预见,那怎么办呢?这个……

会话状态 – Session state

当你的信息需要在应用程序中到处可用,但是它的外形又在你的项目计划中很难很好地定义,那他很可能是会话状态。

或者换种方式来讲,我们认为会话状态的第一原则是:

会话状态包含了这样的信息,这些信息跟当前正在使用你的应用的人相关。

(Session state contains information about the human being which is currently using your application.)

这些内容显而易见,用户id,访问权限等等。当然也可以包含用户希望应用程序如何工作的偏好设置。

现在有一件事情是会话状态中某一部分可能和控制状态很像。比如,控制状态的一部分用于表达树形视图的某一块是展开还是收起。在会话状态中,可能你需要表达同样的信息。但这我敢说这里还是有些不同存在。现在,如果看了下面会话状态第二原则,你自己就可以指出不同:

会话状态仅在一个组件挂载时被读取。
Session state is only ever read when a component is mounted.

显然,这意味着你的树形视图展开还是折叠的会话状态版本是一个控制状态版本的复制品。当然,你的视图如果想写入会话状态那它随时都可以这么干。但对于读,它仅仅需要把它读出来用于初始化视图。或者换句话讲:会话状态可以用来记录偏好设置。

OK,酷。那么现在会话状态怎么保存呢?像这个样子:

会话状态像雨来自天上。在React世界中,这意味着他很可能只是你的组件上下文中的一个普通对象。你很可能想采用一些方法让它在页面重新加载时不会消失,不过我肯定你不难找到这些方法。不过在你动手之前,让我们看看所有状态类型中最坏最刻薄低劣的状态类型。

位置状态 – Location state

什么算作位置(location)?直观地讲,我会想说是”任何你可以用来给别人指出具体方向的东西”。但是像这种空洞无聊的解释给我们带不来什么有价值的东西。现在我们尝试把它弄得更具体一些:

位置状态是你的地址栏中乱七八糟的那坨UTF-8字符串。

(原文:Location state is that UTF-8 mess which appears in your URL bar.)

这个定义包含了以下内容:

  • 这坨UTF-8定义了你应用程序的状态
  • 把它分享给其他用户就像是指路
  • URL中的L表示的是定位的意思

但是不管这个定义怎么好,它还不完善。首先,它没有考虑这样的事实,你可以给人们指路到你的应用程序中没有唯一对应的URL的部分;另外,HTML5 History API实际上将你的存储状态跟URL分开了。通常,你使用一个叫做pushState的方法这么做。

坦白地讲,我还没有成功地找到位置状态的一个合适的定义。所以我从实用的角度上提出了一个。碰巧,它也是位置状态的原则:

位置状态是保存在URL里面和 HTML5 History状态对象里面的信息。
Location state is the information stored in the URL and the HTML5 History state object

Handily, this definition covers both the type of information which is stored, as well as the method for storing it. Unfortunately, it doesn’t help us with the structure. Instead, it forces us into storing one of the most important pieces of state in our entire application as a bloody string.

And it goes without saying that no competent webmaster is going to build a large scale web application with routing based on window.location.hash.indexOf. But one of the funny things about URLs is that while they’re stored as strings, they don’t represent strings. They actually represent a hierarchy. And that happens to overlap with your application’s component hierarchy.

In fact, by combining the hierarchy stored in URLs with the ability to store extra location with pushState, you can build a location tree which maps to a (hopefully proper) subset of your application’s component tree. Or to put it another way, your location state ends up being a set of flags which enable and disable branches of your application. Simple, right?

It would be, if it wasn’t for side effects.

副作用 – Side Effects

Of our five types of state, four of them mostly mind their own business. Changes to Data, Communication, Control and Session state will generally not cause other types of state to change as well.

Location state is a whole different ball game. Every time it changes, you’ll find other state changing too:

  • Changing Location mounts container components, causing Control state to change
  • Changing Location makes HTTP requests which cause Communication state to change, before also causing Data state to change
  • Changing Location can even cause Location to change! That’s what redirects do.

延伸阅读

  • State of React #1: A stateless React app?
  • State of React #2: From Inception to Redux
  • Presentational and Container components
  • React Context
  • The HTML5 History API

英文原文

以上是 React应用的五类状态 的全部内容, 来源链接: utcz.com/z/381291.html

回到顶部