現代前端應用是一個工程化的系統,和jquery時代有着很大的不同,以前的主要工作是模板填充、事件綁定、DOM更新等,而現在前端應用和後端工程一樣,需要從一個完整的系統層面思考,數據流和控制流如何流轉?數據該如何存放?業務方法如何管理?UI組件如何封裝和路由?最後還有編譯打包,優化等問題。幸運的是已有各種工具幫我們解決了絕大部分的問題,可如何將這些工具庫組合在一起形成一個可用的應用,並不是一件簡單的事情,例如我們需要大量的配置來使webpack和babel按照要求工作。
那如何能快速的開始工作呢?選擇一個前端框架定會事半功倍,例如最常用的react-create-app、next.js和dva-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等高級語法支持,如:
class
、async
、@
註解、{...}
解構等 - 熱更新,調試模式下,在瀏覽器不刷新的情況下,使更改立即生效
- 靜態資源服務,在
/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
它們之間的工作流程如下圖
圖中藍色的箭頭表示數據流的方向,紅色箭頭表示控制流的方向,他們都是單向流,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.json
的scripts
節點中添加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命令export
到package.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