這篇就會開始打 code 囉。
不過首先先補充一些重要的概念。


目錄

  1. ES5 VS ES6
    1. class
      1. React.createClass VS extends React.Component
      2. Props & State
      3. bind
    2. 模組規範的整合
      1. require VS import
      2. module.exports VS export default
      3. export & import{}
    3. Arrow functions
    4. Template Strings
    5. 參數預設值
  2. Component
    1. 設計 Component
    2. 使用 Component
    3. Component 疊疊樂
    4. 父子 Component 間資訊的傳遞
      1. props.children
      2. props 傳遞技巧
      3. props 傳遞地獄
  3. 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

1
require('c8763.css');

ES6

1
import 'c8763.css';

直接引入一個文件,像是 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
// abc.jsx
const a = 'A';
const b = 'B';
const c = 'C';
module.exports = {a, b, c};
// Demo.jsx
var abc = require('./abc.jsx');
var a = abc.a;
var c = abc.c;

ES6

1
2
3
4
5
6
// abc.jsx
export const a = 'A';
export const b = 'B';
export const c = 'C';
// Demo.jsx
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

1
2
3
function(a){
...
}

ES6

1
2
3
a => {
...
}

可以把匿名函數的宣告精簡化,那個箭頭潮到出水。

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
// Demo.jsx
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
// main.jsx
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
// CustomButton.jsx
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
// Demo.jsx
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
// Demo.jsx
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
// CustomButton.jsx
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
// Demo.jsx
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
// Demo.jsx
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>
);
}
}
// CustomButton.jsx
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,那我們可能會這樣設計:

  1. 按下 Button 觸發 callback function
  2. 觸發父 Component setState
  3. 父 Component 重繪
  4. 改變 Item props
  5. 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 的環境建置及使用。如果有興趣可以繼續收看~

這篇會不定期更新,如果有打錯或有任何疑問都歡迎留言告知喔~
我們下篇再見~