您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
   
 
 
     
   
 订阅
  捐助
Facebook 的 React 框架解析
 
作者:Josh Haberman 来源:深度开源 发布于 2016-7-21
   次浏览      
 

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 改变背后的基本理解是这样:

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

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

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 里说的内容,

需要更深入去审视

   
次浏览       
相关文章

Java微服务新生代之Nacos
深入理解Java中的容器
Java容器详解
Java代码质量检查工具及使用案例
相关文档

Java性能优化
Spring框架
SSM框架简单简绍
从零开始学java编程经典
相关课程

高性能Java编程与系统性能优化
JavaEE架构、 设计模式及性能调优
Java编程基础到应用开发
JAVA虚拟机原理剖析
最新活动计划
LLM大模型应用与项目构建 12-26[特惠]
QT应用开发 11-21[线上]
C++高级编程 11-27[北京]
业务建模&领域驱动设计 11-15[北京]
用户研究与用户建模 11-21[北京]
SysML和EA进行系统设计建模 11-28[北京]

Java 中的中文编码问题
Java基础知识的三十个经典问答
玩转 Java Web 应用开发
使用Spring更好地处理Struts
用Eclipse开发iPhone Web应用
插件系统框架分析
更多...   

Struts+Spring+Hibernate
基于J2EE的Web 2.0应用开发
J2EE设计模式和性能调优
Java EE 5企业级架构设计
Java单元测试方法与技术
Java编程方法与技术

Struts+Spring+Hibernate/EJB+性能优化
华夏基金 ActiveMQ 原理与管理
某民航公司 Java基础编程到应用开发
某风电公司 Java 应用开发平台与迁移
日照港 J2EE应用开发技术框架与实践
某跨国公司 工作流管理JBPM
东方航空公司 高级J2EE及其前沿技术
更多...