Static Properties in ES Class

问题

ES6 的 class 支持 static property 吗?

static property, 即类(class,在 JS 中即构造函数 constructor)上的静态属性,代码示例如下:

class MyClass {
  static myStaticProp = 42;

  constructor() {
    console.log(MyClass.myStaticProp); // Prints '42'
  }
}

背景

ES6,更合适的说法是 ES2015,所支持的 class 语法如下(来自 babel - Learn ES2015):

class SkinnedMesh extends THREE.Mesh {
  constructor(geometry, materials) {
    super(geometry, materials);

    this.idMatrix = SkinnedMesh.defaultMatrix();
    this.bones = [];
    this.boneMatrices = [];
    //...
  }
  update(camera) {
    //...
    super.update();
  }
  static defaultMatrix() {
    return new THREE.Matrix4();
  }
}

其中有 instance property(通过在 constructor 中赋值), instance method 以及 static method 的示例,但唯独没有 static property 的示例。而到底这一特性是否支持,不同来源的说法不太一致。

理论上,使用已知的 ES2015 语法,可以通过对 class 定义 getter 实现 static property,写法如下:

class MyClass {
  get myStaticProp() {
    return 42
  }
}

但是这么写很傻,我们只是需要一个简单的 property 而已。

这个问题之所以重要,就要说到 react。react 的 component 定义支持 ES5 及 ES6 两种方式:

// The ES5 way
var HelloMessage = React.createClass({
  render: function () {
    return <div>Hello {this.props.name}</div>;
  }
})

// the ES6 way
class HelloMessage extends React.Component {
  render() {
    return <div>Hello {this.props.name}</div>;
  }
}

但是在 ES6 的写法下,initialStatedefaultProps 以及 propTypes 的声明与 ES5 写法下有较大区别,官方文档见此,ES5 的版本是:

// The ES5 way
var Counter = React.createClass({
  getDefaultProps: function() {
    return {
      initialCount: 0
    };
  },
  getInitialState: function() {
    return {
      count: this.props.initialCount
    };
  },
  propTypes: {
    initialCount: React.PropTypes.number
  }
});

在 ES6 写法下,initialState 要求通过在初始化的时候给 this.state 赋值实现,而 defaultPropspropTypes 需要通过在构造函数上定义同名属性(Counter.defaultPropsCounter.propTypes)实现,具体是这样的:

export class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = {count: props.initialCount};
  }
}
Counter.propTypes = { initialCount: React.PropTypes.number };
Counter.defaultProps = { initialCount: 0 };

类的实现代码分割成了两块,很丑陋,这也正是我一度不愿意使用 ES6 的写法的原因。可是,如果 ES6 的 class 支持 static property 的定义的话,我们就可以写成这样:

export class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = {count: props.initialCount};
  }
  static propTypes = {
    initialCount: React.PropTypes.number
  };
  static defaultProps = {
    initialCount: 0
  };
}

看起来会好很多。

答案

static property 并不在 ES2015 范围以内。目前是 stage-1 的提案。将来如果会通过,大概也是 ES7(ES2016),而不是 ES2015 的一部分了。这个提案同时也包含了 instance property (实例属性,在提案中被称为 instance fields) 的部分,如下的写法会在 ClassWithInits 的构造函数执行的时候给实例添加属性 myProp,值为 42:

class ClassWithInits {
  myProp = 42;
}

这个提案对应的 babel plugin 是 transform-class-properties,可以通过在 babel 中配置这个 plugin 或者配置 presets 中添加 stage-1(包含了 plugin transform-class-properties)以支持这一特性。

最后,得到的经验是,得益于 babel 的语法转换功能,在新的开发模式下,在考察语法特性时,不需要考虑是否某一规范(ES6、ES7,...)的组成部分,对应规范是否已正式发布,各大浏览器是否支持,只需要考虑是否可以通过对应的转换插件支持,在自己的开发中使用即可。代码要转换,思维方式更要转换。

参考

  • https://github.com/jeffmo/es-class-fields-and-static-properties
  • http://www.2ality.com/2015/02/es6-classes-final.html
  • https://docs.google.com/document/d/1QbEE0BsO4lvl7NFTn5WXWeiEIBfaVUF7Dk0hpPpPDzU/edit#heading=h.kfc1yc110kdz
  • http://babeljs.io/docs/learn-es2015/
  • http://babeljs.io/docs/plugins/preset-stage-1/
  • http://babeljs.io/docs/plugins/transform-class-properties/
  • http://babeljs.io/blog/2015/06/07/react-on-es6-plus/
  • https://facebook.github.io/react/docs/reusable-components.html
  • http://react-china.org/t/es6-react-createclass/3441/3
  • http://bbs.reactnative.cn/topic/15/react-react-native-%E7%9A%84es5-es6%E5%86%99%E6%B3%95%E5%AF%B9%E7%85%A7%E8%A1%A8