這篇就會開始打 code 囉。
不過首先先補充一些重要的概念。
目錄
- ES5 VS ES6- class- React.createClass VS extends React.Component
- Props & State
- bind
 
- 模組規範的整合- require VS import
- module.exports VS export default
- export & import{}
 
- Arrow functions
- Template Strings
- …
- 參數預設值
 
- Component- 設計 Component
- 使用 Component
- Component 疊疊樂
- 父子 Component 間資訊的傳遞- props.children
- props 傳遞技巧
- props 傳遞地獄
 
 
- refs
ES5 VS ES6
class
有接觸過物件導向程式語言的對 class 應該不陌生吧?JS 雖然是以函數導向為主,但在 ES6 中新增了 class 的語法糖,繼承和建構子之類的 class 基礎語法都可以使用,詳細使用方法和功能可以看這篇。
React.createClass VS extends React.Component
React 中可以利用繼承 React.Component 來建立 Component:
ES5
| 1 2 3 4 5 6 7
 | var Demo = React.createClass({   a: function() {...},   b: function() {...},   render: fucntion() {   	return null;   } });
 | 
ES6
| 1 2 3 4 5 6 7
 | class Demo extends React.Component{   a() {...}   b() {...}   render() {   	return null;   } }
 | 
React.createClass 是傳入一個物件,其屬性值可以是函數或值,換成 class 就可以直接宣告函數或變數,看起來比較直觀。
另外,ES6 可以把 React component 生命周期之一 componentWillMount 內的程式碼放在建構子 (constructor) 中。
Props & State
ES5
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
 | var Demo = React.createClass({   getDefaultProps: function() {     return {       c8763: 87,     };   },   propTypes: {     c8763: React.PropTypes.number.isRequired,   },   getInitialState: function() {     return {       a: this.props.c8763,     };   },   render: fucntion() {     return null;   } });
 | 
ES6
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14
 | class Demo extends React.Component{   static defaultProps = {     c8763: 87,   };   static propTypes = {     c8763: React.PropTypes.number.isRequired,   };   state = {     a: this.props.c8763,   }   render() {   	return null;   } }
 | 
上面的範例顯示出 defaultProps 和 propTypes 設定上的差別,還有 state 初始化的異同。
另外也可以在建構子內初始化 state,如果要做一些運算的話這樣比較方便:
| 1 2 3 4 5 6 7 8
 | class Demo extends React.Component{   constructor(props){     super(props);     this.state = {       a: this.props.c8763,     };   } }
 | 
bind
ES5
| 1 2 3 4 5 6 7 8 9 10 11 12
 | var Demo = React.createClass({   c8763: function() {     this.setState({c8763: 87});   },   render: function(){     return (       <div>         <Button onClick={this.c8763}>c8763</Button>       </div>     )   }, });
 | 
ES6
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14
 | class Demo extends React.Component {   c8763() {     this.setState({c8763: 87});   }   render(){     return (       <div> 	    {// 二選一}         <Button onClick={this.c8763.bind(this)}>c8763</Button>         <Button onClick={function() {this.c8763()}}>c8763</Button>       </div>     )   } });
 | 
React.createClass 預設會自動幫你 bind this 到 method,所以在 method 中使用的 this 都是指到該 Component (Demo) 的 instance,也就是 Virtural DOM 中的 tag。
但如果用 class extends React.Component 的話,不會自動 bind this 到 method,上面的例子如果直接在 button 的 onClick 呼叫 this.c8763() 的話,那麼 c8763() 內 this.setState({c8763: 87}); 的 this 是指到 button 而非 Demo,所以我們要手動 bind 或是用匿名函數包住。
如果有多個 tag 用到這個 function 的話,可以在建構子中直接寫 this.c8763 = this.c8763.bind(this);,之後就可以直接調用 this.c8763() 了。
模組規範的整合
ES6 兼容 CommonJS 和 AMD 這兩種著名的模組規範。
require VS import
ES5
| 1
 | var React = require('react');
 | 
ES6
| 1
 | import React from 'react';
 | 
require 是 CommonJS 引入模組的寫法,ES6 則走 Python 風,使用關鍵字 import,後面分號還可以省略,夠 Python 吧?
ES5
ES6
直接引入一個文件,像是 css。
module.exports VS export default
ES5
| 1 2
 | var Demo = React.createClass({ ... }); module.exports = Demo;
 | 
ES6
| 1
 | export default class Demo extends React.Component{ ... }
 | 
module.exports 是 CommonJS 模組輸出的寫法。與 import 相對,ES6 使用的是 export。default 則代表輸出只有它。
兩者後面可以接變數,函數,物件或 class。
export & import{}
ES5
| 1 2 3 4 5 6 7 8 9
 | const a = 'A'; const b = 'B'; const c = 'C'; module.exports = {a, b, c}; var abc = require('./abc.jsx'); var a = abc.a; var c = abc.c;
 | 
ES6
| 1 2 3 4 5 6
 | export const a = 'A'; export const b = 'B'; export const c = 'C'; import {a, c} from './abc.jsx'
 | 
export 後面不加 default 的話代表輸出可能不只有它,會自動將所有 export 包成物件。
import 後面加大括號可以直接取出 module 的物件屬性,不過限制是存放的變數和物件屬性必須同名。
引入官方模組時也很好用,例如引入 react.Component:
ES5
| 1 2
 | var React = require('react'); var createClass = React.createClass;
 | 
ES6
| 1
 | import {Component} from 'react';
 | 
Arrow functions
ES5
ES6
可以把匿名函數的宣告精簡化,那個箭頭潮到出水。
Template Strings
ES5
| 1 2
 | var c = 'c'; var c8763 = c + '8763';
 | 
ES6
| 1 2
 | var c = 'c'; var c8763 = `${c}8763`;
 | 
…
呃,先說好,我沒偷懶,這真的是語法,可能是開發者很無言想不到其他符號吧。
ES5
| 1 2
 | var a = {c8763: 87, xxx: 'xx'} var b = {c8763: 87, xxx: 'xx', www: 'XD'}
 | 
ES6
| 1 2
 | var a = {c8763: 87, xxx: 'xx'} var b = {...a, www: 'XD'}
 | 
迭代展開物件的屬性及其值。很常被用來設定 tag 的屬性和重新產生 state。
參數預設值
不傳參數時自動用預設值,很多語言都有這設計,ES6 終於也設計了這功能,有了這個就更加方便了。
ES5
| 1 2 3 4
 | function(c8763){   c8763 = c8763 || 87;   ... }
 | 
ES6
| 1 2 3
 | function(c8763 = 87){   ... }
 | 
ES5 和 ES6 對比詳細可以看這裡。
Component
設計 Component
還記得上面有提到 ES6 可以使用 class 吧?React.Component 就是個 class,要建立 Component 第一步就是要仁繼承 (extends) 它。
首先,我們來看個範例:(JSX + ES6)
| 1 2 3 4 5 6 7 8 9 10
 | import React from 'react' export default class Demo extends React.Component {   constructor(props) {     super(props);   }   render() {     return null;   } }
 | 
React.Component 已經有 constructor 了,如果要覆寫的話要記得在最前面加上 super(props); 喔~
render() 之前在說 Component 生命周期時有提到,必須覆寫,並回傳 JSX tag /null/false
還有別忘記最前面要引入 react 模組 (require 或 import 皆可)
使用 Component
上面我們知道設計 Component 的方法了,那要怎麼使用它呢?
| 1 2 3 4
 | import ReactDOM from 'react-dom' import Demo from './Demo.jsx' ReactDOM.render(<Demo/>, document.getElementById('root'));
 | 
這樣就會把 Component Demo 置於 id 為 root 的 tag 中。
接下來就是用 babel 把 main.jsx 編譯成 js。
如果有安裝 babel-cli 的讀者可以用以下指令:
babel main.jsx -o compiled.js
這樣只要引入 compiled.js 就行了。
EX:
| 1 2 3 4 5 6 7 8 9 10
 | <!doctype html> <html>   <head>     <title>C8763</title>   </head>   <body>     <div id="root"></div>     <script src="compiled.js"></script>   </body> </html>
 | 
記得加 <div id="root"></div> 喔,不然 main.jsx 中的 document.getElementById('root') 會抓不到 tag。
Component 疊疊樂
當然 Component 裡面也可以包 Component。
新增 CustomButton.jsx:
| 1 2 3 4 5 6 7 8 9 10
 | import React from 'react' export default class CustomButton extends React.Component {   constructor(props) {     super(props);   }   render() {     return <button> C8763 </button>;   } }
 | 
修改 Demo.jsx:
| 1 2 3 4 5 6 7 8 9 10 11
 | import React from 'react' import CustomButton from './CustomButton.jsx' export default class Demo extends React.Component {   constructor(props) {     super(props);   }   render() {     return <CustomButton />;   } }
 | 
引入後直接將其當 tag 使用即可。
通常 Demo 對於 CustomButton 來說是父 Component,反之則是子 Component。
父子 Component 間資訊的傳遞
上一篇有提到 props,這裡直接仁來複習一下。
props 是 React 父子元件間溝通的橋樑。靜態(唯讀)。
父元件用屬性賦值的方式傳給子元件,子元件用 this.props 讀取。但不應於子元件內變動 (唯讀)。
父元件傳入的 props 改變將造成子元件重繪。
EX:
父元件內
| 1 2 3
 | ... <Fuck fuck="?"/> ...
 | 
子元件Fuck內
| 1 2 3
 | ... <a>{this.props.fuck}</a> ...
 | 
因為父元件傳入?,所以這裡會顯示?
– 仁自前一篇
同樣地,我們可以:
| 1 2 3 4 5 6 7 8 9 10 11
 | import React from 'react' import CustomButton from './CustomButton.jsx' export default class Demo extends React.Component {   constructor(props) {     super(props);   }   render() {     return <CustomButton text='C8763'/>;   } }
 | 
| 1 2 3 4 5 6 7 8 9 10
 | import React from 'react' export default class CustomButton extends React.Component {   constructor(props) {     super(props);   }   render() {     return <button> {this.props.text} </button>;   } }
 | 
props.children
之前我們都是用單個閉合標籤表示自定義 Component (像是<CustomButton />),然後再一層一層向內設計。但有時候我們想在父 Component 中決定子 Component 的內部結構。
這時,我們可能會這樣設計:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
 | import React from 'react' import CustomButton from './CustomButton.jsx' export default class Demo extends React.Component {   constructor(props) {     super(props);   }   render() {     return <CustomButton content={<button> {...} </button>}/>;   } } // CustomButton.jsx import React from 'react' export default class CustomButton extends React.Component {   constructor(props) {     super(props);   }   render() {     return {this.props.content};   } }
 | 
利用 props 把子 Component 的內部結構傳進去,但這樣程式碼很醜,身為肥宅工程師,本身就已經慘不忍睹了,怎麼還能讓程式碼跟我們一樣呢?
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
 | import React from 'react' import CustomButton from './CustomButton.jsx' export default class Demo extends React.Component {   constructor(props) {     super(props);   }   render() {     return ( 	  <CustomButton> 	    <button> {...} </button>       </CustomButton> 	);   } } import React from 'react' export default class CustomButton extends React.Component {   constructor(props) {     super(props);   }   render() {     return {this.props.children};   } }
 | 
這樣一來,程式碼就易讀很多了~
props 傳遞技巧
資料只能在父子間傳遞,而且一般來說是父傳子,所以如果有兩個子元件想要溝通,資料通常會在父元件取得,再往下傳,由一個子元件的 callback function 觸發父元件重繪,改變另一個子元件的 props。
比如說想設計一個 Button,按下後會新增一個 Item,那我們可能會這樣設計:
- 按下 Button 觸發 callback function
- 觸發父 Component setState
- 父 Component 重繪
- 改變 Item props
- Item 重繪
props 傳遞地獄
當 Component 愈疊愈多層,想要把資料從最頂層的元件傳到最底層的元件就會愈麻煩,就是所謂的 props 傳遞地獄。
解決方法是使用後端框架 (EX: Redux) 將 state 集中起來,以便管理。
Redux 在第四篇會介紹。
refs
雖說不建議直接操作實體 DOM,但有時候仍會碰到需要直接操作的情況。
這時可以設定ref屬性來存取實體 DOM:
若 ref 的值為字串,則可以透過this.refs.字串值 存取。
| 1 2 3
 | React.findDOMNode(this.refs.theInput).focus(); ... (render 內) <input ref="theInput">
 | 
若 ref 的值為函數,則傳入的參數值為該元件本身。
| 1 2 3
 | this.input.focus(); ... (render 內) <input ref={(input) => this.input = input}>
 | 
由於 React.findDOMNode() 傳回的是實體 DOM,所以必須確保實體 DOM 已經被渲染出來才能使用 (componentDidMount() 後面的才能用)
先講到這,下篇 講的是開發工具 webpack 的環境建置及使用。如果有興趣可以繼續收看~
這篇會不定期更新,如果有打錯或有任何疑問都歡迎留言告知喔~
我們下篇再見~