⭐️写在开头

  • 阅读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应用中能够很好的使用ReactJSXPyReact设计的宗旨是提供一个转化JSXJavaScript的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 pathfast pathfast 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属性:rxry

  • 同时支持getInitialStategetDefaultProps

  • 支持挂载到iframes

  • 表单组件的bugfix

  • 增加react.version

  • 增加react.isValidClass:用户检查某个值是否是有效的组件构造函数。

  • 删除React.autoBind。在React v0.4中弃用,现在正式删除。

  • 重命名:React.unmountAndReleaseReactRootNodeReact.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核心使用了。确信后面会有更多的特性加入。

JSXES6方面的支持我们已经在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和传递actiondispatcher

不同的actioins由一个type属性定义。当所有的stores收到action的时候,它们就使用这个属性来决定怎么响应它。在Flux应用中,storesviews彼此自我控制,它们不会被外部影响。操作流通过stores定义注册的回调进入store, not through setter methods。

使stores自我更新能够避免很多一些MVC应用的复杂情况,例如:各个models之间的联合更新会导致状态的不稳定并且会导致测试非常困难。objectsFlux应用中高度分离,并且严格准守得墨忒耳定律。这样会导致软件更加可维护、适配、测试以及对新工程师来谁更加容易理解。


Why We Need a Dispatcher

随着应用的壮大,不同stores之间的依赖必然存在。例如:Store A 必须 Store A 先更新,然后 Store A才知道如何去更新自己。这个时候我们就需要dispatcher能够调用 Store B的回调,之后再操作Store A。为了申明这种依赖,Store A 需要告诉dispatcher,我需要等待Store B完成后才能执行这个actiondispatcher通过 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

如果你使用ReactJSX转化器,这个升级将会非常简单。

// 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以后将不会再维护JSTransformreact-toolsreact-tools has always been a very thin wrapper around JSTransform.),ReactReact 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

ComponentsElementsComponent instrances三者的不同也许迷惑着许多初学者。为什么三个不同的事物合作能够在屏幕上绘图。

Managing the Instances

如果你是初学者,你也许开始是和Component classesComponent 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 nodeChild Component关联并且在合适的时间创建/销毁(DOM nodeChild Component)。随着代码量的增加,组件可能的状态数会按平方级增长,并且父级可以直接访问子组件的实例,将来想要解偶就会变的非常困难。

比起上述,React有什么不同?

Elements Describe the Tree

为解决上述问题,React里面出现了ElementsAn 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

Elementtype是一个字符串时,它代表着一个拥有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 ElementsParent 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

然后Elementstype参数可以使一个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或其它ComponentElements的props中可以包含其它Elements。创建ReactReact Element是廉价的,一旦创建了React Element,就不会再发生变化。

一个组件的申明有几个不同的方式:classfunctionReact.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可能包括父级渲染时重新实例化了的实例对象的引用,但概念上是相等的(具有相同的keyvalue)。深比较可以检测到这一点,除过这点又会出现新的问题,因为没有办法比较两个函数在语义上是否相同。

由于语言的限制,有时我们不可能实现真正意义上相等的语义。在这种情况下,React会调用componentWillReceiveProps方法(即使props可能没有改变),使得组件有机会检测新的props,并采取相应的处理。

这样一来,实现componentWillReceiveProps方法时候要确保props不能被修改。如果你想在props被改变后执行一些操作(如网络请求),你的componentWillReceiveProps代码需要检查props是否真正的被改变了。


汻烬椮
18 声望1 粉丝

做一个细致入微的人