jsx-in-depth

深入JSX

JSX是一个JavaScript语法的一个扩展,它看起来很像XML.你可以在React里使用JSX语法转换

为什么是JSX

你不一定要在React里使用JSX,你可以使用纯JS,但是,我们建议你使用JSX,因为它提供了简洁,熟悉的语法来定义带有属性的树结构

对于临时发开人员比如设计师来说,JSX更亲切.

XML有标签的打开和闭合.这在树结构较大的时候,比调用函数或对象字面量更容易阅读.

它并不改变JavaScript语法.

HTML标签和React组件

React既可以渲染HTML标签(字符串),也可以渲染React组件(类)

要渲染一个HTML标签,请在JSX中使用小写的标签名:

var myDivElement = <div className="foo" />;
ReactDOM.render(myDivElement, document.getElementById('example'));

要渲染一个React组件,请创建一个本地变量,以大写字母开头:

var MyComponent = React.createClass({/*...*/});
var myElement = <MyComponent someProperty={true} />;
ReactDOM.render(myElement, document.getElementById('example'));

React的JSX约定: 通过使用大写和小写字母开头命名,来区分本地的组件和HTML标签.

Note: 由于JSX是JavaScript,所以,classfor这类标识符不建议被用于XML的属性名. 取而代之的是,React的DOM建议给DOM元素的属性命名为类似于 classNmae,htmlFor 这样的规则.

转换

React JSX 会把像XML一样的语法转换成原生JavaScript. XML元素,属性,子元素,都会变成参数,传递给 React.createElement.

var Nav;
// Input (JSX):
var app = <Nav color="blue" />;
// Output (JS):
var app = React.createElement(Nav, {color:"blue"});

注意,要使用 <Nav /> , 作用域里必须有 Nav 变量.

JSX还可以使用XML语法来定义子元素:

var Nav, Profile;
// Input (JSX):
var app = <Nav color="blue"><Profile>click</Profile></Nav>;
// Output (JS):
var app = React.createElement(
  Nav,
  {color:"blue"},
  React.createElement(Profile, null, "click")
);

当 displayName 属性未被定义的时候, JSX 会通过它赋值的变量名来推断 displayName

// Input (JSX):
var Nav = React.createClass({ });
// Output (JS):
var Nav = React.createClass({displayName: "Nav", });

通过 Babel REPL 来体验JSX是如何变成纯JavaScript的. 还有 HTML to JSX converter 这个网站可以把HTML转换成JSX.

如果你想要使用JSX,Getting Started教程会展示如何设置编译.

Note: JSX表达式最终的结果总是一个ReactElement.实际执行的细节会有一些变化.有一种优化的方法,它可以把ReactElement作为一个对象字面量内联进去,从而避免 React.createElement 的验证代码.

命名空间的组件

如果你要创建一个有很多子元素的组件,比如form,你很可能会需要申明很多变量名.

// Awkward block of variable declarations
var Form = MyFormComponent;
var FormRow = Form.Row;
var FormLabel = Form.Label;
var FormInput = Form.Input;

var App = (
  <Form>
    <FormRow>
      <FormLabel />
      <FormInput />
    </FormRow>
  </Form>
);

为了简化它,命名空间的组件可以使用一个组件以及它的属性来作为它的子组件.

var Form = MyFormComponent;

var App = (
  <Form>
    <Form.Row>
      <Form.Label />
      <Form.Input />
    </Form.Row>
  </Form>
);

要实现这个功能,你只需要创建你的子组件作为父组件的属性就可以了.

var MyFormComponent = React.createClass({ ... });

MyFormComponent.Row = React.createClass({ ... });
MyFormComponent.Label = React.createClass({ ... });
MyFormComponent.Input = React.createClass({ ... });

JSX在编译的时候会正确处理他们.

var App = (
  React.createElement(Form, null,
    React.createElement(Form.Row, null,
      React.createElement(Form.Label, null),
      React.createElement(Form.Input, null)
    )
  )
);

Note: 这个特性只在v0.11及以上被支持.

JavaScript 表达式

属性里的表达式

要在属性里使用JavaScript表达式,把表达式包在 {} 里,而不是 “” 里

// Input (JSX):
var person = <Person name={window.isLoggedIn ? window.name : ''} />;
// Output (JS):
var person = React.createElement(
  Person,
  {name: window.isLoggedIn ? window.name : ''}
);

属性值是一个布尔值

如果一个属性的值被省略了,JSX会认为它的默认值就是 true. 如果要让它的值为 false,就必须要传入表达式, 这在HTML form表单里很常见,比如 disabled required checked readonly 属性等…

// 下面这两种禁用按钮的方式在JSX里是等价的.
<input type="button" disabled />;
<input type="button" disabled={true} />;

// 下面这两种不禁用按钮的方式在JSX里是等价的.
<input type="button" />;
<input type="button" disabled={false} />;

子元素表达式

同样的,JavaScript表达式也可以表示子元素

// Input (JSX):
var content = <Container>{window.isLoggedIn ? <Nav /> : <Login />}</Container>;
// Output (JS):
var content = React.createElement(
  Container,
  null,
  window.isLoggedIn ? React.createElement(Nav) : React.createElement(Login)
);

注释

在JSX里添加注释很简单;他们只是JS表达式.有一点需要注意,当你在标签的子元素里使用注释时,使用 {} 把注释内容包起来.

var content = (
  <Nav>
    {/* child comment, put {} around */}
    <Person
      /* multi
         line
         comment */
      name={window.isLoggedIn ? window.name : ''} // end of line comment
    />
  </Nav>
);

Note: JSX和HTML相似,但不完全相同.查看 JSX gotchas ,里面列出了一些常见的坑.

查看原文


end

displaying date

展示数据

视图最基本的任务就是展示数据.React使得数据的展示变得简单,并且当数据发生变化时自动更新界面.

开始

看个最简单的例子.创建一个 hello-react.html 文件,代码如下:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Hello React</title>
    <script src="https://fb.me/react-0.14.7.js"></script>
    <script src="https://fb.me/react-dom-0.14.7.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js"></script>
  </head>
  <body>
    <div id="example"></div>
    <script type="text/babel">

      // ** Your code goes here! **

    </script>
  </body>
</html>

文本剩下的内容围绕在上面的这个模板里注释部分应该写的JavaScript代码以及JSX代码

var HelloWorld = React.createClass({
  render: function() {
    return (
      <p>
        Hello, <input type="text" placeholder="Your name here" />!
        It is {this.props.date.toTimeString()}
      </p>
    );
  }
});

setInterval(function() {
  ReactDOM.render(
    <HelloWorld date={new Date()} />,
    document.getElementById('example')
  );
}, 500);

响应式更新

在浏览器里打开 hello-react.html ,在文本框里输入你的名字.注意,React只改变视图的时间部分 - 你输入的名字还在,虽然你没有写任何代码来指定这个行为,但React已经识别了你的需求,帮你做了正确的处理.

React识别它的方法是: 如果没有必要,React就不会操作DOM.它使用了一个内部的虚拟DOM来比较差异,为你计算出最高效的改变DOM的方法.

这个组件的数据输入被称为 props - “properties”的缩写.他们会被传入JSX语法的属性里.(比如上面这个例子的 date 属性).注意,props 在组件内是不能被改变的,所以,不要给 this.props 赋值.

组件就像函数

React组件非常简单.你可以认为他们就是简单的函数,函数带有propsstate(后面会说到)这和两个参数,然后负责渲染HTML.有了这个概念,组件就很容易理解了.

注意: 有一个限制,React组件只能渲染一个根节点.如果你想要返回多个节点,他们必须被包在一个根节点里.

JSX 语法:

我们十分确定,比起”模板”和”展示逻辑”,组件可以更好的分离关注点.我们认为,html和渲染html的代码,他们是紧密相连的.展示逻辑通常非常复杂,而使用模板引擎来展示它则变得更难以处理.

我们发现,最有效的解决这个问题的办法就是,直接从javascript代码来生成HTML和组件数.这样就好像是使用一种富有表达力的编程语言来创建视图.

为了让它变得更简单,我们加入了一种简单的,可选的类似HTML的语法来创建这些React树节点.

JSX允许你使用HTML语法来创建JavaScript对象.使用原生的JavaScript来生成一个节点可以这样写:

React.createElement('a', {href: 'https://facebook.github.io/react/'}, 'Hello!')

使用JSX语法后变成这样:

<a href="https://facebook.github.io/react/">Hello!</a>

可以看到,这使得创建React应用变得简单,并且是开发者喜欢的语法,但每个人都有自己的工作流程,所以并不是一定要在React里使用JSX语法**

JSX非常小.了解更多关于JSX的知识,查看:JSX in depth,或者查看在线转换: the Babel REPL

JSX和HTML非常相似,但是不完全相同.查看JSX gotchas,里面包含他们的主要不同点.

Babel提供了许多帮助你使用JSX的方法,从命令行工具到Ruby on Rails的集成都有.选择最适合你的方式.

不使用JSX

JSX完全是可选的,你在写React的时候,不一定要使用JSX,你完全可以通过 React.createElement 方法,使用纯Javascript来创建React元素,它包含了标签的名字或组件,一个属性对象,以及任意数量的子组件作为参数传入.

var child1 = React.createElement('li', null, 'First Text Content');
var child2 = React.createElement('li', null, 'Second Text Content');
var root = React.createElement('ul', { className: 'my-list' }, child1, child2);
ReactDOM.render(root, document.getElementById('example'));

方便起见,你可以把创建自定义组件的函数赋值给一个函数,以便于获取调用.

var Factory = React.createFactory(ComponentClass);
...
var root = Factory({ custom: 'prop' });
ReactDOM.render(root, document.getElementById('example'));

React已经有内置的工厂函数来生成一些常用的标签:

var root = React.DOM.ul({ className: 'my-list' },
             React.DOM.li(null, 'Text Content')
           );

原文地址


end

spread operator

spread operator 当需要多个参数(调用函数时),或多个元素(在数组字面量里)时,可以使用一个简单的表达式来指向他们.

语法

调用函数时:

myFunction(...iterableObj);

在数组字面量里使用时:

[...iterableObj, 4, 5, 6]

在解构赋值中使用:

[a, b, ...iterableObj] = [1, 2, 3, 4, 5];

栗子

取代apply

比如: 当你想把一个数组的值传入函数作为参数,通常会使用 Function.prototype.apply

function myFunction(x, y, z) { }
var args = [0, 1, 2];
myFunction.apply(null, args);

有了ES6的spread语法,你可以这样写:

function myFunction(x, y, z) { }
var args = [0, 1, 2];
myFunction(...args);

参数列表里的任意参数你都可以使用spread,并且可以多次使用:

function myFunction(v, w, x, y, z) { }
var args = [0, 1];
myFunction(-1, ...args, 2, ...[3]);

更强大的数组字面量

比如: 如果你有一个数组,然后想创建一个包含了这个数组的新的数组,那么仅仅使用数组字面量的语法就不够了,你不得不再自己写代码,结合使用 push splice concat 等方法.但有了spread语法,这一切变得很简单:

var parts = ['shoulders', 'knees'];
var lyrics = ['head', ...parts, 'and', 'toes']; // ["head", "shoulders", "knees", "and", "toes"]

就像在参数列表里使用一样,…可以在数组字面量的任何地方使用,并且可以多次使用.

在 new 对象时使用

比如: 在ES5里,通过 apply 方法来调用 new 关键字是不行的.(在ES5里,apply使用的是[[call]],不是[[Construct]]).但在ES6里,spread语法支持它.

var dateFields = readDateFields(database);
var d = new Date(...dateFields);

取代push

比如: push可以往数组里插入一个数组.在ES5里,通常是这样实现的:

var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
// Append all items from arr2 onto arr1
Array.prototype.push.apply(arr1, arr2);

有了ES6的spread,它可以这样写:

var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
arr1.push(...arr2);

原文地址


end

Array.from()

Array.from() 方法从一个类数组或可迭代对象,创建一个新的数组实例.

在 ES6里, 类的语法允许你从内置的类和自定义的类创建子类,因此,像 Array.from 这样类的静态方法也被 Array 的子类所继承了,它创建的是子类的实例,而不是 Array 的实例.

语法

Array.from(arrayLike[, mapFn[, thisArg]])

参数

arrayLike

一个类数组或可迭代对象,用于被转换成数组.

mapFn

可选的.数组里每个元素都要经过这个函数的执行.

thisArg

可选的.当执行 mapFn 的时候,函数的this指针指向这个参数

描述

Array.from() 允许你通过以下对象创建数组对象:

  • 类数组对象(有length属性,并且每个元素都有索引值的对象)
  • 可迭代对象(你可以获取到它的元素的对象,比如MapSet)

Array.from() 有一个可选的参数 mapFn ,可以对创建的数组的每个元素迭代执行 map 函数.说的更清楚点,Array.from(obj,mapFn,thisArg)Array.from(obj).map(mapFn,thisArg) 效果是一样的,只是它不产生中间数组.这对某些数组子类是很有用的,比如 typed arrays,因为对于他们,中间数组的值不得不被删减以适应对应的类型.

from()方法的length属性值是 1.

栗子

// 类数组对象 (arguments) 转数组
function f() {
  return Array.from(arguments);
}

f(1, 2, 3); 
// [1, 2, 3]


// 可迭代对象...
// Set
var s = new Set(["foo", window]);
Array.from(s);   
// ["foo", window]


// Map
var m = new Map([[1, 2], [2, 4], [4, 8]]);
Array.from(m);                          
// [[1, 2], [2, 4], [4, 8]]  


// 字符串
Array.from("foo");                      
// ["f", "o", "o"]
// 查看下面的pollify,可以看到,有一句是 Object(arrayLike),既把参数转换为对象.字符串`'foo'`通过`Object('foo')`以后,变成了这样的对象:
// String {0: "f", 1: "o", 2: "o", length: 3, [[PrimitiveValue]]: "foo"}


// 使用一个箭头函数作为map函数来操作元素.
Array.from([1, 2, 3], x => x + x);      
// [2, 4, 6]


// 生成数字序列
Array.from({length: 5}, (v, k) => k);    
// [0, 1, 2, 3, 4]

Polyfill

Array.from 方法被添加在第六版的 ECMA-262 标准中; 因此,在其他版本的标准中可能不能使用.你可以在你脚本的开头插入下面这段代码来使用它.这个算法事实上也是在ECMA-262第六版里指定的算法. 假设 ObjectTypeError 是已经存在的方法. callback.call 相当于 Function.prototype.call. 另外,由于真正的迭代器不能被polyfilled,所以这个执行环境不支持 ECMA-262第六版所定义的 generic iterable

// Production steps of ECMA-262, Edition 6, 22.1.2.1
// 参考: https://people.mozilla.org/~jorendorff/es6-draft.html#sec-array.from
if (!Array.from) {
  Array.from = (function () {
    var toStr = Object.prototype.toString;
    var isCallable = function (fn) {
      return typeof fn === 'function' || toStr.call(fn) === '[object Function]';
    };
    var toInteger = function (value) {
      var number = Number(value);
      if (isNaN(number)) { return 0; }
      if (number === 0 || !isFinite(number)) { return number; }
      return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number));
    };
    var maxSafeInteger = Math.pow(2, 53) - 1;
    var toLength = function (value) {
      var len = toInteger(value);
      return Math.min(Math.max(len, 0), maxSafeInteger);
    };

    // The length property of the from method is 1.
    return function from(arrayLike/*, mapFn, thisArg */) {
      // 1. 把this对象定义成变量C.
      var C = this;

      // 2. 把需要转换的类数组对象定义成变量items
      var items = Object(arrayLike);

      // 3. 如果遇到意外则抛出错误.(items).
      if (arrayLike == null) {
        throw new TypeError("Array.from requires an array-like object - not null or undefined");
      }

      // 4. mapfn没有定义时, 不执行mapping迭代操作
      var mapFn = arguments.length > 1 ? arguments[1] : void undefined;
      var T;
      if (typeof mapFn !== 'undefined') {
        // 5. else
        // 5. a. 如果 IsCallable(mapfn) 是false, 抛出 TypeError 错误.
        if (!isCallable(mapFn)) {
          throw new TypeError('Array.from: when provided, the second argument must be a function');
        }

        // 5. b. 如果提供了thisArg参数, 把thisArg定义成变量T; 反之,T就是undefined
        if (arguments.length > 2) {
          T = arguments[2];
        }
      }

      // 10. 获取对象的长度
      // 11. 把对象的长度通过 ToLength(lenValue) 转换,并将结果赋值给len.
      var len = toLength(items.length);

      // 13. 如果 IsConstructor(C) 是 true, 则:
      // 13. a. 通过C来执行内部的Construct构造函数,把对象的长度作为参数传入.把执行结果定义为变量A.
      // 14. a. 反之, 创建一个新的数组(len),并把它定义为变量A.
      var A = isCallable(C) ? Object(new C(len)) : new Array(len);

      // 16. 定义变量k,初始值为0.
      var k = 0;
      // 17. 循环 while k < len… (also steps a - h)
      var kValue;
      while (k < len) {
        kValue = items[k];
        if (mapFn) {
          A[k] = typeof T === 'undefined' ? mapFn(kValue, k) : mapFn.call(T, kValue, k);
        } else {
          A[k] = kValue;
        }
        k += 1;
      }
      // 18. 把length添加进去 Put(A, "length", len, true).
      A.length = len;
      // 20. 返回 A.
      return A;
    };
  }());
}

原文地址


end

fromCodePoint()

静态 String.fromCodePoint() 方法通过传入的码点序列来返回一个字符串

语法

String.fromCodePoint(num1[, ...[, numN]])

参数

num1,…,numN

一个码点序列

报错

如果传入一个无效的Unicode码点,会报错 RangeError.(e.g. “RangeError: NaN is not a valid code point”)

描述

由于 fromCodePoint()String 对象的静态方法,所以调用的时候要用 String.fromCodePoint(), 记住它不是你创建的字符串对象的方法.

栗子

使用 fromCodePoint()

String.fromCodePoint(42);       // "*"
String.fromCodePoint(65, 90);   // "AZ"
String.fromCodePoint(0x404);    // "\u0404"
String.fromCodePoint(0x2F804);  // "\uD87E\uDC04"
String.fromCodePoint(194564);   // "\uD87E\uDC04"
String.fromCodePoint(0x1D306, 0x61, 0x1D307) // "\uD834\uDF06\u0061\uD834\uDF07"

String.fromCodePoint('_');      // RangeError
String.fromCodePoint(Infinity); // RangeError
String.fromCodePoint(-1);       // RangeError
String.fromCodePoint(3.14);     // RangeError
String.fromCodePoint(3e-2);     // RangeError
String.fromCodePoint(NaN);      // RangeError

仅使用 String.fromCharCode() 没有办法获取到下面这类高位的码点对应的字符.但是使用String.fromCodePoint()既可以返回两个自己的字符,也可以返回四个字节的字符.
(i.e., 它可以返回一个length为2的单字符,比如’𠮷’);

console.log(String.fromCodePoint(0x2F804)); // or 194564 in decimal

Ployfill

String.fromCharCode() 方法已经被添加为 ES6 的一个标准方法,但还不能被所有的浏览器或环境所支持,可以使用下面的代码来兼容:

/*! http://mths.be/fromcodepoint v0.1.0 by @mathias */
if (!String.fromCodePoint) {
  (function() {
    var defineProperty = (function() {
      // IE 8 only supports `Object.defineProperty` on DOM elements
      try {
        var object = {};
        var $defineProperty = Object.defineProperty;
        var result = $defineProperty(object, object, object) && $defineProperty;
      } catch(error) {}
      return result;
    }());
    var stringFromCharCode = String.fromCharCode;
    var floor = Math.floor;
    var fromCodePoint = function() {
      var MAX_SIZE = 0x4000;
      var codeUnits = [];
      var highSurrogate;
      var lowSurrogate;
      var index = -1;
      var length = arguments.length;
      if (!length) {
        return '';
      }
      var result = '';
      while (++index < length) {
        var codePoint = Number(arguments[index]);
        if (
          !isFinite(codePoint) ||       // `NaN`, `+Infinity`, or `-Infinity`
          codePoint < 0 ||              // not a valid Unicode code point
          codePoint > 0x10FFFF ||       // not a valid Unicode code point
          floor(codePoint) != codePoint // not an integer
        ) {
          throw RangeError('Invalid code point: ' + codePoint);
        }
        if (codePoint <= 0xFFFF) { // BMP code point
          codeUnits.push(codePoint);
        } else { // Astral code point; split in surrogate halves
          // http://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
          codePoint -= 0x10000;
          highSurrogate = (codePoint >> 10) + 0xD800;
          lowSurrogate = (codePoint % 0x400) + 0xDC00;
          codeUnits.push(highSurrogate, lowSurrogate);
        }
        if (index + 1 == length || codeUnits.length > MAX_SIZE) {
          result += stringFromCharCode.apply(null, codeUnits);
          codeUnits.length = 0;
        }
      }
      return result;
    };
    if (defineProperty) {
      defineProperty(String, 'fromCodePoint', {
        'value': fromCodePoint,
        'configurable': true,
        'writable': true
      });
    } else {
      String.fromCodePoint = fromCodePoint;
    }
  }());
}

end