iT邦幫忙

2017 iT 邦幫忙鐵人賽
DAY 2
0

本篇目的

希望能帶完全不會 React 的人快速掌握基礎概念,React 雖然博大精深,但核心概念是小而精美的。

React 是什麼?

React 是用來建置 UI 的 JavaScript Library,它不依賴在任何的平台之上 (但當然必須有 JavaScript Runtime),利用 react-domreact-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 環境為主軸來進行。

JSX

在看 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>

Component

在寫 React 時,我們的 App 是由一個個的 Component 組合而成,視覺上可以切分的單位,例如:HeaderFooterMenubar 等等,都是常見的 Component。

在 React 的 API 演進過程中,現在同時存在著三種不同樣貌的 Component,也就是下面會一一介紹的 ES6 Class ComponentFunctional Component 以及 Classic Component

ES6 Class Component

時下最常見的 Component 表示形式,直接使用 ES6 的 Class 語法來建構 Component,只要繼承自 React.Component 即可。

class App extends React.Component {
  render() {
    return <div />;
  }
}

曾經難解的幾個問題也都看到到一些未來的方向了:

Functional Component

v0.14.0 之後才支援的一種直接用 Function 來描述 Component 的用法:

const App = () => <div />;

除了在表示上更為簡潔以外,未來也有比較大的優化空間,包括在記憶體使用量上,想必能比 Class 減少不少。

Classic Component

是比較早就留存下來的 Component API:

const App = React.createClass({
  render() {
    return <div />;
  },
});

在 ES6 Class 的問題一一解決後,重要性已經非常的低,根據核心團隊的說法,未來哪天很可能會從核心移出成為獨立的套件。

有趣的是,這個舊語法,居然偷偷地出現在《西方極樂園》(Westworld) 劇中的未來世界。

Mount

寫了一些 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 上面。

Props

如同我們呼叫 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!

State

前面只有 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

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。

Reconciliation

使用 React 之後我們不必自己煩惱 DOM 的操作,都交給 React 來負責,雖然如此,我們還是有責任了解更新的時機。必須知道,一旦 Props 或是 State 改變就會觸發 Reconciliation 的機制,藉由 React 的 Diffing 演算法來達成有效率的 DOM 更新。

想詳細了解可以參閱官方文件的 Reconciliation 章節

結語

我前面沒有說我們其實必須安裝 reactreact-dombabelbabel-preset-latestbabel-preset-reactwebpackbabel-loader 等等,還有很多要裝的東西...。

在接下來的篇章我也會毫不留情地使用 ES2016、ES2017、以及 Stage 2 以上的功能 (尤其是 Object Rest Spread、Class Public Fields 或甚至 Async Functions)。

覺得這太複雜?沒辦法,在 2016 年寫 JavaScript,你必須趕快跟上才行。

如果對上面的那一些感到很不熟悉,我想 create-react-app 是你最佳的選擇,直接試試吧!


上一篇
Day 01:使用 Modern Web 技術來打造 Native App
下一篇
Day 03:React Ecosystem
系列文
使用 Modern Web 技術來打造 Native App30

尚未有邦友留言

立即登入留言