如何快速啓動React前端應用開發(Redux、webpack、babel、postcss、SSR)

現代前端應用是一個工程化的系統,和jquery時代有着很大的不同,以前的主要工作是模板填充、事件綁定、DOM更新等,而現在前端應用和後端工程一樣,需要從一個完整的系統層面思考,數據流和控制流如何流轉?數據該如何存放?業務方法如何管理?UI組件如何封裝和路由?最後還有編譯打包,優化等問題。幸運的是已有各種工具幫我們解決了絕大部分的問題,可如何將這些工具庫組合在一起形成一個可用的應用,並不是一件簡單的事情,例如我們需要大量的配置來使webpack和babel按照要求工作。

那如何能快速的開始工作呢?選擇一個前端框架定會事半功倍,例如最常用的react-create-appnext.jsdva-cli等,都只需要簡單的幾步操作就可以開始進行業務開發了。如果你還沒有聽說過它們或者它們的同類,相信你現在正在被各種配置文件、架構結構、多人協同開發感到困惑。這裏介紹的是另外一個前端框架@symph/joy,它結合了next.js和dva的特點,是一個全棧型的框架(因爲每個項目對UI的要求都不一樣,所以未集成UI組件),儘量讓前端開發更加簡潔、規範。

react-create-app是一個比較基礎的腳手架,很多東西還得開發者自己搭建,並不建議在實際工程中使用。

我們現在從一個React新手的角度思考問題,對webpack、babel、redux等工具還不瞭解,只是想用React寫一個簡單的展示頁面,該如何開始工作呢?

創建項目目錄

運行yarn init創建一個空工程,並填寫項目的基本信息,當然也可以在一個已有的項目中直接安裝。

yarn install --save @symph/joy react react-dom

添加NPM腳本到package.json文件:

{
  "name": "hello-word",
  "version": "1.0.0",
  "scripts": {
    "dev": "joy",
    "build": "joy build",
    "start": "joy start"
  }
}

編寫第一個頁面

然後就可以開始你的開發工作了,創建./src/index.js文件,並插入以下代碼:

import React, { Component } from 'react'

export default class Index extends Component {
  render () {
    return <div>
      <div>Welcome to @symph/joy!</div>
    </div>
  }
}

得到了什麼

最後運行yarn run dev命令,在瀏覽器中輸入訪問地址http://localhost:3000

進行上面簡單的幾步後,我們擁有了些什麼呢?

  • 應用入口(./src/index.js),一切都從這裏開始,以後可以添加子路由、佈局、Model等組件
  • 啓動了一個服務器,支持服務端渲染和業務請求代理轉發
  • 一個零配置的webpack+babel編譯器,確保代碼在Node.js和瀏覽器上正確運行
  • ES6、7、8等高級語法支持,如:classasync@註解、{...}解構等
  • 熱更新,調試模式下,在瀏覽器不刷新的情況下,使更改立即生效
  • 靜態資源服務,在/static/目錄下的靜態資源,可通過http://localhost:3000/static/訪問

第二個頁面,添加路由

接下來開始第二個頁面的開發,假設添加一個src/about.jsx頁面。

// src/about.jsx
import React, { Component } from 'react'

export default About class extends Component {
  render () {
    return <div>
      <div>about @symph/joy</div>
    </div>
  }
}

此時需要路由組件來完成頁面之間的跳轉,@symph/joy使用react-router-4作爲路由,並在應用初始時已集成到應用中,所以不需要開發者自己添加依賴和初始化react-router-4,我們可以直接從'@symph/joy/router'中導入並使用。

首先對之前的src/index.js進行改造,將首頁的內容獨立出來,作爲一個單獨的頁面,方便在兩個界面之間跳轉。

// src/hello.jsx
import React, { Component } from 'react'

export default Hello class extends Component {
  render () {
    return <div>
        <div>Welcome to @symph/joy!</div>
    </div>
  }
}

修改入口組件

// src/index.js
import React, { Component } from 'react'
import {Switch, Route, Link} from '@symph/joy/router'
import Hello from './hello'
import About from './about'

export default class Index extends Component {
  render () {
    return (<div>
       <div className="header">
         <Link to="/">首頁</Link>
         <Link to="/about">關於</Link>
       </div>
       <Switch>
         <Route exact path="/about" component={About}/>
         <Route path="/" component={Hello}/>
       </Switch>
    </div>)
  }
}

使用樣式

jsx內建樣式

內建了 styled-jsx 模塊,支持Component內獨立域的CSS樣式,不會和其他組件的同名樣式衝突。

// src/hello.js
import React, { Component } from 'react'

export default class Hello extends Component {
  render () {
    return (<div>
       <div className="message">Welcome to @symph/joy!</div>
       <style jsx>{`
         .message {
           color: blue;
         }
       `}</style>
       <style global jsx>{`
         body {
          background: #F5F5F5;
         }
       `}</style>
    </div>)
  }
}

查看 styled-jsx 文檔 ,獲取詳細使用信息。

Import CSS / LESS 文件

@symph/joy提供了插件來處理樣式,默認支持post-css、autoprefixer、css-modules、css-extract,具體使用方法請查看插件使用文檔。

MVC工程模式

我們可以在頁面上使用fetch方法來獲取數據並展示,此時已經具備了編寫一個前端應用的全部能力了。但作爲一個日益複雜,需多人協同開發,且可持續維護的項目來說,還遠遠不夠。至少我們還缺少對數據和業務方法的管理,並且要形成一個規範共識,纔可持續的發展下去。

MVC模式用於規範我們的代碼設計,這是一個很基礎的,也是很容易理解的模式,要將上面的例子變爲MVC模式,只需要添加幾行聲明式的註解代碼,不會侵入業務代碼。MVC三個字母代表着應用中的三個不同類別的部件,它們有各自的職責:

  • Model: 管理應用的行爲和數據,普通class類,有初始狀態,業務運行中更新model狀態
  • View: 展示數據,繼承React.Component
  • Controller: 控制View的展示,綁定Model數據到View,響應用戶的操作,調用Model中的業務, 繼承於React.Component

它們之間的工作流程如下圖

app work flow

圖中藍色的箭頭表示數據流的方向,紅色箭頭表示控制流的方向,他們都是單向流,store中的state對象是不可修改其內部值的,狀態發生改變後,都會生成一個新的state對象,且只將有變化的部分更新到界面上,這和redux的工作流程是一致的。

如果你剛接觸React和Redux不久,也許對上面的概念理解起來有點困難,請不要擔心,框架就是幫助我們解決掉各種複雜的概念和繁瑣重複的工作而的,讓我們更專注於業務開發。所以這裏,我們完全可以放心進行下一步操作。

下面通過一個商品展示的例子來說明Model,View,Controller之間是如何工作的,查看示例工程

目錄結構建議採用以下形式

-project
    -src
        -components/
        -controllers/
            -ProductsController.js
        -models/
            -ProductsModel.js
        -index.js

### 創建Model

下面展示的是一個管理商品列表的model,負責存放商品列表、分頁信息等,還有獲取和處理這些信息的業務方法。

// src/models/ProductsModel.js
import model from '@symph/joy/model'
import fetch from '@symph/joy/fetch'

// 使用註解的方式,申明這是一個Model
@model() 
export default class ProductsModel {

  // 這個model的全局名稱,後面需要通過這個名稱來找到model的狀態。
  namespace = 'products';

  // model的初始化狀態
  initState = {
    pageIndex: null,
    pageSize: 10,
    products: [],
  };

  async getProducts({pageIndex = 1, pageSize}) {
    // 發送遠程請求,獲取數據
    let pagedProducts = await fetch('https://www.example.com/api/hello', 
      {body:{pageIndex, pageSize}});

    let {products} = this.getState();
    if (pageIndex === 1) {
      products = data;
    } else {
      products = [...products, ...pagedProducts];
    }

    // 更新model的狀態,此時和model狀態綁定的Controller也會同步更新
    this.setState({
      products,
      pageIndex,
      pageSize
    });
  }
};

創建Controller

接下里編寫一個Controller來綁定上面ProductsModel中的產品列表,並在界面加載的時候,調用獲取產品列表的業務方法。

// src/controllers/ProductsController.js
import React, {Component} from 'react';
import ProductsModel from '../models/ProductsModel'
import controller, {requireModel} from '@symph/joy/controller'


@requireModel(ProductsModel)          // 註冊依賴的model
@controller((state) => {              // 使用註解的方式,申明這是一個Controller
  return {
    products: state.products.products // 綁定ProductsModel中的產品列表
  }
})
export default class ProductsController extends Component {

  async componentPrepare() {
    let {dispatch} = this.props;

    // TOOD 這裏可以打開加載動畫

    await dispatch({        // 調用ProductsModel中獲取產品列表的業務方法
      type: 'products/getProducts', 
      pageIndex: 1,
      pageSize: 10,
    });

   // TOOD 這裏可以關閉加載動畫

  }

  render() {
    let {products = []} = this.props;   // 獲取綁定的產品列表
    return (
      <div >
        <div>Product List</div>
        <div>
          {products.map((product, i) => {
            return <div key={product.id} onClick={this.onClickProduct.bind(product)}>{product.id}:{product.name}</div>
          })}
        </div>
      </div>
    );
  }
}

使用View

View只是一個普通的React.Component類,是一個純展示組件,它的數據來自於props屬性。

打包部署

最後我們需要將應用打包部署到生產機器上,@symph/joy提供了命令工具來處理這些事情。在package.jsonscripts節點中添加NPM腳本:

// package.json
{
  "scripts": {
    "dev": "joy dev",
    "build": "joy build",
    "start": "joy start"
  }
}

在命令行工具裏使用yarn run build命令來編譯代碼,生成.joy目標目錄。然後將.joy目錄上傳到生產機器上,不用上傳源代碼文件了。最後在生產機器上執行yarn run start命令來啓動應用,此時會直接運行.joy目錄中預先編譯好的代碼。

靜態版本輸出

如果我們的前端應用沒有專門的node.js服務器來運行,或者不需要服務端渲染,可以將應用編譯爲靜態版本,將其放到靜態資源服務器上,瀏覽器直接加載運行。

這不需要修改任何的源代碼,只需添加一個NPM命令exportpackage.json文件中:

{
  "scripts": {
    "build": "joy build",
    "export": "yarn run build && joy export"
  }
}

執行yarn run export命令完成導出工作,靜態版本需要的所有文件都放在應用根目錄下的out目錄中,只需要將out目錄部署到靜態文件服務器就可以了。

高級功能

上文只是對前端日常開發工作做了介紹,但在實際項目中,還會遇到很多需求,例如:

  • 集成到express、koa等服務端框架中
  • 定義html渲染,加入js、css文件引用,自定義head部分等
  • 跨域請求如何解決
  • 更復雜的路由控制
  • 瀏覽器端js按需加載、代碼分割、壓縮等優化處理
  • 自定義webpack和babel配置等

@symph/joy對以上問題都提出瞭解決方案,更多更詳細的使用介紹,請查看其主頁:https://lnlfps.github.io/symph-joy

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章