Skip to content

困扰99%前端新手的"状态管理"究竟是啥意思? #11

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
fanchangyong opened this issue Oct 4, 2019 · 0 comments
Open

困扰99%前端新手的"状态管理"究竟是啥意思? #11

fanchangyong opened this issue Oct 4, 2019 · 0 comments
Labels

Comments

@fanchangyong
Copy link
Owner

​前言

之前有小伙伴问我,经常在各种前端框架的文档中看到的“前端状态管理”究竟是什么意思,他始终弄不懂。我想可能初学前端的同学多少都会对这个概念比较懵,状态管理又是现代的前端框架中非常核心的内容,不理解这个就很难完整的理解React/Vue这类框架, 那我就写一篇文章来解释一下这个概念吧。

前端状态

首先说说什么叫“前端状态"。所有程序都有“状态”,状态表现在代码中其实就是各种类型的变量,其实程序运行的过程就可以理解为是程序内部的“状态“发生改变的过程,而我们编写的程序就是在控制这些“状态”如何发生改变。

前端状态的概念主要是应用在单页应用SPA(Single Page Application)中的,是在React/Vue等现代化的前端框架流行起来之后才有的一个提法,之前的jQuery时代是没有这种概念的。

SPA

那么SPA和之前的网页有什么区别呢?其实最本质的区别就是"服务端渲染“和”前端渲染“的区别。

所谓服务端渲染,是指浏览器请求一个URL地址,服务端返回的HTML页面是完整的,浏览器直接展示这个HTML内容再加上CSS的样式即可。JS主要做一些辅助性的特效或其他工作。

而前端渲染,是指浏览器请求一个URL地址,服务端返回的HTML页面并不包含具体内容,而是会通过 script标签引入一个JavaScript文件。浏览器只有通过解析和执行JS代码页面上才会展示出内容,否则页面就是空白的。而且后续的页面更新也都是在JS中完成,而不是通过跳转到另一个URL地址来完成(这也是单页应用名字的由来)。当然这里并不是指URL地址就一定不会变化,而是说页面不会“刷新”。

SPA的出现使得Web前端成为真正的"客户端程序",可以独立完成渲染(DOM API)、网络请求(XHR,fetch)等任务,也就是所谓的"前端渲染",而不是只是一个“网页展示器”。

声明式编程和命令式编程

前端状态管理之所以流行的另一个原因是,现代的前端框架包括React/Vue都是声明式的编程方式,而之前的jQuery是命令式的。所谓声明式,就是说你在代码中不会直接去操作UI,而是通过操作数据,来间接地改变UI内容。而命令式,是直接操作UI的,这就导致数据层和展现层通常是不作区分的。

声明式的编码方式天然的会把数据状态和页面代码分离,所以更需要一套独立的状态管理系统。

Todo App示例

上面是从概念上解释,接下来我们举一个实例来解释一下这个概念。这里举一个很简单的Todo App:

todo1
todo2

上面两张图分别是列表页和详情编辑页。列表页中的内容是在打开首页的时候从服务端通过请求来的。点击列表页中的一个标题,会进入到详情编辑页,要求在编辑页中显示的标题要和列表页的标题对应。在编辑页可以编辑标题和内容,点击Save按钮会返回到列表页,这里页要求列表页的标题内容更新为新的标题。

产品需求介绍完了,我们先来看一下如果是服务端渲染的方式,是如何工作的。

首先,当你在浏览器中输入https://todo.com/网址的时候,浏览器向你的服务端发起HTTP请求,你的服务端返回给浏览器HTML文件。这个HTML文件中包含了完整的Todo List的内容。浏览器只需要解析HTML代码以及CSS然后展示即可。

列表中的每一项都是一个<a href="/https://github.com/:id">标签,比如当你点击id为1的一个标题的时候,浏览器会“跳转”到https://todo.com/1的地址。这时候会向你的服务器发送另一个HTTP请求,URL就是https://todo.com/1。你的服务器又会针对这个地址返回对应的HTML文件,同样的,这个HTML文件也是有完整内容的,浏览器要做的仅仅是展示HTML内容。

这里的"Save"按钮会是一个form Submit按钮,它会使浏览器向你的服务端发送一个POST请求,并带上你在页面中新输入的标题和内容。你的服务器收到请求之后,会把新输入的内容保存数据库,然后返回一个"Redirect",使你的浏览器重新跳转到列表页。

然后我们看一下如果是前端渲染的方式:

你在浏览器中输入了网址:https://todo.com/,浏览器向你的服务端发起HTTP请求,然后你的服务器返回HTML文件。这一步是没有区别的,区别就在于服务端返回的那个HTML文件的内容。在服务端渲染的情况下,这个文件是由服务端“拼装”出来的,而在前端渲染的情况下,这个文件不包含任何内容,而只是引入了一个JS文件。比如下面这样的:

<html>
  <head>
    <script src="/https://github.com/app.js"></script>
  <head>
  <body>
  </body>
</html>

当这个文件到达浏览器,因为HTML的body部分是空的,所以在执行JS之前页面会是空白的。接下来浏览器会下载和执行JS代码。

在JS代码中,你会使用XHR的方式向服务器发起一个“API请求“,这类请求是浏览器在后台发起,不会引起地址栏和页面刷新。和页面请求不同,服务器对这种“API请求”的响应内容通常会是JSON格式的(服务器通常是根据不同的请求路径判断是页面请求还是API请求,比如/api/*开头的请求都算作API请求)。比如,请求列表的URL是:GET /api/todos,它的响应是:

[
  {
    "id": 1,
    "title": "title1",
    "content": "This is list 1 content",
  }
  ,...
]

收到响应之后,JS代码会被执行,之后会由JS执行DOM操作来改变页面的展示内容,也就是页面内容被“渲染”出来了。

这个列表页面中的每一条会是一个简单的<div>标签,而不是一个会发生浏览器跳转的标签。当你点击其中一个标题的时候,同样会由JS代码接管。由于在请求列表的时候前端已经拥有了所有数据,所以这次不用再请求服务器了,JS代码直接重新操作DOM,来"渲染"出编辑页面即可。

当在编辑页面中点击Save按钮,同样触发JS代码,JS代码会向服务器发起一个保存修改的请求。如果服务器响应成功,那么JS会更新自身的数据,修改为新的标题和内容。

在这个例子中,所谓的前端“状态”,可能就是一个全局的JS对象,比如:

{
  "todos": [
    {
      "id": 1,
      "title": "title1",
      "content": "This is list 1 content",
    }
    ,...
  ]
}

而所谓的"状态管理",其实就是对这个全局对象的一系列增删改查的操作。

通过上面的例子,我们可以看一下前端状态管理的意义有哪些:

第一,数据管理逻辑和页面渲染逻辑分离,使得代码更容易维护。这其实有点类似于MVC的设计模式,操作数据的地方不会关心页面如何展示,展示页面的地方不会关心数据从哪里来的。

第二,可以保证数据有一份“唯一可信数据源“。比如上面例子中的“title"字段,在列表页面和编辑页面都会展示,如果不对这个状态做统一管理,很难保证数据的统一:比如在修改页面修改了之后要保证列表页面同步修改。在实际的项目中,同一份数据可能在N多个地方展示、修改,如果不做好状态管理,代码会写成什么样简直不敢想。

总结

看到这里,我不知道你会不会觉得奇怪,“不是要讲前端状态管理吗,为什么长篇大论的讨论服务端渲染和前端渲染?“。其实我是觉得因为有了单页应用的需求(为了解决浏览器刷新的用户体验问题),所以需要前端渲染;而因为有了前端渲染,所以前端不得不需要自己管理状态。理解了前端渲染的整个流程,对于理解前端状态管理是个前提,也有很大帮助。

不知道你现在对前端状态管理的概念有没有清晰一些。这篇文章主要介绍概念,并没有涉及具体框架的描述。其实理解了概念再去看redux/vuex这些框架,会容易理解很多。


最后推广一下自己微信公众号:<前端时光机>,所有文章都在那里首发,所以如果感兴趣请扫码关注:
qrcode

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant