可复用组件

设计接口的时候,把通用的设计元素(按钮,表单框,布局组件等)拆成接口良好定义的可复用的组件。这样,下次开发相同界面程序时就可以写更少的代码,也意义着更高的开发效率,更少的 Bug 和更少的程序体积。

Prop 验证

随着应用不断变大,保证组件被正确使用变得非常有用。为此我们引入 propTypesReact.PropTypes 提供很多验证器 (validator) 来验证传入数据的有效性。当向 props 传入无效数据时,JavaScript 控制台会抛出警告。注意为了性能考虑,只在开发环境验证 propTypes。下面用例子来说明不同验证器的区别:

  1. React.createClass({
  2. propTypes: {
  3. // 可以声明 prop 为指定的 JS 基本类型。默认
  4. // 情况下,这些 prop 都是可传可不传的。
  5. optionalArray: React.PropTypes.array,
  6. optionalBool: React.PropTypes.bool,
  7. optionalFunc: React.PropTypes.func,
  8. optionalNumber: React.PropTypes.number,
  9. optionalObject: React.PropTypes.object,
  10. optionalString: React.PropTypes.string,
  11. // 所有可以被渲染的对象:数字,
  12. // 字符串,DOM 元素或包含这些类型的数组(or fragment) 。
  13. optionalNode: React.PropTypes.node,
  14. // React 元素
  15. optionalElement: React.PropTypes.element,
  16. // 你同样可以断言一个 prop 是一个类的实例。
  17. // 用 JS 的 instanceof 操作符声明 prop 为类的实例。
  18. optionalMessage: React.PropTypes.instanceOf(Message),
  19. // 你可以用 enum 的方式
  20. // 确保你的 prop 被限定为指定值。
  21. optionalEnum: React.PropTypes.oneOf(['News', 'Photos']),
  22. // 指定的多个对象类型中的一个
  23. optionalUnion: React.PropTypes.oneOfType([
  24. React.PropTypes.string,
  25. React.PropTypes.number,
  26. React.PropTypes.instanceOf(Message)
  27. ]),
  28. // 指定类型组成的数组
  29. optionalArrayOf: React.PropTypes.arrayOf(React.PropTypes.number),
  30. // 指定类型的属性构成的对象
  31. optionalObjectOf: React.PropTypes.objectOf(React.PropTypes.number),
  32. // 特定形状参数的对象
  33. optionalObjectWithShape: React.PropTypes.shape({
  34. color: React.PropTypes.string,
  35. fontSize: React.PropTypes.number
  36. }),
  37. // 你可以在任意东西后面加上 `isRequired`
  38. // 来确保 如果 prop 没有提供 就会显示一个警告。
  39. requiredFunc: React.PropTypes.func.isRequired,
  40. // 不可空的任意类型
  41. requiredAny: React.PropTypes.any.isRequired,
  42. // 你可以自定义一个验证器。如果验证失败需要返回一个 Error 对象。
  43. // 不要直接使用 `console.warn` 或抛异常,
  44. // 因为这在 `oneOfType` 里不起作用。
  45. customProp: function(props, propName, componentName) {
  46. if (!/matchme/.test(props[propName])) {
  47. return new Error('Validation failed!');
  48. }
  49. }
  50. },
  51. /* ... */
  52. });

Single Child

React.PropTypes.element 你可以指定仅有一个子级能被传送给组件

  1. var MyComponent = React.createClass({
  2. propTypes: {
  3. children: React.PropTypes.element.isRequired
  4. },
  5. render: function() {
  6. return (
  7. <div>
  8. {this.props.children} // 这里必须是一个元素否则就会警告
  9. </div>
  10. );
  11. }
  12. });

默认 Prop 值

React 支持以声明式的方式来定义 props 的默认值。

  1. var ComponentWithDefaultProps = React.createClass({
  2. getDefaultProps: function() {
  3. return {
  4. value: 'default value'
  5. };
  6. }
  7. /* ... */
  8. });

当父级没有传入 props 时,getDefaultProps() 可以保证 this.props.value 有默认值,注意 getDefaultProps 的结果会被 缓存。得益于此,你可以直接使用 props,而不必写手动编写一些重复或无意义的代码。

传递 Props:捷径

有一些常用的 React 组件只是对 HTML 做简单扩展。通常,你想 复制任何传进你的组件的HTML属性 到底层的HTML元素上。为了减少输入,你可以用 JSX spread 语法来完成:

  1. var CheckLink = React.createClass({
  2. render: function() {
  3. // 这样会把 CheckList 所有的 props 复制到 <a>
  4. return <a {...this.props}>{'√ '}{this.props.children}</a>;
  5. }
  6. });
  7. ReactDOM.render(
  8. <CheckLink href="/checked.html">
  9. Click here!
  10. </CheckLink>,
  11. document.getElementById('example')
  12. );

Mixins

组件是 React 里复用代码最佳方式,但是有时一些复杂的组件间也需要共用一些功能。有时会被称为 跨切面关注点。React 使用 mixins 来解决这类问题。

一个通用的场景是:一个组件需要定期更新。用 setInterval() 做很容易,但当不需要它的时候取消定时器来节省内存是非常重要的。React 提供 生命周期方法 来告知组件创建或销毁的时间。下面来做一个简单的 mixin,使用 setInterval() 并保证在组件销毁时清理定时器。

  1. var SetIntervalMixin = {
  2. componentWillMount: function() {
  3. this.intervals = [];
  4. },
  5. setInterval: function() {
  6. this.intervals.push(setInterval.apply(null, arguments));
  7. },
  8. componentWillUnmount: function() {
  9. this.intervals.forEach(clearInterval);
  10. }
  11. };
  12. var TickTock = React.createClass({
  13. mixins: [SetIntervalMixin], // 引用 mixin
  14. getInitialState: function() {
  15. return {seconds: 0};
  16. },
  17. componentDidMount: function() {
  18. this.setInterval(this.tick, 1000); // 调用 mixin 的方法
  19. },
  20. tick: function() {
  21. this.setState({seconds: this.state.seconds + 1});
  22. },
  23. render: function() {
  24. return (
  25. <p>
  26. React has been running for {this.state.seconds} seconds.
  27. </p>
  28. );
  29. }
  30. });
  31. ReactDOM.render(
  32. <TickTock />,
  33. document.getElementById('example')
  34. );

关于 mixin 值得一提的优点是,如果一个组件使用了多个 mixin,并用有多个 mixin 定义了同样的生命周期方法(如:多个 mixin 都需要在组件销毁时做资源清理操作),所有这些生命周期方法都保证会被执行到。方法执行顺序是:首先按 mixin 引入顺序执行 mixin 里方法,最后执行组件内定义的方法。

ES6 Classes

你也可以以一个简单的JavaScript 类来定义你的React classes。使用ES6 class的例子:

  1. class HelloMessage extends React.Component {
  2. render() {
  3. return <div>Hello {this.props.name}</div>;
  4. }
  5. }
  6. ReactDOM.render(<HelloMessage name="Sebastian" />, mountNode);

API近似于 React.createClass 除了 getInitialState。 你应该在构造函数里设置你的state,而不是提供一个单独的 getInitialState 方法。

另一个不同是 propTypesdefaultProps 在构造函数而不是class body里被定义为属性。

  1. export class Counter extends React.Component {
  2. constructor(props) {
  3. super(props);
  4. this.state = {count: props.initialCount};
  5. }
  6. tick() {
  7. this.setState({count: this.state.count + 1});
  8. }
  9. render() {
  10. return (
  11. <div onClick={this.tick.bind(this)}>
  12. Clicks: {this.state.count}
  13. </div>
  14. );
  15. }
  16. }
  17. Counter.propTypes = { initialCount: React.PropTypes.number };
  18. Counter.defaultProps = { initialCount: 0 };

无自动绑定

方法遵循正式的ES6 class的语义,意味着它们不会自动绑定this到实例上。你必须显示的使用.bind(this) or 箭头函数 =>.

没有 Mixins

不幸的是ES6的发布没有任何mixin的支持。因此,当你在ES6 classes下使用React时不支持mixins。作为替代,我们正在努力使它更容易支持这些用例不依靠mixins。

无状态函数

你也可以用 JavaScript 函数来定义你的 React 类。例如使用无状态函数语法:

  1. function HelloMessage(props) {
  2. return <div>Hello {props.name}</div>;
  3. }
  4. ReactDOM.render(<HelloMessage name="Sebastian" />, mountNode);

或者使用新的ES6箭头函数:

  1. const HelloMessage = (props) => <div>Hello {props.name}</div>;
  2. ReactDOM.render(<HelloMessage name="Sebastian" />, mountNode);

这个简化的组件API旨在用于那些纯函数态的组件 。这些组件必须没有保持任何内部状态,没有备份实例,也没有组件生命周期方法。他们纯粹的函数式的转化他们的输入,没有引用。
然而,你仍然可以以设置为函数的properties的方式来指定 .propTypes.defaultProps,就像你在ES6类里设置他们那样。

注意:

因为无状态函数没有备份实例,你不能附加一个引用到一个无状态函数组件。 通常这不是问题,因为无状态函数不提供一个命令式的API。没有命令式的API,你就没有任何需要实例来做的事。然而,如果用户想查找无状态函数组件的DOM节点,他们必须把这个组件包装在一个有状态组件里(比如,ES6 类组件) 并且连接一个引用到有状态的包装组件。

在理想世界里,你的大多数组件都应该是无状态函数式的,因为这些无状态组件可以在React核心里经过一个快速的代码路径。 如果可能,这是推荐的模式。