⭐️写在开头
阅读React官网的 RECENT POSTS的个人翻译/摘要(部分)。
英文片段为官网原文片段。
⭐️为什么要使用React
目前已经有很多的JavaScript MVC frameworks
出世,但是为什么Facebook需要创建React
,并且又是什么原因导致我们想要去用它?
不是一个
MVC
框架。是一个可用来构建组件化用户界面的库,同时力挺用于构建那些交互数据随时间改变的可复用的UI组件。不是模板。传统的页面渲染(
JavaScript
渲染),是使用一些模板工具或者直接使用html
标签进行:填充+拼接+渲染。这些构建UI的模板整体抽象了你想要的。
Traditionally, web application UIs are built using templates or HTML directives. These templates dictate the full set of abstractions that you are allowed to use to build your UI.
-
React以不同的方式构建用户界面:分隔整个UI为一块一块小的UI组件
UI Components
。这就意味着我们使用一门真实、充满特色的编程语言来渲染视图,其优于模板渲染有以下几个原因:javaScript是一门灵活、强大的编程语言,其拥有构建抽象对象的能力。这一点在大型应用中非常重要。
通过将标记(标签)与相应的视图逻辑统一起来,React能够更加容易的进行扩展和维护视图。
通过JavaScript code的形式来代替标记(标签)和内容,这个过程没有字符拼接的过程并且减少了XSS漏洞。
同时创建了JSX,它是JavaScript的语法扩展。如果比如原生的JavaScript,你更喜欢HTML的可读性,就使用它吧!
不能再简单的响应式数据更新
当你的应用数据随着时间变化时,React表现是十分耀眼!
在传统的JavaScript应用中,你需要关注哪些数据变化了并且命令式的改变DOM的状态,以便保持视图和数据的一致。甚至通过指令和数据来提供了一种声明式界面的AngularJS,也是通过绑定需要的函数来手动更新DOM节点。
React采用不同的方法:
当组件初始化时调用
render
方法,并且生成一个轻量级的视图表达式。这个表达式将会返回一个标签字符串,并且插入页面文档中去。当数据变化的时候,
render
方法将被会再次调用。为了尽可能的提高更新效率,我们把先旧数据和新数据传入render
方法进行对比,生成一个最小的变化来操作DOM。(render
方法返回的值不是字符串也不是一个DOM元素,而是一个轻量级的DOM表达式)。
HTML仅仅只一个开始
由于React有着自己的轻量级的文本表达式,我们可以做一些更酷的事情:
FaceBook使用React代替html绘制canvas动态图。
结合React和Backbone.Rounter可以完美的构建单页应用(Instagram)。
我们在React内部原理处理,使得React App既能在web上运行,也能通过Objective-C bridge驱动原生IOS视图。
You can run React on the server for SEO, performance, code sharing and overall flexibility。
事件行为在浏览器中保持一次,并且符合标准。默认使用事件代理。
⭐️React v0.3.3
我们将在React v0.4中新增了许多中西,但是与此同时我们发布了React v0.3.3。这个版本解决了人们在使用中遇到的问题,并且使我们的工具更加容易使用。
React
允许反复使用同一个DOMNode渲染不同的组件。
React.renderComponent(<div/>, domNode)
React.renderComponent(<span/>, domNode)
⭐️New in React v0.4: Prop Validation and Default Values
我们收到关于React
问题中,有大部分是关于props
的,特别是人们希望对props
进行验证或者使其有个合理的默认值。
Validation
在使用props之前,我们经常对props的某些参数进行某些特殊的处理,例如:需要确定这些属性是否是特定的类型、需求限定某些值、某些参数的成分是必须的。这些验证我们都可以在render里面处理,但是这回使render显得臃肿!
现在,React v.04
带来的内建的验证模块,你可以写你自己的限定
React.createClass({
propTypes: {
// An optional string prop named "description".
description: React.PropTypes.string,
// A required enum prop named "category".
category: React.PropTypes.oneOf(['News','Photos']).isRequired,
// A prop named "dialog" that requires an instance of Dialog.
dialog: React.PropTypes.instanceOf(Dialog).isRequired
},
...
});
Default Values
在以往的例子里面,我们经常看到以下代码:
React.createClass({
render: function() {
var value = this.props.value || 'default value';
return <div>{value}</div>;
}
});
如果对几个穿插在几个不同组件的props
进行以上操作,这将会产生大量的冗余代码。在React v0.04
中,你可以以申明的方式提供默认值。
React.createClass({
getDefaultProps: function() {
return {
value: 'default value'
};
}
...
});
在render之前,我们使用这些函数进行处理(我们也会在render
之前支持所有的验证函数),以保证你在使用的时候拿到的数据是你所需要的。
Both of these features are entirely optional. We've found them to be increasingly valuable at Facebook as our applications grow and evolve, and we hope others find them useful as well.
⭐️React v0.4.1
React v0.4.1
版本只是进行了小小的改动。主要是进行修复。部分代码在底层修改了,但是这不会影响你调用我们的公共Api。
React
setState的callback参数,会在组件域调用。
click事件已经在移动Safari上支持。
阻止已经在Object.prototype上存在的事件错误处理。
不用设置先前就已经定义了的DOM属性为undefined。
优化对iframe属性的支持。
增加checksums属性来检测和校验服务端渲染和客户端实际渲染内容的是否一致。
JSXTransformer
改良环境检测机制,使其能运行在非浏览器环境。
Improved environment detection so it can be run in a non-browser environment.
⭐️Use React and JSX in Python Applications
今天我们很高兴的宣布PyReact
的初始版本的发布。它使得在你的pyhton
应用中能够很好的使用React
和JSX
。PyReact
设计的宗旨是提供一个转化JSX
为JavaScript
的API。
Usage:show me code
from react import jsx
# For multiple paths, use the JSXTransformer class.
transformer = jsx.JSXTransformer()
for jsx_path, js_path in my_paths:
transformer.transform(jsx_path, js_path)
# For a single file, you can use a shortcut method.
jsx.transform('path/to/input/file.jsx', 'path/to/output/file.js')
Django:support the pip
#install
$ pip install PyReact
#use
PIPELINE_COMPILERS = ( 'react.utils.pipeline.JSXCompiler',)
React Page
Jordan Walke 实现了一个完整的react
项目react-page。其支持客户端和服务端的渲染、使用模板系统进行转化和打包、实时重载。
为什么使用服务端渲染?
-
提高页面初始化速度
在下载
JavaScript
前,一些标签元素将会被展示给用户。在服务端标签元素的构建远远快于客户端。
更快的开发和构建原型
不用等待任何监听脚本或打包器。
更容易的部署
SEO
服务端渲染是如何进行的?
在服务端构建、发送标签给客户端,这样用户可以很快的看到内容。
然后对应的
JavaScript
将会被打包送往客户端。浏览器运行对应
JavaScript
,这样所有的事件句柄、交互、数据更新等将会无缝隙的对服务端生成的标记进行绑定。肉眼看来这一切好像都是发生在客户端,只是更快。
⭐️ React v0.5
Changelog
优化内存使用:通过使用
GC PAUSES
(GC:Garbage Collection,垃圾收集,垃圾回收)机制,减少核心内存分配。提升性能:为了提供代码运行速度,我们从V8和Nitro中剔除了一些
slow path code
。
在网络设备里面区分不同的路径是一个很自然的选择,因为网络设备的首要任务是转发网络包,不同的网络包,在设备里面的处理路径不同。fast path就是那些可以依据已有状态转发的路径,在这些路径上,网关,二层地址等都已经准备好了,不需要缓存数据包,而是可以直接转发。
slow path
是那些需要额外信息的包,比如查找路由,解析MAC地址等。
first path
是设备收到流上第一个包所走过的路径,比如tcp
里面的syn
包,有些实现把三次握手都放到first path
里面处理,而有些只需处理syn
包,其他包就进入fast path
的处理路径。在NP或者ASIC里面也需要区分
slow path
和fast path
,fast path
上的包都放在SRAM
里面,而slow path
的包放在DRAM
里面。不过这也非绝对。决定处理路径的是对于速度的考虑。处理器的速度,内存的速度等。访问内存的速度由速度和带宽两个因素决定。因此,把SRAM
放到fast path
上,也是很自然的选择。
fast path: Frontend -> simple code generator -> assembler
slow path: Frontend -> IR optimizer (sometimes more than one level of IR) -> code-generator -> assembler
标准化支持:
DOM
属性的处理。在处理之前不仅有多余的检查和开销,而且还是混淆使用者。现在我们会一直控制你的属性值为string
直到渲染。支持
Selection
事件。支持混合事件(池化哟)。
支持一些额外DOM属性:
charSet, content, form, httpEquiv, rowSpan, autoCapitalize
。支持额外的SVG属性:
rx
、ry
。同时支持
getInitialState
和getDefaultProps
。支持挂载到
iframes
。表单组件的
bugfix
。增加
react.version
。增加
react.isValidClass
:用户检查某个值是否是有效的组件构造函数。删除
React.autoBind
。在React v0.4中弃用,现在正式删除。重命名:
React.unmountAndReleaseReactRootNode
为React.unmountComponentAtNode
。开始着手于精细的性能分析。
更好的支持服务端渲染。
是React运行于一个严格的安全策略下成为可能。同时使使用React编写chrome插件成为可能。
JSX
属性
class
更名问className
。增加警告提示(非生产环境)。
改善Window兼容性。
⭐️ React v0.9 [RC]
Upgrade Notes
我们JSX
解释器处理空格上面进行了改变。简单来说就是,在一行上面组件与组件之前的空格将会被保留,而换行(文本节点或者组件)会被忽略。
<div>
Monkeys:
{listOfMonkeys} {submitButton}
</div>
0.8-
React.DOM.div(null,
" Monkeys: ",
listOfMonkeys, submitButton
)
0.9+
React.DOM.div(null,
"Monkeys:",
listOfMonkeys, " ", submitButton
)
相信这个新特性会非常有空,可以有效减少匆忙之中带来的非期待的空格。
如果你希望保留 后面紧跟换行文本节点 后面的空格,那么你可以在JSX
里面使用这种写法:{"Monkeys: }
。
⭐️ The Road to 1.0 (2014-03-28)
我们在去年春天发布了React
,但是我们却有意的没有发布版本React v1.0
。其实我们已经做好了生产准备,但是我们计划在内部和外部根据开发者怎么使用React
来发展API和行为特性。在过去的九个月我们学到了很多,并且思考很多关于1.0对于React意味着什么。在过去的两周内,我在多个项目里面概述了我们打算进入1.0+的世界。今天我书写一点点给我们的用户,以便用户更好的了解我们的计划。
我们在1.0中主要的目的是阐明我们的消息模式,并且聚焦在一个与我们目的相关的API上面进行处理。为了完成这个目的,我们清除一些已经遇到的不友好的模式,真正的帮助开发者写出更好的代码。
ES6
在我们正式推出React
之前,我们就思考过如何在React
里面利用ES6
,即类,以此来提高创建React组件的体验。我们觉得使用 React.createClass(…)
不是一个最好的选择。即使在使用方面优劣性没有正确的答案,但是我们在向ES6
方面靠拢。我们希望确保这个过程尽可能的简单。例如:
class MyComponent extends React.Component {
render() {
...
}
}
其他一些ES6
的特性我们在React
核心使用了。确信后面会有更多的特性加入。
JSX
在ES6
方面的支持我们已经在react-tools
里面搭船上线。已经支持转化大部分的ES6
代码转换为老浏览器支持的代码。
Context
尽管我们没有在文档中提及过context
,但是它以某种形式在React
确切存在的。
While we haven't documented context, it exists in some form in React already. It exists as a way to pass values through a tree without having to use props at every single point. We've seen this need crop up time and time again, so we want to make this as easy as possible. Its use has performance tradeoffs, and there are known weaknesses in our implementation, so we want to make sure this is a solid feature.
⭐️ React v0.11
getDefaultProps
从React0.11开始,getDefaultProps()
只会在 React.createClass()
调用的时候调用一次,替代原来每次组件渲染时调用。这就意味着 getDefaultProps()
不会改变他的返回值,同时任何对象将会在所有实例中共享。这个改变提示了性能,并且使得将来能够更早的在渲染中做 PropTypes check ,这将使我们能够给出更好的错误提示。
Rendering to null
自从React发布以来,开发者基本都遇到过 render nothing 的情况。通常是返回一个空<div/>
或者<span/>
。有些更聪明的人返回<noscript/>
来避免不要的DOM nodes
。我们对此提供了一个解决办法:return null 。这样能够进一步帮忙开发者写出有意义的代码。在实现上,我们使用了<noscript>
标签进行处理,尽管我们目的是不返回任何东西。因为noscript
并不会影响你的布局,所以你可以放心使用。
// Before
render: function() {
if (!this.state.visible) {
return <span/>;
}
// ...
}
// After
render: function() {
if (!this.state.visible) {
return null;
}
// ...
}
JSX Namespacing
在JSX
里面支持namespaceing
呼喊我们已经听到了很长一段时间。考虑到JSX
是JavaScript实现的,所以我们不愿意使用XML namespaceing
。相反,我们使用选择JavaScript
标准来实现: object property access
。替代为每个组件分配一个对象,你可以直接这样使用 <Namespace.Component />
。
// Before
var UI = require('UI');
var UILayout = UI.Layout;
var UIButton = UI.Button;
var UILabel = UI.Label;
render: function() {
return <UILayout><UIButton /><UILabel>text</UILabel></UILayout>;
}
// After
var UI = require('UI');
render: function() {
return <UI.Layout><UI.Button /><UI.Label>text</UI.Label></UI.Layout>;
}
Improved keyboard event normalization
根据DOM3,React键盘事件包含了一个标准化的 e.key
,这样允许你在代码中编写一个简单的key,并且能够在所有浏览器运行。
handleKeyDown: function(e) {
if (e.key === 'Enter') {
// Handle enter key
} else if (e.key === ' ') {
// Handle spacebar
} else if (e.key === 'ArrowLeft') {
// Handle left arrow
}
}
React
键盘事件和鼠标事件也包含了标准化的 e.getModifierState()
。
⭐️ Flux: Actions and the Dispatcher
Flux
是Facebook构建JavaScript
应用的时候使用的基于单项数据流的应用框架。我们使用Flux
的构建大型应用都是有小的组件组成,Flux
来控制我们提供的小组件(们)。我们找到了一个非常棒的代码组织结构,我们十分激动的分享到开源社区。
比如一个完整的框架,Flux
更像一种模式,让你不用增加太多新代码就能够使用Flux
。直到最近的,我们还没有发布Flux
模块之一: dispatcher
。但是随着新的Flux code
项目和Flux website
发布,我们提供我们在生产项目中使用的dispatcher
。
Where the Dispatcher Fits in the Flux Data Flow
dispatcher
是一个单例。作为数据控制中心控制Flux
应用的数据流。简单的来说,他就是提供注册回调函数,然后使用一定的命令调用这些回调函数。每个store
都通过dispatcher
注册了回调。当dispatcher
中有新数据来,它使用这些回调通知相应的store
。然后相关程序通过dispatch()
启动回调函数。
Actions and ActionCreators
无论是用户进行界面操作还是接口返回的新数据进入系统的时候,这些数据将会被打包送入一个 action
(由内容和action type
组成的对象)。对此,我们常常创建名为ActionCreateors
的辅助库,用来创建action object
和传递action
给dispatcher
。
不同的actioins
由一个type
属性定义。当所有的stores
收到action
的时候,它们就使用这个属性来决定怎么响应它。在Flux
应用中,stores
和views
彼此自我控制,它们不会被外部影响。操作流通过stores
定义注册的回调进入store
, not through setter methods。
使stores
自我更新能够避免很多一些MVC
应用的复杂情况,例如:各个models
之间的联合更新会导致状态的不稳定并且会导致测试非常困难。objects
在Flux
应用中高度分离,并且严格准守得墨忒耳定律。这样会导致软件更加可维护、适配、测试以及对新工程师来谁更加容易理解。
Why We Need a Dispatcher
随着应用的壮大,不同stores
之间的依赖必然存在。例如:Store A 必须 Store A 先更新,然后 Store A才知道如何去更新自己。这个时候我们就需要dispatcher
能够调用 Store B的回调,之后再操作Store A。为了申明这种依赖,Store A 需要告诉dispatcher
,我需要等待Store B完成后才能执行这个action
。dispatcher
通过 waitFor()
提供这样的功能。
dispatch()
方法通过回调函数提供了一个简单的、同步迭代功能:依次调用。当waitFor()
在某一个回调中触发,随后停止执行这个回调函数,并且 waitFor()
将提供我们一个有关依赖的新的迭代周期。等这些依赖执行完以后,回调函数再继续执行。
更者,waitFor()
方法可是在同一个store
中不不同的actions
间调用。
Problems arise, however, if we have circular dependencies. That is, if Store A needs to wait for Store B, and Store B needs to wait for Store A, we could wind up in an endless loop. The dispatcher now available in the Flux repo protects against this by throwing an informative error to alert the developer that this problem has occurred. The developer can then create a third store and resolve the circular dependency.
⭐️Introducing React Elements
If you currently use JSX everywhere, you don't really have to do anything to get these benefits! The updated transformer will do it for you.
If you can't or don't want to use JSX, then please insert some hints for us. Add a React.createFactory call around your imported class when you require it。
New Terminolog
我们为了使新用户更简单的了解DOM
(和React
的不同之处)。我们使用术语ReactElement
代替<>
,同样ReactNode
代替renderable
。
Creating a ReactElement
我们提供一个API来创建ReactElement
。
var reactElement = React.createElement(type,props,children);
type
参数可以使HTML tag
或者class
。它指示着什么样的HTML tag
或者class
将被渲染和包含哪些props
数据。你也可以只提供一个type
参数创建一个工程函数。
var div = React.createFactory('div');
var reactDivElement = div(props,children);
React Element
的签名就像这样
{
type : string | class,
props : { children, className, etc. },
key : string | boolean | number | null,
ref : string | null
}
Upgrading to 0.12
React With JSX
如果你使用React
的JSX
转化器,这个升级将会非常简单。
// If you use node/browserify modules make sure
// that you require React into scope.
var React = require('react');
React
的JSX将会为你创建ReactElement
。
var MyComponent = React.createClass(...);
var MyOtherComponent = React.createClass({
render: function() {
return <MyComponent prop="value" />;
}
});
React Without JSX
在不使用JSX
情况下需要调用一个组件作为函数,在调用前你需要明确的创建一个工程函数。
var MyComponentClass = React.createClass(...);
var MyComponent = React.createFactory(MyComponentClass); // New step
var MyOtherComponent = React.createClass({
render: function() {
return MyComponent({ prop: 'value' });
}
});
React
为常见的HTML elements
内置了工厂函数。
var MyDOMComponent = React.createClass({
render: function() {
return React.DOM.div({ className: 'foo' }); // still ok
}
});
The Next Step: ES6 Classes
在v0.12版本后,我们的工作将转向ES6 classes
。我们会保持向后兼容(React.createclass
)。如果你已经在使用ES6转译器,你可以按照下面申明你的组件。
export class MyComponent {
render(){
...
}
};
⭐️Deprecating JSTransform and react-tools
随着JavaScript
的发展,JSTransform
有点"跟不上时代",Babel
的出现可以完全将其替代。v0.14以后将不会再维护JSTransform
和react-tools
(react-tools
has always been a very thin wrapper around JSTransform.),React
和React Native
目前都使用三方的Babel
的JSX编译器处理.
Other Deprecations
esprima-fb
ECMAScript解析器
JSXTransformer
JSXTransformer is another tool we built specifically for consuming JSX in the browser. It was always intended as a quick way to prototype code before setting up a build process. It would look for <script> tags with type="text/jsx" and then transform and run. This ran the same code that react-tools ran on the server.
⭐️ReactDOM.render and the Top Level React API
在React
的世界里面所有都是组件,但是你涉及的代码、工程并不是都是由React
来构建的。所以,需要你编写管道代码进行两者的链接。处理这些事情的主要API为:
ReactDOM.render(reactElment,domContainerNode)
这需要你提供一个额外的DOM容器。
当把React
插入在单页应用中时候,需要你手动的控制生命周期。React
不会自动释放元素,需要手动控制:
ReactDOM.unmountComponentAtNode(domComtainerNode)
It is not unique to the DOM. If you want to insert a React Native view in the middle of an existing iOS app you will hit similar issues.
Object Oriented Updates
如果你调用ReactDOM.render
多次,那么对应组件上次的props将会被最新的完全替代。
ReactDOM.render(<App locale="en-US" userID={1} />, container);
// props.userID == 1
// props.locale == "en-US"
ReactDOM.render(<App userID={2} />, container);
// props.userID == 2
// props.locale == undefined ??!?
在面向对象编程中,所有的状态依赖实例存在,通过控制状态的改变控制应用变化。如果你在一个使用面向对象API的app里面使用React
,你也许会惊讶或者迷茫当你设置一个属性的时候会导致其他属性的消失。
对此我们提供了一个辅助函数setProps
来允许你一次只更新所期望的属性。
Unfortunately this API lived on a component instance, required React to keep this state internally and wasn't very natural anyway. Therefore, we're deprecating it and suggest that you build it into your own wrapper instead.
class ReactComponentRenderer {
constructor(klass, container) {
this.klass = klass;
this.container = container;
this.props = {};
this.component = null;
}
replaceProps(props, callback) {
this.props = {};
this.setProps(props, callback);
}
setProps(partialProps, callback) {
if (this.klass == null) {
console.warn(
'setProps(...): Can only update a mounted or ' +
'mounting component. This usually means you called setProps() on ' +
'an unmounted component. This is a no-op.'
);
return;
}
Object.assign(this.props, partialProps);
var element = React.createElement(this.klass, this.props);
this.component = ReactDOM.render(element, this.container, callback);
}
unmount() {
ReactDOM.unmountComponentAtNode(this.container);
this.klass = null;
}
}
Object-oriented APIs don't look like that though. They use setters and methods. I think we can do better. If you know more about the component API that you're rendering, you can create a more natural object-oriented API around your React component.
class ReactVideoPlayer {
constructor(url, container) {
this._container = container;
this._url = url;
this._isPlaying = false;
this._render();
}
_render() {
ReactDOM.render(
<VideoPlayer url={this._url} playing={this._isPlaying} />,
this._container
);
}
get url() {
return this._url;
}
set url(value) {
this._url = value;
this._render();
}
play() {
this._isPlaying = true;
this._render();
}
pause() {
this._isPlaying = false;
this._render();
}
destroy() {
ReactDOM.unmountComponentAtNode(this._container);
}
}
⭐️React Components, Elements, and Instances
Components
、Elements
、Component instrances
三者的不同也许迷惑着许多初学者。为什么三个不同的事物合作能够在屏幕上绘图。
Managing the Instances
如果你是初学者,你也许开始是和Component classes
和Component classes Instances
打交道。栗如,创建class
来申明Button
组件。当应用运行时,你可能有Button
组件的多个实例,每个实例有着自己的属性和本地状态。这是传统的面向对象的UI编程。
在传统的UI模块中,由你来控制创建和销毁组件实例。栗:如果一个Form
组件要渲染一个Button
组件。它需要一个Button
的实例并且手动保持和任何信息的交流。
class Form extends TraditionalObjectOrientedView {
render() {
// Read some data passed to the view
const { isSubmitted, buttonText } = this.attrs;
if (!isSubmitted && !this.button) {
// Form is not yet submitted. Create the button!
this.button = new Button({
children: buttonText,
color: 'blue'
});
this.el.appendChild(this.button.el);
}
if (this.button) {
// The button is visible. Update its text!
this.button.attrs.children = buttonText;
this.button.render();
}
if (isSubmitted && this.button) {
// Form was submitted. Destroy the button!
this.el.removeChild(this.button.el);
this.button.destroy();
}
if (isSubmitted && !this.message) {
// Form was submitted. Show the success message!
this.message = new Message({ text: 'Success!' });
this.el.appendChild(this.message.el);
}
}
}
上面的伪复合UI代码(或者增加更多)都是按照面向对象的方式使用库,就像Backbone
一样。每个组件实例都保持DOM node
、Child Component
关联并且在合适的时间创建/销毁(DOM node
、Child Component
)。随着代码量的增加,组件可能的状态数会按平方级增长,并且父级可以直接访问子组件的实例,将来想要解偶就会变的非常困难。
比起上述,React
有什么不同?
Elements Describe the Tree
为解决上述问题,React
里面出现了Elements
。An element is a plain object describing a component instance or DOM node and its desired properties.
它包含了一些唯一的信息:组件类型(Button
)、属性(color
)和拥有的Child Elements
。
PlainObject:JSON形式定义的普通对象或者new Object()创建的简单对象。
一个Element
不是一个实际的实例。相反,他是一种告诉React
什么需要渲染在屏幕上的方式。你不能调用任何在Elements
上面的方法,它仅仅是一个拥有两个字段属性的不可变对象(type : ( string | ReactClass )
和props : Object
)。
DOM Elements
当Element
的type
是一个字符串时,它代表着一个拥有props
作为属性的DOM node
(这些就是React
将会渲染的)。栗子:
{
type: 'button',
props: {
className: 'button button-blue',
children: {
type: 'b',
props: {
children: 'OK!'
}
}
}
}
⇩
<button class='button button-blue'>
<b>
OK!
</b>
</button>
对于Elements
嵌套的问题,按照惯例,我们希望创建一个Elements
树,然后指定一个或者多个Child Elements
作为其容器/父元素的children props
。
最重要的一点还是Child Elements
和Parent Elements
仅仅只一中描述方式并不是真实实例。当你创建它们的时候并不涉及在屏幕上面呈现的东西。创建和丢弃他们都是无关紧要的。
React elements are easy to traverse, don’t need to be parsed, and of course they are much lighter than the actual DOM elements—they’re just objects!
Component Elements
然后Elements
的type
参数可以使一个React Component
对应的函数或者类。
{
type : Button,
props : {
color : "blue",
children : "OK!",
}
}
这是React
的核心思想。
An element describing a component is also an element, just like an element describing the DOM node. They can be nested and mixed with each other.
这个特性可以让你创建一个DangerButton
,它是一个拥有特定color
属性值的Button
组件。并且不用当心Button
是否渲染为DOM <button>
、<div>
或者其他的。
const DangerButton = ({ children }) => ({
type: Button,
props: {
color: 'red',
children: children
}
});
你可以混合搭配DOM/Component Elements
在一个简单的Element Tree
里面。
const DeleteAccount = () => ({
type: 'div',
props: {
children: [{
type: 'p',
props: {
children: 'Are you sure?'
}
}, {
type: DangerButton,
props: {
children: 'Yep'
}
}, {
type: Button,
props: {
color: 'blue',
children: 'Cancel'
}
}]
});
或者你使用JSX
const DeleteAccount = () => (
<div>
<p>Are you sure?</p>
<DangerButton>Yep</DangerButton>
<Button color='blue'>Cancel</Button>
</div>
);
这种混合和匹配有助于降低组件之间的耦合度,因此它们完全可以通过一下的结构同时表达is-a和has-a的关系
Button组件是一个具有特定属性的DOM元素button;
DangerButton组件是一个具有特定属性的Button组件;
DeleteAccount在一个div元素中包含一个Button组件和一个DangerButton组件。
Components Encapsulate Element Trees
当React
收到一个使用函数或者类作为type
值得Element
的时候,它知道询问对应的组件什么样的Element
需要被渲染,并给予相应的props
。
当看到这个Element
的时候
{
type: Button,
props: {
color: 'blue',
children: 'OK!'
}
}
React
将会询问Button Component
什么需要渲染。Button Component
将会返回
{
type: 'button',
props: {
className: 'button button-blue',
children: {
type: 'b',
props: {
children: 'OK!'
}
}
}
}
React
将重复此过程,直到它知道页面上的每个组件有用的底层DOM
标签元素。
前面提到的Form
实例使用React
可以这样
const Form = ({ isSubmitted, buttonText }) => {
if (isSubmitted) {
// Form submitted! Return a message element.
return {
type: Message,
props: {
text: 'Success!'
}
};
}
// Form is still visible! Return a button element.
return {
type: Button,
props: {
children: buttonText,
color: 'blue'
}
};
};
以上,对于React Component
组件,props
是输入,Element Tree
是输出。
The returned element tree can contain both elements describing DOM nodes, and elements describing other components. This lets you compose independent parts of UI without relying on their internal DOM structure.
返回的元素树包括描述DOM node
及描述其它Component
。这可以让我们独立地编写UI部分,而无需依赖它们的内部DOM
结构。
我们使React
创建、更新、销毁实例。
我们使用Components
返回的Elements
描述实例,React
负责管理实例。
Components Can Be Classes or Functions
// 1) As a function of props
const Button = ({ children, color }) => ({
type: 'button',
props: {
className: 'button button-' + color,
children: {
type: 'b',
props: {
children: children
}
}
}
});
// 2) Using the React.createClass() factory
const Button = React.createClass({
render() {
const { children, color } = this.props;
return {
type: 'button',
props: {
className: 'button button-' + color,
children: {
type: 'b',
props: {
children: children
}
}
}
};
}
});
// 3) As an ES6 class descending from React.Component
class Button extends React.Component {
render() {
const { children, color } = this.props;
return {
type: 'button',
props: {
className: 'button button-' + color,
children: {
type: 'b',
props: {
children: children
}
}
}
};
}
}
When a component is defined as a class, it is a little bit more powerful than a functional component. It can store some local state and perform custom logic when the corresponding DOM node is created or destroyed.
A functional component is less powerful but is simpler, and acts like a class component with just a single render() method. Unless you need features available only in a class, we encourage you to use functional components instead.
Top-Down Reconciliation
ReactDOM.render({
type: Form,
props: {
isSubmitted: false,
buttonText: 'OK!'
}
}, document.getElementById('root'));
你当运行上述代码的时候,React
会询问Form
组件返回的Element Tree
,给予对应props
。
// React: You told me this...
{
type: Form,
props: {
isSubmitted: false,
buttonText: 'OK!'
}
}
// React: ...And Form told me this...
{
type: Button,
props: {
children: 'OK!',
color: 'blue'
}
}
// React: ...and Button told me this! I guess I'm done.
{
type: 'button',
props: {
className: 'button button-blue',
children: {
type: 'b',
props: {
children: 'OK!'
}
}
}
}
This is a part of the process that React calls reconciliation which starts when you call ReactDOM.render() or setState(). By the end of the reconciliation, React knows the result DOM tree, and a renderer like react-dom or react-native applies the minimal set of changes necessary to update the DOM nodes (or the platform-specific views in case of React Native).
You might have noticed that this blog entry talks a lot about components and elements, and not so much about the instances. The truth is, instances have much less importance in React than in most object-oriented UI frameworks.
Summary
Element
只是是PlainObject
,用来描述呈现在屏幕上的DOM node
或其它Component
。Elements
的props中可以包含其它Elements
。创建ReactReact Element
是廉价的,一旦创建了React Element
,就不会再发生变化。
一个组件的申明有几个不同的方式:class
、function
、React.creatClass
。无论哪种方式,总是props
为输入,Element Tree
为输出。
An instance is what you refer to as this in the component class you write. It is useful for storing local state and reacting to the lifecycle events.(实例就是对组件类中this的引用。)
最后,创建React Elements
使用React.createElement()
、JSX
或者Element Factory helper
。别在实际代码中编写PlainObect
形式的Elements
(只需要知道在底层他们是PlainObject
就行了)。
⭐️(A => B) !=> (B => A)
文档里面对于componentWillReceiveProps
的陈述为:componentWillReceiveProps
在对应props
被改变的时候调用,并作为rerender
的结果。这导致部分用户认为:componentWillReceiveProps
被调用了对应props
一定会变化。逻辑上这个结论是不正确的。
formal logic/mathematics
:A包含着B,不代表B包含着A。有很多原因导致componentWillReceiveProps
被调用,即使对应的props
没有改变。
你如果不相信,可以试试使用准确的props
三次调用ReactDOM.render()
,并且监控componentWillReceiveProps
的调用。
class Component extends React.Component {
componentWillReceiveProps(nextProps) {
console.log('componentWillReceiveProps', nextProps.data.bar);
}
render() {
return <div>Bar {this.props.data.bar}!</div>;
}
}
var container = document.getElementById('container');
var mydata = {bar: 'drinks'};
ReactDOM.render(<Component data={mydata} />, container);
ReactDOM.render(<Component data={mydata} />, container);
ReactDOM.render(<Component data={mydata} />, container);
以上代码componentWillReceiveProps
会被调用两次。
为了理解为什么会这样,我们需要想想会发生什么。在初始渲染和两次后续更新之间数据可能已经被改变了,如果代码像下面这样执行:
var myData = {
bar: 'drinks'
};
ReactDOM.render(<Component data={myData} />, container);
myData.bar = 'food';
ReactDOM.render(<Component data={myData} />, container);
myData.bar = 'noise';
ReactDOM.render(<Component data={myData} />, container);
数据没有改变,但React并没有办法知道。因此,React
需要调用componentWillReceiveProps
方法,因为组件需要新props
来通知(即使新的props
和旧props
完全相同)。
你可能会认为,React
可以使用很巧妙的检测机制来检测是否相等,但这种想法也有一些问题
旧的myData和新的myData实际上是相同的物理对象(仅对象内部的值被改变)。由于采用的是
triple-equals-equal
,检查是否相同的时候并不会告诉我们值是否被改变。惟一可能的解决方法就是创建数据的一个深拷贝副本,接着做深比较,但这对比较大的数据结构而言过于昂贵(特别是循环)myData对象可能包括对函数的引用,该函数通过闭包获取变量。
React
没有办法获取闭包内部的变量值,因此也没有办法复制和验证它们是否相等myData可能包括父级渲染时重新实例化了的实例对象的引用,但概念上是相等的(具有相同的
key
和value
)。深比较可以检测到这一点,除过这点又会出现新的问题,因为没有办法比较两个函数在语义上是否相同。
由于语言的限制,有时我们不可能实现真正意义上相等的语义。在这种情况下,React
会调用componentWillReceiveProps
方法(即使props
可能没有改变),使得组件有机会检测新的props,并采取相应的处理。
这样一来,实现componentWillReceiveProps
方法时候要确保props
不能被修改。如果你想在props
被改变后执行一些操作(如网络请求),你的componentWillReceiveProps
代码需要检查props
是否真正的被改变了。