這篇就會開始打 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 的環境建置及使用。如果有興趣可以繼續收看~
這篇會不定期更新,如果有打錯或有任何疑問都歡迎留言告知喔~
我們下篇再見~