muwoo / blogs

📚一个前端的博客。
2.32k stars 352 forks source link

React 思想 #40

Open muwoo opened 4 years ago

muwoo commented 4 years ago

在深入学习react之前,我们需要先对react基本理念有一定的认知,只有在了解react设计初衷和解决什么问题后我们才能更好的理解他,学习他。

1. virtual dom 封装dom操作

为什么先提vdom呢,因为在此之前有关于react的各种能力的,React最大的价值究竟是什么?是高性能虚拟DOM、服务器端Render、封装过的事件机制、还是完善的错误提示信息?尽管每一点都足以重要。但是在此之前其实react设计之初所提倡的vdom理念核心是为了解决开发者手动操作dom带来的性能问题。我们知道不同dom操作所带来的性能问题有很大的差别,但是Facebook内部的开发者并不是每个人都可以对dom操作使用最优的方案,而且每个人写的风格迥异。vdom设计之初也是想基于此在真实的dom上封装一层,这样开发者操作到的只是封装好的vdom,vdom到真实的dom的操作便有框架解决,再怎么弄也是个虚拟的dom,底层只要做好性能处理,便不会有太大的问题。 我们再来说一下作为数据驱动的框架,一个数据模型的变化可能导致分散在界面多个角落的UI同时发生变化。界面越复杂,这种数据和界面的一致性越难维护。在Facebook内部他们称之为“Cascading Updates”,即层叠式更新,意味着UI界面之间会有一种互相依赖的关系。开发者为了维护这种依赖更新,有时不得不触发大范围的界面刷新,而其中很多并不真的需要。React的初衷之一就是,既然整体刷新一定能解决层叠更新的问题,那我们为什么不索性就每次都这么做呢?让框架自身去解决哪些局部UI需要更新的问题。这听上去非常有挑战,但React却做到了,实现途径就是通过虚拟DOM(Virtual DOM)的 Diff,当数据变更时,产生一个新的Dom树,和旧的Dom树进行比对,决策出最小更新单元,关于Diff算法,我之前写过一篇Vue的diff原理,不过大体类似,这里不再赘述。 然后我们再提及一下React vdom底层不仅仅可以实现vdom -> dom 的渲染,作为vdom我们只需要变更不同的DSL便可跨端渲染。这也正是react 服务端渲染、RN等跨端解决方案的根本之一。也有基于vdom做其他方向渲染的案例,比如Mpvue,react canvas 等等。我也曾尝试过基于Vue的vdom做canvas的渲染。

2. JSX

将HTML直接嵌入到JavaScript代码中看上去确实是一件足够疯狂的事情。人们花了多年时间总结出的界面和业务逻辑相互分离的“最佳实践”就这么被彻底打破。那么React为何要如此另类? 前端界面的最基本功能在于如何将数据准确的展示到页面中,我们先抛开jquery命令式的方式不谈,来看看其他模板引擎是如何实现: Angular

<div ng-if="person != null">
    Welcome back, <b>{{person.firstName}} {{person.lastName}}</b>!
</div>
<div ng-if="person == null">
    Please log in.
</div>

emberjs

{{#if person}}
  Welcome back, <b>{{person.firstName}} {{person.lastName}}</b>!
{{else}}
  Please log in.
{{/if}}

Knockoutjs

<div data-bind="if: person != null">
    Welcome back, <b>{{person.firstName}} {{person.lastName}}</b>!
</div>
<div data-bind="if: person == null">
    Please log in.
</div>

模板可以直观的定义 UI 来展现 Model 中的数据,你不必手动的去拼出一个很长的 HTML 字符串,几乎每种框架都有自己的模板引擎。传统 MVC 框架强调界面展示逻辑和业务逻辑的分离,因此为了应对复杂的展示逻辑需求,这些模板引擎几乎都不可避免的需要发展成一门独立的语言,如上面代码所示,每个框架都有自己的模板语言语法。而这无疑增加了框架的门槛和复杂度。 如果说掌握一种模板语言并不是很大的问题,那么其实由模板带来的架构复杂性则是让框架也变得复杂的重要原因之一,例如: 模板需要对应数据模型,即上下文,如何去绑定和实现? 模板可以嵌套,不同部分界面可能来自不同数据模型,如何处理? 模板语言终究是一个轻量级语言,为了满足项目需求,你很可能需要扩展模板引擎的功能。 为了解决这些复杂度,框架本身需要精心的设计,以及创造新的概念(例如 Angular 的 Directive)。这些都会让框架变得复杂和难以掌握,不仅增加了开发成本,各种难以调试的 Bug 还会降低开发质量。 例如在 Angular 中:

<ul class="unstyled">
  <li ng-repeat="todo in todoList.todos">
    <input type="checkbox" ng-model="todo.done">
    <span class="done-{{todo.done}}">{{todo.text}}</span>
  </li>
</ul>

而使用 JSX,则代码如下:

var ul = (
  <ul class="unstyled">
    {
        this.todoList.todos.map((todo) => (
        <li>
          <input type="checkbox" checked={todo.done}>
          <span className={'done-' + todo.done}>{todo.text}</span>
        </li>
      ))
    }
  </ul>
);

可以看到,JSX中除了另类的HTML标记之外,并没有引入其它任何新的概念。Angular中的repeat在这里被一个简单的数组方法map所替代。在这里你可以利用熟悉的JavaScript语法去定义界面,在你的思维过程中其实已经不需要存在模板的概念,需要考虑的仅仅是如何用代码构建整个界面。这种自然而直观的方式直接降低了React的学习门槛并且让代码更容易理解。

3. 单向数据流动

单向数据流对应的就是多项数据流,我们先来看看多项数据流带来的问题:想想看,如果我们现在视图上有个switch按钮,按钮状态依赖组件内部的store,用户可以触发点击操作修改store从而触发视图的更新。但是如果我们需要做另一个操作:纪录用户的行为,在用户退出时存储行为到服务端,再进入时展示上一次的操作。此时数据源又多了个server。这样view的展示逻辑就变得复杂,想想看,如果再来其他的n个数据源,要经过多少判断处理? 所以react提出的单向数据流的优点是所有状态变化都可以被记录、跟踪,状态变化通过手动调用通知,源头易追溯,没有“暗箱操作”。同时组件数据只有唯一的入口和出口,使得程序更直观更容易理解,有利于应用的可维护性。整体的设计理念也是希望数据更新策略变得简单化,让一切变化变得更加可控。

4. Immutability Data

Immutability含义是只读数据,React提倡使用只读数据来建立数据模型。这又是一个听上去相当疯狂的机制:所有数据都是只读的,如果需要修改它,那么你只能产生一份包含新的修改的数据。假设有如下数据:

var employee = {
  name: ‘John’,
  age: 28
};

如果要修改年龄,那么你需要产生一份新的数据:

var updated = {
  name: employee.name,
  age: 29
};

这样,原来的employee对象并没有发生任何变化,相反,产生了一个新的updated对象,体现了年龄发生了变化。这时候需要把新的updated对象应用到界面组件上来进行界面的更新。在javascript中我们可以通过deep clone来模拟Immutable Data,就是每次对数据进行操作,新对数据进行deep clone出一个新数据。当然你或许意识到了,这样非常的慢。有兴趣的可以继续了解https://github.com/immutable-js/immutable-js