如果你觉得可以,请多点赞,鼓励我写出更精彩的文章🙏。
如果你感觉有问题,也欢迎在评论区评论,三人行,必有我师焉
概要
这是一次公司组内分享的内容,本来是想写一篇关于React_Fiber
源码相关的东西,但是在分享的过程中发现大家对React
一些常规 API如何使用倒背如流。但是如果在继续深究或者说上升到架构层面的问题,就有点含糊不清了。(这种情况在我和其他技术人员讨论的时候,也存在诸如此类的问题,其实这不是大家的错,而是 React这个 库所牵扯的东西太过于庞杂。不是不想学,而是学不动了。谁不想在996之后,峡谷溜达会。)所以,我抱着,我不下"地狱",谁下地狱的决心,来用我 浅薄的知识点,用另外一种角度来简单的描述一下React
是什么。这其中有我自己对一个东西的认知和见解,如果和大家所掌握的知识所不同,欢迎大家在评论中指出和一起讨论。毕竟, There are a thousand Hamlets in a thousand people's eyes
时间不早了,该干点正事了,咱们书归正转。 (郭德纲语音包)
👈 点击触发
React是什么
我们在接触一个人或者一件事,可能会经历是什么
👉为什么
👉怎么办
,也就是What
=> Why
=>How
三部曲。
所以,我们该篇文章也按着这个流程来。
我们来一个直击灵魂的问题,React 是什么!
在和一些刚接触框架的新前端er交流的时候,猛然发现一个支持度很高的说辞。
React 是MVC框架的实现
其实如果按照从架构的角度来考虑,这个答案是政治正确的。也就是说,说你错误吧,挑不出啥毛病,但是承认你正确吧,总有一种如鲠在喉的感觉。
但是,其实说一个东西是XX,需要一个context
。
其实,关于React是什么的这个问题,React官网早已给出了答案。
A JavaScript library for building user interfaces
"地主老爷",把话说的很明白了 => React 就是一个UI库,他带着他家三个打手,
/Declarative
/Component-Based
。一起用棍棒在敲打来福。Learn Once, Write Anywhere
其实,这也是React 对于新手来说,比较难入门的地方,因为想要实现一个页面,用了人们口中所谓的 框架。但是这个框架却只负责页面的展示,那势必就需要利用辅助性工具来将应用能够跑起来。而这些辅助性工具。不乏针对路由控制的 react-router,还有针对页面状态控制的redux。
而一个真正的应用,势必是需要状态控制的。这不,我们即将介绍的主角👉redux正在缓缓向我们走来。
前端场景下MVC架构
该篇幅所说的,仅代表个人观点!如有雷同,不胜感激!如有不同的看法,也欢迎评论区指出。
在介绍即将出场的主角之前,我们还需要有一个前置知识点。什么是MVC。
想必大家无论是在面试还是在学习的工作中,总是会看到这个概念。 xxx框架是MVC框架的实现。就像我们上文描述的一样。有些人说React 是MVC框架的实现。那到底什么是MVC呢。
其实MVC这种架构模式,是为了实现代码分离或者用一个更拽的词-关注点分离。
MVC是三个单词的首字母缩写,它们是Model(模型)、View(视图)和Controller(控制)。
模型(Model):程序需要操作的数据或信息 视图(View):用户界面 控制器(Controller):用于连接 View 和 Model,管理 Model 与 View 之间的逻辑
而服务端MVC架构模式中,有一个很突出的特点,就是单向数据流。
但是,但是,但是,此处有转折,上面介绍的MVC的概念中有一个很重要的点,就是单向数据流。但是在前端场景下,MVC其实被无形中变更为双向数据流了。
这里放一张,我比较认同的关于前后端MVC层级划分的汇总。
然后继续看前端MVC中数据是如何流向的。
案例: 用户点击页面某个button( view),而button绑定了对应的事件回调( Controller),而该事件回调所要做的就是改变button的innerText( Modal)
除了允许用户通过 View 层交互来触发流程以外,MVC 架构还有另外一种形式,即允许用户通过直接触发 Controller 逻辑来触发流程
案例: 用户通过DOM提供的API, 绕过页面,直接获取了页面中某个button的实例,而button绑定了对应的事件回调( Controller),而该事件回调所要做的就是改变button的innerText( Modal)
前端应用/框架往往出于交互的需要,允许 View 和 Model 直接通信。
其实,大家可能此时对前端MVC 概念感到懵逼,如果给大家看几个图片,就会有点感觉了。
在前后端还未分离之前,负责拼装页面的任务其实在服务端。例如 JSP(Java Server Page)
在有了Node以后,前端可以拥有属于自己的资源服务,并且拥有自己的路由系统。这样,就把原本需要后断提供的UI层,移到了前端范围。
其实是什么是Js的MVC?Js的MVC只是后端MVC中的View里面再去分出来的MVC,跟后端MVC没太大关系。前端的MVC是为了解决复杂前端情况下模块化 Js 的问题。最典型的应用,单页面的 Js 应用。
针对MVC架构绕的有点远了。好了,咱们--收。
为什么要讲MVC框架,其实为了引出Flux的设计模式。
Flux
Flux is the application architecture that Facebook uses for building client-side web applications. It complements React's composable view components by utilizing a unidirectional data flow. It's more of a pattern rather than a formal framework.
这段描述是从 Flux官网中找到的。
我们从其描述中可以得到以下的观点
- 1:用于客户端(client-side)应用的架构。
- 2:提供单向数据流
- 3:它更偏向于设计模式。
我们通过如上的图,可以看到在Flux流程中有四个节点。
Action | Dispatcher | Store | View |
对变化的描述 | 用来接收Actions、执行回调函数 | 用来存放应用的状态, 一旦发生变动,就提醒Views要更新页面 |
视图层 |
我们用一个简单的🌰来看看Flux是如何运行的。 由于Flux现在已经被Redux替换,所以我就直接CV官网的例子,来简单展示一下数据流向。
Redux + React = MVC
其实我们在上面讲了很多和React
关系不大的废话,只是为了用更多的角度来反面论证
React 是MVC框架的实现,如果单纯从React本身来讲,是站不稳的。(是站不稳,不是站不住,我为了免于同行打脸,给自己留了些许的余地)。
那如何描述才会能变的不那么让人感觉这个论调是成立的。换句话说,在何种场景下,才可以说React是前端MVC的一种实现呢。
注意措辞。React在Redux的加持下,才算的上是一个比较经得起考验的前端应用。
React+ Redux = MVC
无论从MVC的概念还是从React库本身的职责考虑,React真的算不上真正意义的MVC框架。
我们来简单的描述一下Redux的工作流
ReduxA Predictable State Container for JS Apps
其实,Redux的出现,是为React提供了一套完整的数据管理。充当着MVC架构
中的M
M和C
的作用。
Redux是Flux架构模式的具体实现,所以有些概念也是和Flux一脉相承。
Redux 主要由 3 部分组成:Store、Reducer 和 Action。
- Store:一个单一的数据源,而且是只读只读的。
- Action 对变化的描述。
- Reducer 是一个函数,它负责对变化进行分发和处理,最终将新的数据返回给 Store。
这里可能有一个疑问,View是如何知道Store数据变化的。
我们通过Redux的源码来分析下。
首先,在view通过调用store中subscribe(()=>{})注册对应的回调函数。会将对应的事件回调push
到一个全局的Listeners
数组中。
其次,在dispatch(action)
的时候,在reducer计算出最新的state之后,就会依次调用存在全局的回调函数。
由于篇幅有限,下面的代码做了些删减。
还有一点还需要说明的,很多人总感觉Redux是针对React所构建的。其实不是,他是一个用于管理前端应用数据流向的库。正如官网说的,Redux可以使用于任何前期框架。
而如果熟悉React的同学,并且用Redux作为前端数据管理。肯定接触过react-redux。 而利用了react-redux之后,就不必要每次在需要监听store变化的组件中调用subscribe。而是用了一种更优雅的方式来做。
将组件通过HOCconnect包裹,并重新返回一个增强版的组件。
其他具体的API就不在这里赘述了。如果想了解更多关于Redux的源码,可以看我原来写的纯手硬撸Redux。
现在我们只简单介绍connect是如何实现的。
其实还是换汤不换药,在Redux中如果想监听它数据变更,就需要通过store.subscribe()来注册回调函数。
总结:其实在上面费了好多的时间去解释什么是React,什么是前端MVC。其实用一句话就能概况React就是一个UI库。
而我还决定,用很大篇幅去,婆婆妈妈的去讲解这些概念。👉最终的目的,就是想从框架的角度出发,让大家用一个全新的角度去了解React的数据流。而React本身的数据流(单向数据流),被别人估计都讲烂了。所以,我就没有拾人牙慧。
JSX
在进行React开发时,大家习惯上用JSX来表示页面结构,而在通过ReactDOM.render处理以后,所写的页面结构就跃然纸上。
那什么是JSX呢?
按照 React 官方的解释,JSX 是一个 JavaScript 的语法扩展,或者说是一个类似于 XML 的 ECMAScript 语法扩展。
关于JSX的其他相关知识点,可以参考官网JSX 和JSX In Depth
而我们今天换一种角度来考虑JSX。 既然JSX是JS的语法扩展,就需要有一个很直接的认知。常规浏览器是无法识别的。 既然,无法识别,就需要通过一些方式将其转换为浏览器能够识别的语法。 类似于ES6+的语法,需要通过处理才可以被浏览器识别。
而这个处理,就是通过babel通过指定对应转换规则,将其转换为常规的浏览器能够识别的语法。
而babel其实内核就是一个JS编译器。
接下来,我们通过实现一个自定义的babel-plugin来了解这个过程。
在介绍代码之前,我们需要对编译器流程有一个大致的了解。
大多数编译程序(compiler)分为三个步骤:Parsing(分析阶段)/Transformation(转换)/Code Generation(代码生成或者说生成目标代码)
Parsing | Transformation | Code Generation |
将源代码(raw code)转换为AST(抽象语法树) | 接收Parsing生成的AST,并且按照compiler内定的规则进行代码的转换 | 接受被compiler转换过的代码,按照一定的规则将代码转换为最终想要输出的代码格式。 |
由于篇幅有限,我们就直接CV代码了。如果想真正了解compiler的处理过程,可以参考我原来写的文章。编译程序(compiler)的简单分析和代码来构建一个简单的compiler
手动实现一个JSX转换器
我们通过babel来对一个JSX的文件进行转换。
.babelrc ==>babel配置文件
jsx-parser.js jsx-plugin.js
test.jsx
function JSX(){ return <div><span>Hello JSX</span></div> } 复制代码
我们通过 babel test.jsx
来开启babel转码。处理结果如下:
有几点需要注意下:
- 1 启动本地babel转码需要在本地或者全局按照
babel-cli
👉npm install babel-cli -g
- 2 在配置文件中,执行方向是从右往左进行的。
- 3 在将JSX转换为React.createElement配合React将其转换为对象,而这个对象就是我们平时所说的Virtual DOM
这篇文章是为了讲述一下React的框架层面数据流向。具体例如setState是同步异步,Fiber架构如何实现。由于需要很大的篇幅去描述。所以,打算重新开一篇来描述。