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

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