React全棧開發框架快速開發 @symph/joy=webpack+babel+redux+MVC+依賴注入+Dva兼容+服務端渲染+插件化配置+優化集成

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

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

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

也許你現在對webpack、babel、redux還完全不瞭解,沒關係,我們依然可以開始工作,因爲這些底層技術@symph/joy已經爲你準備好了,等你以後想深入定製開發,也可以輕鬆升級定製它們,下面開始寫第一個頁面。

創建項目目錄

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

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

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

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

編寫第一個頁面

1. 寫代碼

創建./src/index.js文件,並插入以下頁面代碼:

import React, { Component } from 'react'

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

2. 運行

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

3. 做了什麼

到此處,我們擁有了哪些呢?

  • 應用入口(./src/index.js),一切都從這裏開始,以後可以逐步實現更多功能
  • 啓動了一個開發服務器,支持服務端渲染和請求代理
  • 一個零配置的webpack+babel編譯器,確保代碼在服務端和瀏覽器上正確運行
  • ES6、7、8等高級語法支持,如:classasync@註解、{...}解構等
  • 熱更新,調試模式下,在瀏覽器不刷新的情況下,使更改立即生效
  • 靜態資源服務,在/static/目錄下的靜態資源,可通過http://localhost:3000/static/訪問
  • 支持靜態版本導出,可部署到靜態服務上,比如Nginx和CDN上

使用樣式

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三個字母代表着應用中的三種不同類別的模塊,它們有各自的職責:

  • Model: 管理應用的數據和行爲,有初始狀態,業務運行中不斷更新狀態
  • View: 展示數據,繼承React.Component
  • Controller: 控制協調Model和View,例如:View的展示,綁定Model數據到View,調用Model中的業務

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

app work flow

圖中藍色的箭頭表示數據流的方向,紅色箭頭表示控制流的方向,他們都是單向流,Model的業務方法在運行的過程中動態修改store的statestate發生改變時,綁定到該state的View將自動更新,詳細的工作流程請閱讀redux文檔

如果你對Redux不瞭解,請不要擔心,框架已經幫我們簡化了這些底層細節,讓我們更專注於業務開發,這裏完全可以放心進行下一步操作。

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

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

-project
    -src
        -controllers/
            -TodoListController.js
        -models/
            -TodosModel.js
        -index.js

創建Model

下面展示的是一個管理待辦列表的model。

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

@model()
export default class TodosModel {

  // model的唯一標識,在一個應用內不能重複
  namespace = 'todos';

  // 初始狀態
  initState = {
    pageSize: 5,
    count: 0,
    entities: [],
  };

  async getTodos({lastId: 0, pageSize: 5}) {
    // @symph/joy fetch已集成自動代理服務,不用擔心是否跨域問題
    let pagedTodos = await fetch('https://www.example.com/api/hello', 
      {body:{lastId, pageSize}});

    let {entities} = this.getState();
    if (lastId === 0) {
      // first page
      entities = data;
    } else {
      entities = [...entities, ...pagedTodos];
    }
    
    // 更新state狀態
    this.setState({
      entities,
      pageSize
    });
  }
};

創建Controller

接下里編寫一個Controller,綁定上面TodosModel中的待辦列表並展示,在界面初始化的時候加載數據。

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

@controller((state) => {              
  return {
    todos: state.todos.entities // 綁定model的狀態
  }
})
export default class IndexController extends Component {

  @autowire()
  todosModel: TodosModel       // 申明依賴的Model

  async componentPrepare() {
    // 調用Model中的業務方法
    await this.todosModel.getTodos(0, 5) 
  }

  render() {
    let {todos = []} = this.props;
	// View
    return (
      <div >  
        <div>Todo List</div>
        <div>
          {todos.map((todo, i) => {
            return <div key={todo.id} >{todo.id}:{todo.content}</div>
          })}
        </div>
      </div>
    );
  }
}

使用View

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

添加路由 Router

接下來進行第二個頁面的開發,假設添加一個src/controllers/TodoDetailController.js.jsx頁面來展示待辦事項的詳細信息。

// src/controllers/TodoDetailController.js.jsx
import React, { Component } from 'react'
import { controller } from '@symph/joy/controller'
import { autowire } from '@symph/joy/autowire'
import TodosModel from '../models/TodosModel'

@controller((state, ownProps) => {
  const todoId = Number(ownProps.match.params.id)
  return {
    todoId,
    todo: state.todos.details[todoId]
  }
})
export default class TodoDetailController extends Component {

  @autowire()
  todoModel: TodosModel

  async componentPrepare () {
    let {todoId} = this.props
    await this.todoModel.getTodo(todoId)
  }

  render () {
    let {todo} = this.props
    return (
      <div className={styles.root}>
        <h1>Todo Detail</h1>
        {
          !todo
            ? <div className={styles.loading}>data loading...</div>
            : <div>content: {todo.content}</div>
        }
      </div>
    )
  }
}

此時需要路由組件來完成頁面之間的跳轉,@symph/joy使用react-router-4作爲路由,我們可以直接從'@symph/joy/router'中導入並使用。

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

修改入口組件

// src/index.js
import React, { Component } from 'react'
// 路由組件
import { Switch, Route, Redirect } from '@symph/joy/router'
// 我們的業務組件
import TodoListController from './controllers/TodoListController.js'
import TodoListController from './controllers/TodoDetailController.js'

export default class Main extends Component {
  render () {
    return (
      <div>
        <h1>Example Basic - Header</h1>
        <Switch>
          <Route exact path="/" component={TodoListController}/>
          <Route exact path="/todos/:id" component={TodoDetailController}/>
        </Switch>
      </div>
    )
  }
}

打包部署

最後我們需要將應用打包,然後部署到生產機器上,@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部分等
  • 多Model協同工作
  • 更復雜的路由控制
  • 瀏覽器端js按需加載、代碼分割、壓縮等優化處理
  • 自定義webpack和babel配置等
  • 404等錯誤頁面自定義

請查看@symph/joy詳細使用文檔:https://lnlfps.github.io/symph-joy

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