感谢「 React 全栈」。这是一本入门 React 的好书。在最让我抓狂的2月20日到3月底这一个多月的时间里,我曾多次的翻阅本书的第三章、第四章,它清晰易懂的讲解和案例演示启发了我,并帮助我解决了多个问题。毫不夸张的说,这本书让我端住了 React 这碗饭。
感谢「 React 学习手册」。这本书不是入门 React 的书,首先这本书是翻译后的作品,翻译的倒是没有问题,但跟原汁原味的中文著作毕竟还是不大一样;其次,它写的非常冷静,用词也很专业,有些晦涩,读起来可能会有点儿像是教科书一样无聊;再次,这本书有点「代码是第一公民」的风格,本书的主体章节四到七章,需求层层递进,代码也越来越“讲究”,比如要求用函数式的方式或高阶组件的方式来实现某一功能。它即学即练,以练代教,这对于读者来说,有点挑战。因此它不适合被当作入门教程,不过如果把它当作进阶教程来看,那么这些缺点就都成了优点。这本书让我精进许多,毫不夸张的说,这本书让我端稳了 React 这碗饭。
我要感谢这两本书。我花费在这两本书中的时间都是些零零散散的“读书时刻”,从中学到的可真是实实在在的能解决问题的技能。
我要感谢这两本书还有一个重要原因,这个原因关乎到无产阶级的信仰。无产阶级艺术最伟大的代表者高尔基曾经说过:书籍,是人类进步的阶梯。以前我是不认可这一点的。我一直认为自己是没有本领看严肃的技术书籍的,那靠什么呢?以前的我只能靠百度,靠视频教学。但这两本书读下去,让我克服了对技术书籍的恐惧。书籍,真的是人类进步的最最有效、也最最高效的阶梯。
因此,我当然要感谢这两本书。
当我写下「Why React」的时候,我就发现它有双重含义。
为什么我要学习 React?答案其实很简单:被逼无奈。“舒适圈”这个概念流传已久,但谁喜欢主动踏出舒适圈呢?人都是有惰性的,人都喜欢“钱多事儿少离家近”,人人知道“学海无涯”,可谁喜欢“苦作舟”呢?没办法,我要吃饭。目前整个项目前端的重心都在 React 上。要把饭吃得踏实,React,我是绕不过去的。
为什么项目选用 React 技术栈?因为我没有接触过 vue 和 angular,所以我仅仅能粗略的谈谈原生 JavaScript 与 React 框架的优缺点。
JavaScript 能不能做?当然可以,事实上,不管用什么前端框架,不管框架怎么折腾,最终还都是要转译成 JavaScript 代码的,因此,前端框架能做的,JavaScript 肯定可以做,问题在于,你所在的前端项目要处理多少数据?页面展示对性能有多高的要求?
我们都见过 HTML 代码,浏览器会读取它并生成可操作的节点树,所有页面的变化都来自于节点树的变化,JavaScript 操作的对象就是这节点树。就像生活中我们种的树一样,要想砍掉某个树杈是容易的,但要精确的在某某枝桠上“嫁接”出新枝干出来,这事儿就费劲了。JavaScript 操作节点树也有这个问题:更新或者修改节点树容易,但插入新元素的过程则非常低效。
当我们用 JavaScript 构建应用程序时,其实是不大注意到底应该用更新还是删除的,一般就都粗暴的删除再插入新元素,页面能正常展示即可。毕竟,又不是不能用,但其实,这样的惯常操作是牺牲了一部分性能的。
我们无法要求程序员每写一行代码都要经过反反复复的斟酌和考量(那样太浪费时间和精力了)。React 解决了这个问题:当操作真正的节点树时,React 总会选择最高效的方式执行,以提高程序性能。
如果项目不大,对页面渲染数据的性能也没有要求,那么 JavaScript 搭配 JQuery 其实已经足够了;反之,页面要加载的数据相当之多,交互逻辑也非常复杂,并希望能提高前端的渲染速度,那么,React 框架也许会是个比较英明的选择。
Golang 是当下比较时髦的静态编程语言,我所在的项目用 Go Programming Language 实现了一整套的 Mysql 数据库管理平台解决方案,代码洋洋洒洒有几十万到百万行不等。我有幸使用 Go 维护了该项目有一年多的时间,后来才自告奋勇接了一些前端的工作。原因是在当时我的眼里,前端所做的工作太神秘了。
一开始我对 JavaScript 的印象不是很好,因为我总要判断某个值到底是不是 undefined 我才敢放心使用,在获取可能是数组对象的第一个值时尤其要注意,一不留神就给你返回一个大大的 undefined。刚入手 JavaScript 的时候,我觉得这门编程语言滑溜的简直像小时候挽着裤脚在河里逮的泥鳅,给我带来特别大的不适感。不过这样的挫败感只折磨了我半个月的时间,当我读完 《 JavaScript 高级程序设计》 后,我很快接受了这门语言,后来项目迁移到带类型标注的 TypeScript,我没有感觉到任何额外的学习成本。
刚入手 React 的时候,我觉得这玩意儿简直就像洪水猛兽、邪恶的异端、潘多拉的魔盒,因为它的语法实在是太诡异!JavaScript 代码都不能光明正大的写了,还要包在大括号里面,搞得像偷鸡摸狗一样,还有什么单向数据流、函数双向绑定,一听到这些我的手心里就全是汗。
两本书救了我。
《 React 全栈》让我大概了解了 React 的代码结构和最基本的执行逻辑,我可以上手修复些简单的 bug 了,这就算保住了这个饭碗,没有搞砸。
《 React 学习手册》不仅让我知其然,还让我知道了一点“所以然”。有不少时刻,当看到书中干净整洁、逻辑清晰且扩展性很好的 React 代码示例,我由衷的佩服 React。
React 最大的亮点是它所支持的「3+1」特性。3 指的是三个基础特性,凡用 React 者必知必会,也是 React 核心优势所在,包括Virtual DOM,组件化以及状态机 state 和 props;1 指的是一个加分项,React 原生支持了函数式编程。
DOM,可勉强翻译为「文档对象模型」。浏览器会把 HTML 代码都转成 DOM,然后根据 DOM 完成页面展示。前面说过,为了提升页面渲染的性能,React 会尽量减少对 DOM 节点执行添加操作。那怎么做的呢?
React 全面接管了真实的 DOM,程序员编写的代码只能操作虚拟的 DOM - - 即 Virtual DOM。
这就好比我们用的导航软件,条条大路通罗马,但最优线路却不多。我们可没有精力天天手里拿着地图去算这个东西,导航软件帮我们做了,你仅仅需要告诉它起点和终点,它会帮助你计算出最优答案。导航软件就类似于 React 中的 Virtual DOM。
React Virtual DOM 是存储在内存中的键值对,修改起来非常方便。一旦修改,便会自动触发 React 对真实 DOM 进行修改的计算,计算出最优解后则立即对真实 DOM 执行变更。
高级!
Linux 有一个原则:由目的单一的小程序组成,通过组合小程序完成复杂任务。
我们还有一个经典故事叫做:一个和尚挑水吃,两个和尚抬水吃,三个和尚没水吃。
我通过 Linux 还有三个和尚的故事想表达什么意思呢?权责明确是高效合作的基础,一旦出现分工不合理、权责不明确,效率就一定会低下,出现了问题就一定会推诿扯皮,很简单,一件事情A管B管C也管,那就意味着谁都不管。
组件化出现在软件开发的过程中,目的其实就是为了分工明确。
用 JavaScript 开发应用程序,通常一个界面就是一个 js 文件,顶多两个,你想啊,有关这个页面所有的交互逻辑都包含在这一个文件中,维护起来是非常不方便的。
React 的组件化解决了这个问题,当你把页面拆分成若干独立的组件,组件里还可以再套组件,那么最后整个程序将会是像树状图一样的清晰明了。
优雅!
动态变化着的数据是组件的生命。因为实现交互是前端的使命,而交互的本质是数据的变化。
在单一组件中,有统一存放数据的地方 - -state,当这里的数据存在变更,相关的页面就会自动重新渲染,当然是经由 React 计算后以最节省性能的方式渲染。这是动态交互的一种。
应用程序组件化意味着程序中往往不止一个组件,这些组件构成了组件树,组件与组件或并列或包含与被包含。
但组件与组件之间不可能总是老死不相往来的关系,他们之间是允许“交流”的,这种“交流”在父子组件中应用最为广泛。
比如一个弹出层组件,这个弹出层里面展示的是一个表单,那么为了降低颗粒度,表单也应该自成一个组件,于是就嵌入到弹出层组件中,作为弹出层的子组件使用。假如弹出层已经获取到数据,那么这些数据没有必要在表单中重新获取,那么这数据如何从父组件传到子组件的表单中呢?子组件的数据如何实时的、动态的随着父组件数据的变动而变动呢?
答案就是为子组件配置 props。props 就像函数里参数,父组件就像调用函数一样给子组件的参数赋予值,子组件接收到该值,就可以正常的渲染。
有人把 React 中 state 和 props 形象的比喻为“状态机”- - 即维护组件状态的机器,挺好。不过这让没有用过 React 的人会觉得有点高深莫测,其实这没什么,就是函数的另一种表现方式而已。state 是函数的局部变量,props 是函数的签名,就是这样子而已。
《 React 学习手册》一书花了整整一章的篇幅讲了函数式编程,一开始我还很不理解作者用意,可越往后读才越发现这个概念的重要性,于是不得不好几次回过头来重读本章。
从前我想当然的认为,多写一些函数就是函数式编程了:即把大任务拆分成小任务,最后把这些函数整合来完成应用程序的构建。
对吗?对是对,但并没有触及函数式编程的本质,并且有些细节也没有厘清。
表面上来看,函数式编程的确就像刚刚描述的“化整为零”的战术一样,把一个整体分成许多零散的部分,然后按需组合调用。
这种解决问题的方式一点儿也没有什么稀奇,谁做事情不是 step-by-step 的呢?那为什么还非得专门用一个数学术语(函数)来重复定义它呢?
函数概念来自数学,拿我们最为熟知的正弦函数 y=sin x 为例:x 和 y 都是变量,x 是自变量,y 随着 x 变化而变化,所以是因变量。
函数的返回结果只依赖于输入的参数,当 x 为 30° 时,不管你在帕米尔高原还是乌苏里江,这个函数的结果都是1/2;函数也不会产生任何副作用,它不需要改变外界的任何东西;函数还能套函数使用,形成高阶函数。
所有这些特性在函数式编程中都有应用。
例如一个经典问题:参数传递是值传递还是址传递?以 Go 语言为例,如果没有特殊处理(比如传递指针类型),函数的参数传递都是值传递,为的就是不取代原生数据,尽量减少函数对外部的影响,不产生副作用。
再比如 React 特别推荐使用的无状态函数式组件,以及代码复用时常用的高阶组件,还有经常在函数中使用的递归,这都是对函数这一数学概念的实践。
函数式编程不是粗糙的把整个大部队拆分成零零散散的游击队,而更像是精心培育的小型特种部队,它机动灵活、学有专长、自成系统、随时待命。用专业的术语来说,函数式编程不仅仅是函数的拆分和组合,还要尽量是纯函数、保证数据的不可变、对外不产生副作用,此外,还应该根据场景尽可能的应用高阶函数和递归。
在近两个月的时间里,我先后经历了对 React 的怕、恨、不解和尊敬,这是一个学习的过程。当然了,这最得益于两本书的作者们慷慨的分享,我也晓得,我懂的还很少,只是略知一二,很有可能,开始才刚刚开始。