React/Redux 一個月的開發感想

  1. 1. 先講結論
  2. 2. 簡介
  3. 3. 入門
    1. 3.0.1. components
    2. 3.0.2. containers
  • 4. 入門之後?
  • 5. Redux
    1. 5.0.1. 觀念方面
    2. 5.0.2. 操作方面
    3. 5.0.3. middlewares
  • 6. 共通的問題
    1. 6.0.1. 重複的工作太多
  • 7. 學習與開發重點
    1. 7.0.1. ES6
    2. 7.0.2. React
    3. 7.0.3. Redux
  • 8. 後記
  • 9. 延伸閱讀
  • 10. 線上課程
  • 11. 首圖來源
  • banner

    之前自學 React 時寫了一個簡單的線上簡歷,而我也確實靠他加分而拿到了工作,之後進去也是開始用 React 建立頁面,之後加上了 Redux;雖然這兩個總天數加起來大概就一個月,不敢說自己有摸清這些東西,不過還是就開發經驗來寫一些感想。

    先講結論

    React 其實不討厭,還算是喜歡的;但若是要我向別人推荐的話,我會建議學 Vue.js 入門,熟了再考慮要不要碰 React。若是不排斥用其他語言轉換成 js 的話,Elm 看起來是個不錯的選擇,但 ES6 開始普及之後我對這方面的興趣不大,所以這個建議比較不負責任一點。

    摸新東西當然都會面臨挫折、觀念轉變以及學習曲線的問題,但事實上,不把前後分離這件事考慮進去的話(畢竟這是前端 framework 最主要的目的),失去的不會比獲得的感受來的少多少,頂多接近打平,剩下的端看你的 M 體質點多高來去彌補。說實在的,當初把玩 Rust 語言被摧殘到躺地板時我覺得還比較爽一點。

    簡介

    React 是以函數式編程為原則來做開發,函數式編程幾個簡單的重點如:

    • 數值不可變:在一些比較嚴格規定的語言如 Haskell 的每個變數都是強制不可變的,而大部分的語言都是折衷分為可變與不可變。
    • 一個函數只做一件事:這表示函數要單純,所以要把處理的事情分的很清楚,對不是從函數式語言入門的人來說,要分多清楚端看過往寫程式的經驗以及潔癖程度。
    • 每次回傳都是一筆新的資料:基於數值不可變的特性。
    • 一樣的參數 => 一樣的結果:這點也包括不該改變參數原本的值,比如 slicesplice 這兩個函數都能夠回傳一個陣列指定範圍的切片,但 splice 不屬於純函數。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    let fruits = ['Banana', 'Orange', 'Lemon', 'Apple', 'Mango'];

    // 純函數
    fruits.slice(1, 3); // ["Orange", "Lemon"]
    fruits.slice(1, 3); // ["Orange", "Lemon"]
    fruits.slice(1, 3); // ["Orange", "Lemon"]
    console.log(fruits); // ['Banana', 'Orange', 'Lemon', 'Apple', 'Mango']

    // 非純函數
    fruits.splice(1, 3); // ["Orange", "Lemon", "Apple"]
    console.log(fruits); // ["Banana", "Mango"]

    fruits.splice(1, 3); // ["Mango"]
    console.log(fruits); // ["Banana"]

    fruits.splice(1, 3); // []

    這會帶來什麼好處?

    • 耦合度低:每個函數都很單純,程式碼簡短(通常)。
    • 容易測試:沒有副作用,傳入與傳出的結果是可預測的。
    • 效率:有一個叫 Om 的項目,是以 ClojureScript 來寫 React 再轉成純 js,轉換後發現執行效率比用純 js 編寫高出了三倍,其中數值不可變是比較大的主因,促使 React 官方寫了 Immutable.js 以達成一樣的結果。

    入門

    入門不算困難,只要知道兩個資料夾的分工、怎麼組合起來,你就入門了。

    components

    存放網頁單一元素的地方,比如按鈕、表單、卡片等會重複使用到的東西,程式碼會儘量保持簡單乾淨。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    // Section.jsx
    class Section extends React.Component {
    render() {
    return <section className="section">{this.props.children}</section>;
    }
    }

    // Header.jsx
    class Header extends React.Component {
    render() {
    return (
    <header>
    <h1>{this.props.title}</h1>
    </header>
    );
    }
    }

    // Description.jsx
    class Description extends React.Component {
    render() {
    return <p>{this.props.text}</p>;
    }
    }

    containers

    載入各個 component 組合出一個頁面、進行數據傳遞以及一些事件綁定的函式建立等等,簡單講就是骯髒事儘量先在這邊處理再傳遞到各個 component。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // App.jsx
    class App extends React.Component {
    render() {
    return (
    <Section>
    <Header title="Title" />
    <Description text="blablabla" />
    </Section>
    );
    }
    }

    最終會生成這段 html:

    1
    2
    3
    4
    5
    6
    <section class="section">
    <header>
    <h1>Title</h1>
    </header>
    <p>blablabla</p>
    </section>

    清楚明瞭是我剛入門時的第一個看法,尤其 jsx 的排法令人覺得賞心悅目,搭配 rscss 似乎會是個不錯的選擇。

    入門之後?

    js 基於結構靈活(鬆散)的特性、越來越多的 libraries、frameworks 以及往網頁以外的領域發展的趨勢,使之成為一門易學難精的語言。而 React 本身對於以往的開發方式與概念是完全截然不同的,加上 ES6 語法、Webpack、BABEL、npm script 等等東西幾乎是要一起摸索,而在你摸索期間發現,網路上用上 ES7 當作範例的情況愈來愈多,對於初學在尋求幫助上,無異於徒增困擾。

    隨著項目稍微變大、程式碼變多時,另一個問題就浮現了(至少我覺得困擾),一份 React 程式碼(尤其是 containers 資料夾底下的)通常是一份混雜著 js + jsx + html 語法的檔案,必要時還會加點 css,理想狀況是 jsx 將 html 包成一個個 component 再組合,使用起來比純 html 要來的賞心悅目,但 jsx 本身混雜的大括號一多,甚至不得已混了一點純 html 標籤進去時,那一段就會變成一個看起來非常雜亂的區塊,進而讓整份程式碼看起來更加凌亂。

    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
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    // App.jsx
    class App extends React.Component {
    constructor() {
    super();
    this.state = {
    hideAlert: true,
    hideModal: true
    };

    this.openModal = this.openModal.bind(this);
    this.closeModal = this.closeModal.bind(this);
    }

    openModal() {
    this.setState({
    hideModal: false
    });
    }

    closeModal() {
    this.setState({
    hideModal: true
    });
    }

    render() {
    const links = [{
    href: '#',
    text: 'Link1'
    }, {
    href: '#',
    text: 'Link2'
    }];

    return (
    <Main>
    <Nav>
    {links.map(({ href, text }, i) =>
    <Link key={i} href={href} text={text} />;
    )}
    </Nav>
    <Section>
    <Header title="Title" />
    <Content>
    <Description text="blablabla" />
    <Button primary onClick={this.openModal} />
    </Content>
    <Alert hide={this.state.hideAlert} text="You Idiot!!" />
    </Section>
    <Modal hide={this.state.hideModal} header="Modal Header" description="blablabla" onClose={this.closeModal} />
    </Main>
    );
    }
    }

    隨著鼓吹將 css 直接套進 jsx 的解決方案愈來愈多,無論你自己本身願不願意這樣寫,未來都將面臨看到愈來愈多將 css 硬塞進去的程式碼(事實上已經開始了),而你勢必得習慣他。

    但這也不是沒有好處,以往載入如 Bootstrap 等的 framework 時,如果不考慮直接修改原始碼來改變樣式的話,很常需要另外寫 css 來覆蓋原本的設計,必要時還得補上 !important,如果用上如 Semantic UI 這種小元件很多又彼此有關聯的,那就有如一場災難。

    jsx 裡的 css 以 JSON 格式儲存,如果想改變或新增部份的 Material-UI 裡的 css,只要寫進參數就可以直接替換。

    當然,這所謂的「好處」是建立在你用的東西是這樣幹的時候。

    Redux

    引入了 Redux 之後,以往用 jQuery 的事件處理變成資料流的方式進行,其實我還滿喜歡這個概念的,但實際運用時卻是有點令人煩躁。

    觀念方面

    在一開始時,我還在摸索 React,所以對於 Flux/Redux 的技術只有看看文章而已,或許是我資質駑鈍,但這些文章跟圖說實在很難在我腦中建立關聯,在我摸索了幾個簡單的 Redux 官方範例好一陣子後,我才慢慢有個概念出來。

    操作方面

    Redux 引入之後,在原本的兩個資料夾為基礎新增了 actionsreducersstore 三個資料夾,而新增一個按鈕事件時,流程是這樣的:

    1. 在 reducers 的 button.js 新增一個事件常數(比如 CLICK)進 switch case 裡。
    2. 在 actions 的 button.js 新增一個函數(比如 click()),負責傳遞這個常數以及資料過去給 reducer。
    3. 在 container 裡把 actions 裡的函數引進來,再傳給 Button component 做事件綁定。
    4. Button component 接收到這個函數後,綁定在 DOM 事件上。

    為了新增一個事件,一個沒有要幹什麼大事的事件,我得跑過四個資料夾才算完成!!

    middlewares

    由於我是第一次使用 Redux,所以我決定不要任意引入額外的 middlewares,以了解 Redux 最原本的使用方式,但我還是去翻了翻一些網路範例常用的 middlewares,而這些 middlewares 常讓我有一個疑問(那感覺我也說不上來),他們到底是「解決方案」?還是「折衷方案」?抑或只是「虛幌一招」(看起來好像很厲害)?

    這恐怕只有在往後實際用上時才能體會了。

    共通的問題

    重複的工作太多

    每建立一個 React 檔案,就得先補上以下的程式碼:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    import React, { Component } from 'react';

    class App extends Component {
    render() {
    // return jsx or html;
    }
    }

    export default App;

    Redux 除了上面講的新增事件的困擾以外,如果是得多新增檔案,那也得補上跟其他檔案一樣的東西,縱使有 redux-actions 之類的 middleware 改善,但也僅僅是就那麼點改善,這到底是「解決方案」?還是「折衷方案」?

    學習與開發重點

    ES6

    Airbnb JavaScript Style Guide 可以當成一個不錯的 ES6 入門文件,之後再閱讀 ECMAScript 6 入门做更進一步的了解。

    React

    • 能用 css 解決的就用 css。
    • 能用 Virtual DOM 解決的就不要用原生操作。
    • Say goodbye to jQuery.
    • Airbnb React/JSX Style GuideReact 稍微懂一些後可以參考看看。
    • React 大部分的 plugins 程式碼都不多,使用前不妨看一下原始碼,或許你只需要擷取出一部份,不用再多載一個就能自己實現。
    • ES6 的 class 只做了半套,在有一定程度了解後,ES7 不妨給他用下去。

    舉個簡單的例子,React 有 PropTypes 可以檢測傳進來的參數是否有錯誤,ES6 的寫法是這樣:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class Modal extends React.Component {
    render() {
    // ......
    }
    }

    Modal.propTypes = {
    hide: React.PropTypes.bool.isRequired,
    header: React.PropTypes.string.isRequired,
    description: React.PropTypes.string.isRequired,
    onClose: React.PropTypes.func.isRequired
    };

    ES7:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class Modal extends React.Component {
    static propTypes = {
    hide: React.PropTypes.bool.isRequired,
    header: React.PropTypes.string.isRequired,
    description: React.PropTypes.string.isRequired,
    onClose: React.PropTypes.func.isRequired
    };

    render() {
    // ......
    }
    }

    Redux

    • 如果只是改變 component 自身狀態,不要考慮用。
    • 跨 component 的事件處理如果數量不多,可以考慮不用。
    • 第一次用建議不要用太多額外的 middlewares,先知道基本用法是怎樣,之後再去考慮這件事。

    後記

    其他前端 framework 除了 ES6/ES7、Webpack 等等要學之外有沒有各自的狀況?我想是有的,但在討論學習曲線這件事時,我想不會有像 Twitter 上這樣的回應

    就像前面說的,js 是個易學難精的語言,配上 React 不一樣的觀念以及整個生態後,無異於更多麻煩。但目前已決定在 React 上暫且繼續做下去,對其它流行的前端框架目前興趣不大,也沒有切換的打算。

    但我想我之後下個階段會研究 RxJSCycle.js 吧,其中 Cycle.jsRedux 都有從 Elm 語言尋求靈感,所以 Elm 或許也會成為一個考慮選項也說不定。

    延伸閱讀

    線上課程

    首圖來源

    React Redux Example