附錄一、React ES5、ES6+ 常見用法對照表

    是 Facebook 推出的開源 JavaScript Library。自從 React 正式開源後,React 生態系開始蓬勃發展。事實上,透過學習 React 生態系(ecosystem)的過程中,可以讓我們順便學習現代化 Web 開發的重要觀念(例如:ES6、、Babel、模組化等),成為更好的開發者。雖然 ES6(ECMAScript2015)、ES7 是未來趨勢(本文將 ES6、ES7 稱為 ES6+),然而目前在網路上有許多的學習資源仍是以 ES5 為主,導致讀者在學習上遇到一些坑洞和迷惑(本文假設讀者對於 React 已經有些基本認識,若你對於 React 尚不熟悉,建議先行和本篇入門教學)。因此本文希望透過整理在 React 中 ES5、ES6+ 常見用法對照表,讓讀者們可以在實現功能時(尤其在 )可以更清楚兩者的差異,無痛轉移到 ES6+。

    大綱

    1. Modules
    2. Classes
    3. Method definition
    4. Property initializers
    5. State
    6. Arrow functions
    7. Dynamic property names & template strings
    8. Destructuring & spread attributes
    9. Mixins
    10. Default Parameters

    1. Modules

    隨著 Web 技術的進展,模組化開發已經成為一個重要課題。關於 JavaScript 模組化我們這邊不詳述,建議讀者參考 這份投影片 和 。

    ES5 若使用 CommonJS 標準,一般使用 用法引入模組:

    輸出則是使用 module.exports

    1. module.exports = MyComponent;

    ES6+ import 用法:

    1. import React from 'react';
    2. import MyComponent from './MyComponent';

    輸出則是使用 export default

    1. export default class MyComponent extends React.Component {
    2. }

    2. Classes

    在 React 中元件(Component)是組成視覺頁面的基礎。在 ES5 中我們使用 React.createClass() 來建立 Component,而在 ES6+ 則是用 Classes 繼承 React.Component 來建立 Component。若是有寫過 Java 等物件導向語言(OOP)的讀者應該對於這種寫法比較不陌生,不過要注意的是 JavaScript 仍是原型繼承類型的物件導向程式語言,只是使用 Classes 讓物件導向使用上更加直觀。對於選擇 class 使用上還有疑惑的讀者建議可以閱讀 這篇文章。

    ES5 React.createClass() 用法:

    1. var Photo = React.createClass({
    2. render: function() {
    3. return (
    4. <div>
    5. <images alt={this.props.description} src={this.props.src} />
    6. </div>
    7. );
    8. }
    9. });
    10. ReactDOM.render(<Photo />, document.getElementById('main'));

    ES6+ class 用法:

    1. class Photo extends React.Component {
    2. render() {
    3. return <images alt={this.props.description} src={this.props.src} />;
    4. }
    5. }
    6. ReactDOM.render(<Photo />, document.getElementById('main'));

    在 ES5 我們會在 componentWillMount 生命週期定義希望在 render 前執行,且只會執行一次的任務:

    1. var Photo = React.createClass({
    2. componentWillMount: function() {}
    3. });

    在 ES6+ 則是定義在 constructor 建構子中:

    1. class Photo extends React.Component {
    2. constructor(props) {
    3. super(props);
    4. // 原本在 componentWillMount 操作的動作可以放在這
    5. }
    6. }

    在 ES6 中我們使用 Method 可以忽略 function,,使用上更為簡潔!ES5 React.createClass() 用法:

    1. var Photo = React.createClass({
    2. handleClick: function(e) {},
    3. render: function() {}
    4. });

    ES6+ class 用法:

    1. class Photo extends React.Component {
    2. handleClick(e) {}
    3. render() {}
    4. }

    4. Property initializers

    1. var Todo = React.createClass({
    2. getDefaultProps: function() {
    3. return {
    4. checked: false,
    5. maxLength: 10,
    6. };
    7. },
    8. propTypes: {
    9. checked: React.PropTypes.bool.isRequired,
    10. maxLength: React.PropTypes.number.isRequired
    11. },
    12. render: function() {
    13. return();
    14. }
    15. });

    在 ES6+ 中我們則是參考 ES7 property initializers 使用 class 中的靜態屬性(static properties)來定義:

    ES6+ 另外一種寫法,可以留意一下,主要是看各團隊喜好和規範,選擇合適的方式:

    1. class Todo extends React.Component {
    2. render() {
    3. return (
    4. <View />
    5. );
    6. }
    7. }
    8. Todo.defaultProps = {
    9. checked: false,
    10. };
    11. Todo.propTypes = {
    12. checked: React.PropTypes.bool.isRequired,
    13. maxLength: React.PropTypes.number.isRequired,
    14. };

    5. State

    在 React 中 PropsState 是資料流傳遞的重要元素,不同的是 state 可更動,可以去執行一些運算。在 ES5 中我們使用 getInitialState 去初始化 state

    1. var Todo = React.createClass({
    2. getInitialState: function() {
    3. maxLength: this.props.maxLength,
    4. };
    5. },
    6. });

    在 ES6+ 中我們初始化 state 有兩種寫法:

    1. class Todo extends React.Component {
    2. state = {
    3. maxLength: this.props.maxLength,
    4. }
    5. }

    另外一種寫法,使用在建構式初始化。比較推薦使用這種方式,方便做一些運算:

    1. class Todo extends React.Component {
    2. constructor(props){
    3. super(props);
    4. this.state = {
    5. maxLength: this.props.maxLength,
    6. };
    7. }
    8. }

    6. Arrow functions

    在講 Arrow functions 之前,我們先聊聊在 React 中 this 和它所代表的 context。在 ES5 中,我們使用 React.createClass() 來建立 Component,而在 React.createClass() 下,預設幫你綁定好 methodthis,你毋須自行綁定。所以你可以看到像是下面的例子,callback function handleButtonClick 中的 this 是指到 component 的實例(instance),而非觸發事件的物件:

    1. var TodoBtn = React.createClass({
    2. handleButtonClick: function(e) {
    3. // 此 this 指到 component 的實例(instance),而非 button
    4. this.setState({showOptionsModal: true});
    5. },
    6. render: function(){
    7. return (
    8. <div>
    9. <Button onClick={this.handleButtonClick}>{this.props.label}</Button>
    10. </div>
    11. )
    12. },
    13. });

    然而自動綁定這種方式反而會讓人容易誤解,所以在 ES6+ 推薦使用 bind 綁定 this 或使用 Arrow functions(它會绑定當前 scopethis context)兩種方式,你可以參考下面例子:

    1. class TodoBtn extends React.Component
    2. {
    3. handleButtonClick(e){
    4. // 確認綁定 this 指到 component instance
    5. this.setState({toggle: true});
    6. }
    7. render(){
    8. // 這邊可以用 this.handleButtonClick.bind(this) 手動綁定或是 Arrow functions () => {} 用法
    9. return (
    10. <div>
    11. <Button onClick={this.handleButtonClick.bind(this)} onClick={(e)=> {this.handleButtonClick(e)} }>{this.props.label}</Button>
    12. </div>
    13. )
    14. },
    15. }

    Arrow functions 雖然一開始看起來有點怪異,但其實觀念很簡單:一個簡化的函數。函數基本上就是參數(不一定要有參數)、表達式、回傳值(也可能是回傳 undefined):

    1. // Arrow functions 的一些例子
    2. ()=>7
    3. e=>e+2
    4. ()=>{
    5. alert('XD');
    6. }
    7. (a,b)=>a+b
    8. e=>{
    9. if (e == 2){
    10. return 2;
    11. }
    12. return 100/e;
    13. }

    不過要注意的是無論是 bind 或是 Arrow functions,每次執行回傳都是指到一個新的函數,若需要再調用到這個函數,請記得先把它存起來:

    錯誤用法:

    1. class TodoBtn extends React.Component{
    2. componentWillMount(){
    3. Btn.addEventListener('click', this.handleButtonClick.bind(this));
    4. }
    5. componentDidmount(){
    6. Btn.removeEventListener('click', this.handleButtonClick.bind(this));
    7. }
    8. onAppPaused(event){
    9. }
    10. }

    正確用法:

    1. class TodoBtn extends React.Component{
    2. constructor(props){
    3. super(props);
    4. this.handleButtonClick = this.handleButtonClick.bind(this);
    5. }
    6. componentWillMount(){
    7. Btn.addEventListener('click', this.handleButtonClick);
    8. }
    9. componentDidMount(){
    10. }

    更多 Arrows and Lexical This 特性可以。

    以前在 ES5 我們要動態設定屬性名稱時,往往需要多寫幾行程式碼才能達到目標:

    1. var Todo = React.createClass({
    2. onChange: function(inputName, e) {
    3. var stateToSet = {};
    4. stateToSet[inputName + 'Value'] = e.target.value;
    5. this.setState(stateToSet);
    6. },
    7. });

    Template Strings 是一種語法糖(syntactic sugar),方便我們組織字串(這邊也用上 letconst 變數和常數宣告的方式,和 varfunction scope 不同的是它們是屬於 block scope,亦即生存域存在於 {} 間):

    1. // Interpolate variable bindings
    2. const name = "Bob", let = "today";
    3. `Hello ${name}, how are you ${time}?` \\ Hello Bob, how are you today?

    8. Destructuring & spread attributes

    在 React 的 Component 中,父元件利用 props 來傳遞資料到子元件是常見作法,然而我們有時會希望只傳遞部分資料,此時 ES6+ 中的 Destructuring 和 ,... Spread Attributes 主要是用來迭代物件:

    1. class Todo extends React.Component {
    2. render() {
    3. var {
    4. className,
    5. ...others, // ...others 包含 this.props 除了 className 外所有值。this.props = {value: 'true', title: 'header', className: 'content'}
    6. } = this.props;
    7. return (
    8. <div className={className}>
    9. <TodoList {...others} />
    10. <button onClick={this.handleLoadMoreClick}>Load more</button>
    11. </div>
    12. );
    13. }
    14. }

    但使用上要注意的是若是有重複的屬性值則以後來覆蓋,下面的例子中若 ...this.props,有 className,則被後來的 main 所覆蓋:

    1. <div {...this.props} className="main">
    2. </div>

    Destructuring 也可以用在簡化 Module 的引入上,這邊我們先用 ES5 中引入方式來看:

    1. var React = require('react-native');
    2. var Component = React.component;
    3. class HelloWorld extends Component {
    4. render() {
    5. return (
    6. <View>
    7. <Text>Hello, world!</Text>
    8. </View>
    9. );
    10. }
    11. }
    12. export default HelloWorld;

    以下 ES5 寫法:

    1. var React = require('react-native');
    2. var View = React.View;

    在 ES6+ 則可以直接使用 Destructuring 這種簡化方式來引入模組中的元件:

    1. // 這邊等於上面的寫法
    2. var { View } = require('react-native');

    更進一步可以使用 import 語法:

    1. import React, {
    2. View,
    3. Component,
    4. Text,
    5. } from 'react-native';
    6. class HelloWorld extends Component {
    7. render() {
    8. return (
    9. <View>
    10. <Text>Hello, world!</Text>
    11. </View>
    12. );
    13. }
    14. }
    15. export default HelloWorld;

    9. Mixins

    在 ES5 中,我們可以使用 Mixins 的方式去讓不同的 Component 共用相似的功能,重用我們的程式碼:

    1. var PureRenderMixin = require('react-addons-pure-render-mixin');
    2. React.createClass({
    3. mixins: [PureRenderMixin],
    4. render: function() {
    5. return <div className={this.props.className}>foo</div>;
    6. }
    7. });

    但由於官方不打算在 ES6+ 中繼續推行 Mixins,若還是希望使用,可以參考看看第三方套件或是。

    10. Default Parameters

    以前 ES5 我們函數要使用預設值需要這樣使用:

    1. var link = function (height, color) {
    2. var height = height || 50;
    3. var color = color || 'red';
    4. }

    現在 ES6+ 的函數可以支援預設值,讓程式碼更為簡潔:

    1. var link = function(height = 50, color = 'red') {
    2. ...

    以上就是 React ES5、ES6+常見用法對照表,能看到這邊的你應該已經對於 React ES5、ES6 使用上有些認識,先給自己一些掌聲吧!確實從 ES6 開始,JavaScript 和以前我們看到的 JavaScript 有些不同,增加了許多新的特性,有些讀者甚至會很懷疑說這真的是 JavaScript 嗎?ES6 的用法對於初學者來說可能會需要寫一點時間吸收,下一章我們將進到同樣也是有革新性設計和有趣的 React Native,用 JavaScript 和 React 寫 Native App!

    延伸閱讀

    1. React/React Native 的ES5 ES6写法对照表
    2. react native 中es6语法解析
    3. ECMAScript 6入门
    4. React INTRO TO REACT.JS
    5. react-native-coding-style

    | 勘誤、提問或許願 |