希望能帶完全不會 React 的人快速掌握基礎概念,React 雖然博大精深,但核心概念是小而精美的。
React 是用來建置 UI 的 JavaScript Library,它不依賴在任何的平台之上 (但當然必須有 JavaScript Runtime),利用 react-dom、react-native 等等的 Library 來實現學一次就能寫任何平台的目標,帶給各位滿滿的大平台。
另外,它是基於 Component 在開發,在 jQuery 的時代大家還不太有這個概念,不過在現在包括 Angular2、Vue 的等等框架,這個概念已經不太陌生。提高可維護性,同時也是目前能提高前端程式 Reuse 能力的主要方式。
Declarative 也是一個重要的特性,定義好 State 與 UI 之間的關係,React 會在資料變動時自動幫你有效的更新 UI。如此一來,Debug 也變得比較簡單,因為我們只是試圖在做一個把 State (x)
映射到 HTML (y)
的 Function:
const HTML = App(State);
我們此篇的介紹,會以下筆此刻的 React 版本 v15.4.1
做為基礎,並以 Browser 環境為主軸來進行。
在看 React 的範例時,你可能會不時看到這種語法。為什麼能把 HTML 寫在 JavaScript 裡面呢?
function MyComponent() {
return <div />;
}
這是一種叫做 JSX 的語法,這個特殊的語法在 React 裡面只是 React.createElement
的 Syntactic sugar 而已:
function MyComponent() {
return React.createElement("div", null);
}
可以在這裡試試看編譯後的結果。
上面的例子很簡單,但複雜的 Component 更能看出 JSX 的優點:
function Article() {
return (
<div className="article">
<h1 className="article-title">Title</h1>
<p className="article-body">Body</p>
</div>
);
}
React.createElement
的第二個參數是 props
(後面會提到),第三個參數則是 children
,也就是 Tag 之間所夾的東西。
function Article() {
return React.createElement(
"div",
{ className: "article" },
React.createElement(
"h1",
{ className: "article-title" },
"Title"
),
React.createElement(
"p",
{ className: "article-body" },
"Body"
)
);
}
不使用 JSX 的版本複雜了許多,可以在這裡試試看編譯後的結果。
所以,如果
Article
是 Component 的話,
<Article />
就是實體化的 Component 我們稱之為 Element。
JSX 還可以用 {}
來傳遞 JavaScript Expression 作為 Props,或是在 Tag 中間嵌入 JavaScript Expression:
const styles = {
button: 'button_1ad5e',
};
const textOnButton = '按我';
<button
className={styles.button}
onClick={() => console.log('clicked!')}
>{textOnButton}</button>
在寫 React 時,我們的 App 是由一個個的 Component 組合而成,視覺上可以切分的單位,例如:Header
、Footer
、Menubar
等等,都是常見的 Component。
在 React 的 API 演進過程中,現在同時存在著三種不同樣貌的 Component,也就是下面會一一介紹的 ES6 Class Component
、Functional Component
以及 Classic Component
。
時下最常見的 Component 表示形式,直接使用 ES6 的 Class 語法來建構 Component,只要繼承自 React.Component
即可。
class App extends React.Component {
render() {
return <div />;
}
}
曾經難解的幾個問題也都看到到一些未來的方向了:
Mixin 的功能有了 Higher Order Component 這個解決辦法。
能替代 Class Method Auto Bind 的 Class Public Fields 也已經進到了 Stage 2。
在 v0.14.0
之後才支援的一種直接用 Function 來描述 Component 的用法:
const App = () => <div />;
除了在表示上更為簡潔以外,未來也有比較大的優化空間,包括在記憶體使用量上,想必能比 Class 減少不少。
是比較早就留存下來的 Component API:
const App = React.createClass({
render() {
return <div />;
},
});
在 ES6 Class 的問題一一解決後,重要性已經非常的低,根據核心團隊的說法,未來哪天很可能會從核心移出成為獨立的套件。
有趣的是,這個舊語法,居然偷偷地出現在《西方極樂園》(Westworld) 劇中的未來世界。
寫了一些 Component 後,什麼事都沒有發生?
沒錯,因為我們還需要一道手續,將它放到 Host Environment 中,這在每個環境會不一樣 (之後會介紹 React Native 的部分),這就如同我們一般寫程式常會有的 Main Function,是 App 的進入點:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(
<App />,
document.getElementById('root')
);
上面 Browser 環境的例子,把 <App />
放到一個 Root DOM Node 上面。
如同我們呼叫 Function 時可以傳遞一些參數的概念,我們建立 Element 時也可以傳遞 Props 作為參數:
<Post
className="post-recent"
title="Day 02:快速學會 React 基礎"
body="一天學會也太厲害..."
/>
這個縮排我當初適應了好久,但是是我覺得最清楚的方式了。
它也就是我們傳遞給 React.createElement
第二個參數,是個一般的 JavaScript Object:
{
className: 'post-recent',
title: 'Day 02:快速學會 React 基礎',
body: '一天學會也太厲害...',
}
這讓 Component 能依據 Props 去 Render 不同的 UI,試想我們有一個這樣的 Post Component:
class Post extends React.Component {
render() {
const { className, title, body } = this.props;
return (
<div className={`post ${className}`}>
<h2>{title}</h2>
<p>{body}</p>
</div>
);
}
}
那
<Post
className="post-recent"
title="Day 02:快速學會 React 基礎"
body="一天學會也太厲害..."
/>
就會 Render 出來
<div className="post post-recent">
<h2>Day 02:快速學會 React 基礎</h2>
<p>一天學會也太厲害...</p>
</div>
而
<Post
className="post-old"
title="Day 01:使用 Modern Web 技術來打造 Native App"
body="React 也太厲害..."
/>
則會 Render 出來
<div className="post post-old">
<h2>Day 01:使用 Modern Web 技術來打造 Native App</h2>
<p>React 也太厲害...</p>
</div>
Cool!
前面只有 Props 的 Component 我們稱之為 Stateless Component,它們只單純依靠在 Parent 所傳遞的參數。
而實體化的 Component 也可以擁有自己的狀態,也就是 State:
class Counter extends React.Component {
state = {
count: 0,
};
render() {
return <div>{this.state.count}</div>;
}
}
有了這個能有什麼幫助?我們必須介紹修改 State 的方式:setState
。
setState 是一個可以用來修改 State 的 Method,我們通常會在特定的 Event 發生時去做這個動作:
class Counter extends React.Component {
state = {
count: 0,
};
handleClick = () => {
this.setState({
count: this.state.count + 1,
});
};
render() {
return (
<div>
{this.state.count}
<button
onClick={this.handleClick}
>+1</button>
</div>
);
}
}
這樣我們就能做到在 button
點擊時把 count
加一了。而且如果我放了很多 <Counter />
,他們能各自有不同的 count
。
可以在這裡試試看。
但增加 State 並不一定是好事,無謂的 State 可能會造成 Component 的複雜度大幅提升,並容易出錯,所以我們必須非常謹慎地去設計出 App 所需的 State。
使用 React 之後我們不必自己煩惱 DOM 的操作,都交給 React 來負責,雖然如此,我們還是有責任了解更新的時機。必須知道,一旦 Props 或是 State 改變就會觸發 Reconciliation 的機制,藉由 React 的 Diffing 演算法來達成有效率的 DOM 更新。
想詳細了解可以參閱官方文件的 Reconciliation 章節。
我前面沒有說我們其實必須安裝 react
、react-dom
、babel
、babel-preset-latest
、babel-preset-react
、webpack
、babel-loader
等等,還有很多要裝的東西...。
在接下來的篇章我也會毫不留情地使用 ES2016、ES2017、以及 Stage 2 以上的功能 (尤其是 Object Rest Spread、Class Public Fields 或甚至 Async Functions)。
覺得這太複雜?沒辦法,在 2016 年寫 JavaScript,你必須趕快跟上才行。
如果對上面的那一些感到很不熟悉,我想 create-react-app 是你最佳的選擇,直接試試吧!