React 解析

原文: http://blog.reverberate.org/2014/02/react-demystified.html
作者: Josh Haberman
中文社区导航: http://nav.react-china.org

这篇文章跟博客已有的其他文章有一些分离,博客大部分是语言解析和底层编程的,
最近我对一些 JavaScript 框架有了兴趣,包括 Facebook 的 React.
我最近阅读的文章,特别是 The Future of JavaScript MVC Frameworks,
让我相信在 React 当中有一些深入的强大的想法在里边,
然而我没找到文章或者文档能把它核心的抽象解释到我满意的.
就像我前一篇文章 LL and LR Parsing Demystified,
这篇文章尝试解释 React 里对我有意义的思想.

The 1000-Foot View

传统的 Web app 当中,你要花费高昂的代价和 DOM 进行交互,通常是用 jQuery:

我把 DOM 标记成了红色,因为更新 DOM 开销是很大的.
现在的很多 "App" 会有个 Model class 用来在内部表示状态,
但在我们这里认为只是 app 内部的实现细节.

React 主要的目标是提供一套不同的,高效的方案来更新 DOM.
不是通过直接把 DOM 变成可变的数据,而是通过构建 "Virtual DOM",虚拟的 DOM,
随后 React 处理真实的 DOM 上的更新来进行模拟相应的更新:

引入额外的一个层怎么就更快了呢?
那不是意味着浏览器的 DOM 操作不是最优的,如果在上边加上一层能让整体变快的话?

是有这个意思,只不过 virtual DOM 在语义上和真实的 DOM 有所差别.
最主要的是,virtual DOM 的操作,不保证马上就会产生真实的效果.
这样就使得 React 能够等到事件循环的结尾,而在之前完全不用操作真实的 DOM.
在这基础上,React 计算出几乎最小的 diff,以最小的步骤将 diff 作用到真实的 DOM 上.

批量处理 DOM 操作和作用最少的 diff 是应用自身都能做到的.
任何应用做了这个,都能变得跟 React 一样地高效.
但人工处理出来非常繁琐,而且容易出错. React 可以替你做到.

Components

我前面提到 virtual DOM 和真实的 DOM 有着不用的语义,但同时也有明显不同的 API.
DOM 树上的节点被称为元素,而 virtual DOM 是完全不同的抽象,叫做 components.

component 的使用在 React 里极为重要,因为 components 的存在让计算 DOM diff 更高效,
比起完整通用的 tree-diff 算法消耗的 O(n^3) 高效多了.

想知道为什么,就要深入一点 components 的设计当中.
拿 React 首页的 "Hello World" 做个例子:

/** @jsx React.DOM */
var HelloMessage = React.createClass({
  render: function() {
    return <div>Hello {this.props.name}</div>;
  }
});
 
React.renderComponent(<HelloMessage name="John" />,mountNode);

这里边有多得可怕的运行细节没有被解释彻底.
这个例子尽管小,却展示了一些宏大的想法,所以这里我花点时间慢慢讲.

这个例子创建了 React component class HelloMessage,
然后创建了一个 virtual DOM,包含 component,
(<HelloMessage>,本质是是 HelloMessage class 的一个实例)
并挂载到真实的 DOM 元素里的一个节点.

首先注意这个 virtual DOM 是由应用定义的 components 组成的(这里是 <HelloMessage>).
这和浏览器真实的 DOM 有着显著的不同,那些都只是浏览器内建的比如 <p> <ul>.
真实的 DOM 不含应用特定的逻辑,而仅仅是可以托管事件回调的数据结构.
而 React 里的 virtual DOM,则是含有应用特定内在逻辑的,专为应用定制的 components.
这远不止于一个 DOM 更新类库. React 是一种新的抽象,新的构建 View 的框架.

另外,如果你一直关心 HTML 的消息,你应该知道HTML 自定义标签很快会有浏览器支持.
这将带给真实的 DOM 相似的功能: 根据应用特定逻辑定制应用需要的 DOM 元素.
不过 React 不需要等待官方的自定义标签支持,因为 virtual DOM 不是真实的 DOM.
这使得 React 能提前应用,嵌入类似自定义标签和 Shadow DOM 的功能,
而不用等到浏览器加上了所有这些功能才能被使用.

回到例子里,已经能确定,其中创建了一个叫做 <HelloMessage> 的 component 挂载到了节点上.
我想用图把最初的状态表示为下面几种形式. 先来展示 virtual DOM 和真实 DOM 之间的关系.
先假定挂载点是文档的 <body> 标签:

里边的箭头表示 virtual 标签挂载到了真实的 DOM 元素上,很快可以看到结果.
同时看一下现在应用的 view 的逻辑说明:

这里是说,整张网页内容是通过我们定制的 <HelloMessage> component 展示的.
不过,一个 <HelloMessage> 看起来是什么样子呢?

component 的渲染通过 render() 函数定义.
React 没有明确说明什么时候或者多频繁他会去调用 render(),
只是会尽量调用,使得正确的界面更新能看清.
render() 方法返回的内容,表示了浏览器里真实的 DOM 看起来应该怎样.

这里例子当中,render() 返回了 <div>,里面还有一些内容.
React 调用了 render() 函数,得到 <div>,并相应到真实的 DOM 做更新.
所以现在图片更像是:

这里不仅更新了 DOM,还保存了 component 过去被更新了怎么样.
所以 React 才能进行在后面进行快速的 diff.

我掩盖了一件事,render() 函数为什么能够返回 DOM 节点.
这是通过 JSX 完成的,不是通过单纯 JavaScript. 看 JSX 的编译结果更有好处:

/** @jsx React.DOM */
var HelloMessage = React.createClass({displayName: 'HelloMessage',render: function() {
    return React.DOM.div(null,"Hello ",this.props.name);
  }
});
 
React.renderComponent(HelloMessage( {name:"John"} ),mountNode);

所以 return 的不是真实的 DOM 元素,而是 React 类似 Shadow DOM 的实现,
(比如说是 React.DOM.div) 对应到真实的 DOM 元素.
所以 React 的 shadow DOM 实际上没有真实的 DOM 节点.

表示状态和改变

到上面为止,我跳过了很大一段故事,就是 comonent 是怎样被改变的.
如果 component 不允许改变,React 顶多只是个静态渲染框架,
像是纯粹的模板引擎,比如 Mustache 或者 HandlebarsJS.
而 React 的要点是快速进行更新. 要更新,component 就需要能更改.

React 将其 state 作为 component 的 state 属性建模存储.
这在 React 页面上的第二个例子里阐述了:

/** @jsx React.DOM */
var Timer = React.createClass({
  getInitialState: function() {
    return {secondsElapsed: 0};
  },tick: function() {
    this.setState({secondsElapsed: this.state.secondsElapsed + 1});
  },componentDidMount: function() {
    this.interval = setInterval(this.tick,1000);
  },componentWillUnmount: function() {
    clearInterval(this.interval);
  },render: function() {
    return (
      <div>Seconds Elapsed: {this.state.secondsElapsed}</div>
    );
  }
});
 
React.renderComponent(<Timer />,mountNode);

回调函数 getInitialState(),componentDidMount(),componentWillUnmount()
都会被 React 在对应的时机触发,他们的命名根据前面提到的应该写的很清楚了.

而 component 和 state 改变背后的基本理解是这样:

  1. render() 仅仅是一个返回 component state 和 props 的函数

  2. state 只有在 setState() 调用时才改变

  3. props 不会改变,除非父级 component 重新调用了渲染,传入新的 props

(props 属性在前面没有明确说,他们是渲染时从父级元素传进来的属性.)

前面我是 React 会调用渲染函数"足够频繁",
意味着 React 不会再去调用 render(),直到 component 的 setState() 被调用,
或者被父级元素传入不同的 props 属性重新渲染.

把所有信息汇集到一起,可以阐释 app 初始化时 virtual 改变的数据流
(比如,响应一个 Ajax 请求):

从 DOM 当中获取数据

上面只讨论了怎么把数据的更新传播到 DOM.
实际的应用是,也要从 DOM 获取数据,因为我们需要那样从用户获取数据
要看是如何工作的,可以看第三个 React 主页上的例子:

/** @jsx React.DOM */
var TodoList = React.createClass({
  render: function() {
    var createItem = function(itemText) {
      return <li>{itemText}</li>;
    };
    return <ul>{this.props.items.map(createItem)}</ul>;
  }
});
var TodoApp = React.createClass({
  getInitialState: function() {
    return {items: [],text: ''};
  },onChange: function(e) {
    this.setState({text: e.target.value});
  },handleSubmit: function(e) {
    e.preventDefault();
    var nextItems = this.state.items.concat([this.state.text]);
    var nextText = '';
    this.setState({items: nextItems,text: nextText});
  },render: function() {
    return (
      <div>
        >h3<TODO</h3>
        <TodoList items={this.state.items} />
        <form onSubmit={this.handleSubmit}>
          <input onChange={this.onChange} value={this.state.text} />
          <button>{'Add #' + (this.state.items.length + 1)}</button>
        </form>
      </div>
    );
  }
});
React.renderComponent(<TodoApp />,mountNode);

简单说,手动操作 DOM (像 onChange() 方法里写的),
事件回调可以调用 setState() 来更新 UI.
如果你的应用里有 model 的 class,那么你的事件回调是应该去相应更新 model,
还有就是调用 setState() 让 React 知道数据有更新.
如果你已经习惯了一些自动进行双向绑定的框架,
model 和 view 的数据两个方向相互传播,这里可能有点落后了.

这个例子里有很多一眼能看见以外的东西. 虽然例子看起来是这样的,
React 实际上没有在真实的 <input> 元素上绑定 handler.
而是在整个文档的级别绑定了 handler 等待事件冒泡,再分发到 virtual DOM 对应的元素.
这带来的好处有速度(在真实的 DOM 上绑定大量的 handler 会很慢),
还有是一致的跨浏览器兼容(即便浏览器行为遵循标准,或者属性不全).

所有这些放在一起,终于能看到整个图景里的数据流动,
从用户事件(比如说鼠标点击)开始,最终完成 DOM 的更新:

结论

通过写这篇文章我学到了不少关于 React 的东西. 下面是我主要的收获.

React 是一个 View 的类库
React 没有影响到你使用任何 model.
React 的 component 是一个 view 级别的概念,其中 state 对应这个 UI 部分的状态.
你可以把任何 model 类库结合到 React 来使用
(当然有些 model 的处理使得更新被优化得更深入,比如 Om 的文章里写的).

React 的 component 抽象很适合把更改作用到 DOM 上去.
component 的抽象是条理化的,适合被复合,这个设计带来了 DOM 更新的高效.

React component 从 DOM 上获取更新相对不那么方便
手写 event handler 让 React 看起来明显比一些自动更新 view 更改到 model 的类库低级.

React 的抽象是有漏洞的.
大多数时间你只是对 virtual DOM 进行编程,但有时你需要能直接操作真的 DOM.
React 文档里关于这个讲了很多,这在他们的Working With the Browser 章节是必需的.

根据我的理解,我倾向认为在 The Future of JavaScript MVC Frameworks 里说的内容,
需要更深入去审视. 但这个不大一样,我要等到另一篇文章写.

我不是 React 方面专家,如果有错了好心提醒我一下.

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如果组件之中有复用的代码,需要重新创建一个父类,父类中存储公共代码,返回子类,同时把公用属性...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例如我们的 setState 函数式同步执行的,我们的事件处理直接绑定在了 dom 元素上,这些都跟 re...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom 转为真实 dom 进行挂载。其实函数是组件和类组件也是在这个基础上包裹了一层,一个是调...
react 本身提供了克隆组件的方法,但是平时开发中可能很少使用,可能是不了解。我公司的项目就没有使用,但是在很多三方库中都有使用。本小节我们来学习下如果使用该...
mobx 是一个简单可扩展的状态管理库,中文官网链接。小编在接触 react 就一直使用 mobx 库,上手简单不复杂。
我们在平常的开发中不可避免的会有很多列表渲染逻辑,在 pc 端可以使用分页进行渲染数限制,在移动端可以使用下拉加载更多。但是对于大量的列表渲染,特别像有实时数据...
本小节开始前,我们先答复下一个同学的问题。上一小节发布后,有小伙伴后台来信问到:‘小编你只讲了类组件中怎么使用 ref,那在函数式组件中怎么使用呢?’。确实我们...
上一小节我们了解了固定高度的滚动列表实现,因为是固定高度所以容器总高度和每个元素的 size、offset 很容易得到,这种场景也适合我们常见的大部分场景,例如...
上一小节我们处理了 setState 的批量更新机制,但是我们有两个遗漏点,一个是源码中的 setState 可以传入函数,同时 setState 可以传入第二...
我们知道 react 进行页面渲染或者刷新的时候,会从根节点到子节点全部执行一遍,即使子组件中没有状态的改变,也会执行。这就造成了性能不必要的浪费。之前我们了解...
在平时工作中的某些场景下,你可能想在整个组件树中传递数据,但却不想手动地通过 props 属性在每一层传递属性,contextAPI 应用而生。
楼主最近入职新单位了,恰好新单位使用的技术栈是 react,因为之前一直进行的是 vue2/vue3 和小程序开发,对于这些技术栈实现机制也有一些了解,最少面试...
我们上一节了了解了函数式组件和类组件的处理方式,本质就是处理基于 babel 处理后的 type 类型,最后还是要处理虚拟 dom。本小节我们学习下组件的更新机...
前面几节我们学习了解了 react 的渲染机制和生命周期,本节我们正式进入基本面试必考的核心地带 -- diff 算法,了解如何优化和复用 dom 操作的,还有...
我们在之前已经学习过 react 生命周期,但是在 16 版本中 will 类的生命周期进行了废除,虽然依然可以用,但是需要加上 UNSAFE 开头,表示是不安...
上一小节我们学习了 react 中类组件的优化方式,对于 hooks 为主流的函数式编程,react 也提供了优化方式 memo 方法,本小节我们来了解下它的用...
开源不易,感谢你的支持,❤ star me if you like concent ^_^
hel-micro,模块联邦sdk化,免构建、热更新、工具链无关的微模块方案 ,欢迎关注与了解
本文主题围绕concent的setup和react的五把钩子来展开,既然提到了setup就离不开composition api这个关键词,准确的说setup是由...
ReactsetState的执行是异步还是同步官方文档是这么说的setState()doesnotalwaysimmediatelyupdatethecomponent.Itmaybatchordefertheupdateuntillater.Thismakesreadingthis.staterightaftercallingsetState()apotentialpitfall.Instead,usecom