現代前端應用是一個工程化的系統,和jquery時代有着很大的不同,以前工作中大部分時間都在模板填充、事件綁定、DOM更新等,而現在前端應用和後端工程一樣,是一個完整的系統化工程,數據流和控制流如何流轉?數據該如何存放?業務方法如何管理?UI組件如何封裝和路由?最後還有編譯打包,優化等問題。幸運的是已有各種工具幫我們解決了絕大部分的問題,可如何將這些工具庫組合在一起形成一個可用的應用,並不是一件簡單的事情,例如我們需要大量的配置來使webpack和babel按照要求工作。
那如何能快速的啓動工作呢?選擇一個前端框架定會事半功倍,例如最常用的react-create-app、next.js和dva-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等高級語法支持,如:
class
、async
、@
註解、{...}
解構等 - 熱更新,調試模式下,在瀏覽器不刷新的情況下,使更改立即生效
- 靜態資源服務,在
/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中的業務
它們之間的工作流程如下圖
圖中藍色的箭頭表示數據流的方向,紅色箭頭表示控制流的方向,他們都是單向流,Model的業務方法在運行的過程中動態修改store的state
,state
發生改變時,綁定到該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.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部分等
- 多Model協同工作
- 更復雜的路由控制
- 瀏覽器端js按需加載、代碼分割、壓縮等優化處理
- 自定義webpack和babel配置等
- 404等錯誤頁面自定義
請查看@symph/joy詳細使用文檔:https://lnlfps.github.io/symph-joy