symphony-joy
symphony-joy 的目标是创建便捷高效开发和用户体验的 React 应用,灵感来自于Next.js和Dva等优秀的开源库,在此非常感谢以上开源贡献者的辛勤付出。
特征
以下特征功能均可零配置实现, symphony-joy默认为应用良好运行提供了默认配置,当然可以定制配置。
- 自动编译和打包源码(使用webpack和babel)
- 服务端数据获取和渲染, 解决首屏加载速度、页面静态化、SEO等问题
- 代码热加载,便于开发调试
- 按需加载,提升页面加载效率
- 使用Model类管理redux 的action、state、reducer部件,代码结构和业务逻辑更清晰
- 支持插件化配置,兼容next.js的大部分插件。
安装
运行npm init
创建一个空工程,并填写项目的基本信息,当然也可以在一个已有的项目中直接安装。
npm install --save symphony-joy react react-dom
symphony-joy 只支持 React 16.
创建./src/index.js
文件,并插入以下代码:
import React Component from 'react' { return <div>Welcome to symphony joy!</div> }
然后运行symphony
命令,在浏览器中输入访问地址http://localhost:3000
。如果需要使用其它端口来启动应用 symphony dev -p <your port here>
到目前为止,一个简单可完整运行的react app已经创建完成,例子 hello-world, 那我们拥有了什么呢?
- 一个应用入口(
./src/index.js
),我们可以在里面完善我们的app内容和添加路由(参考react-router-4的使用方法) - 启动了一个开发服务器,可以访问我们编写的界面了
- 一个零配置的webpack编译器,监控我们的源码,并实时编译为在服务端和浏览器运行的js。
- 热加载,如果我们修改了
./src/index.js
的内容并保存,界面会自动刷新 - 静态资源服务,在
/static/
目录下的静态资源,可通过http://localhost:3000/static/
访问
样式 CSS
jsx内建样式
和next.js一样,内建了 styled-jsx 模块,支持Component内独立域的CSS样式,不会和组件外同名样式冲突。
<div> Hello world <p>scoped!</p> <style >` p { color: blue; } div { background: red; } @media (max-width: 600px) { div { background: blue; } } `</style> <style >` body { background: black; } `</style> </div>
查看 styled-jsx 文档 ,获取详细信息。
Import CSS / LESS / SASS 文件
为了支持导入css、less和sass样式文件,可使用next.js的兼容插件,具体使用方法请见插件详情页面。
访问静态文件
在工程根目录下创建static
目录,在代码里,通过在url前面添加/static/
前缀来引用里面的资源
<img ="/static/my-image.png" />
自定义 Head
symphony-joy 提供了内建的component来自定义html页面的部分
import Head from 'symphony/head' <div> <Head> <title>My page title</title> <meta ="viewport" ="initial-scale=1.0, width=device-width" /> </Head> <p>Hello world!</p> </div>
为了避免在head
中重复添加多个相同标签,可以给标签添加key
属性, 相同的key只会渲染一次。
import Head from 'next/head' <div> <Head> <title>My page title</title> <meta ="viewport" ="initial-scale=1.0, width=device-width" ="viewport" /> </Head> <Head> <meta ="viewport" ="initial-scale=1.2, width=device-width" ="viewport" /> </Head> <p>Hello world!</p> </div>
在上面的例子中,只有第二个<meta name="viewport" />
被渲染和添加到页面。
获取数据
symphony-joy提供了symphony-joy/fetch
方法来获取远程数据, 其调用参数和浏览器提供的fetch方法保持一致。
import fetch from 'symphony-joy/fetch' ;
symphony-joy/fetch
内建提供简单的跨域解决方案,在浏览器发起的跨域请求,会先被封装后转发到服务端,由服务端完成远端的数据请求和将响应转发给浏览器端,服务端作为自动的代理服务器。
TODO 插入流程图
如果想关闭改内建行为,使用jsonp来完成跨域请求,可以在fetch的options参数上设定options.mode='cors'
import fetch from 'symphony-joy/fetch'
在不做任何配置的前提下,依然可以使用其它的类似解决方案,例如:node-http-proxy, express-http-proxy等,在服务端搭建proxy服务。我们内建了这个服务,是为了让开发人员像原生端开发人员一样,更专注于业务开发,不再为跨域、代理路径、代理服务配置等问题困扰。
应用组件
图中蓝色的箭头表示数据流的方向,红色箭头表示控制流的方向,在内部使用redux来实现整个流程,为了更好的推进工程化以及简化redux的实现,我们抽象了出了Controller和Model两个类。
为了更好的理解以下内容,查先查阅一下知识点:redux, dva concepts
Controller
Controller的作用是管理View和model状态的绑定,新增了componentPrepare
生命周期方法,用于在界面渲染前获取业务数据,在服务端渲染时,componentPrepare
会在服务端被执行一次,等待里面的所有数据获取方法执行完成后,才会渲染出界面返回给浏览器,浏览器会复用服务端准备的数据,不会执行再次执行该方法,如果没有启动服务端渲染,或者是在运行时动态加载的界面,该方法将在客户端上自动运行。
import React Component from 'react';import controller from 'symphony-joy/controller' @ { let dispatch = thisprops; } { let user = thisprops; return <div> user name:me ? mename : 'guest' </div> ; }
在上面,我们使用@Controller(mapStateToProps)
装饰器来将一个普通的React Component声明为一个Controller,同时提供mapStateToProps
的参数来将model状态和组件props属性绑定, 当model的状态发生改变时,同时会触发props的改变。
每个controller的props
都会被注入一个redux的dispatch
方法,dispatch
方法是controller给model发送action的唯一途径,action
是一个普通对象,其type属性指定了对应的model和方法。
Model
Model拥有初始状态initState
和更新state的方法setState(nextState)
,和Component的state概念类似,这里并没有什么魔法和创造新的东西,只是将redux的action
、actionCreator
、reducer
,thunk
等难以理解的概率抽象成业务状态和流程,并封装到同一个model中,从而使开发人员更专注于业务,同时实现业务和展现层的分离.
下面是一个简单的model对象示例:
import model from 'symphony-joy/model' @ // the mount point of store state tree, must uniq in the app. namespace = 'products'; // model has own state, this is the initial state initState = pageIndex: null pageSize: 5 products: ; async { // fetch data let data = await resolve reject ; ; let products = this; if pageIndex === 1 products = data; else products = ...products ...data; this; } ;
我们使用@model()
将一个类声明为Model类,Model类在实例化的时候会添加getState
、setState
,dispatch
等快捷方法,下面展示如何使用一个model
import React Component from 'react';import ProductsModel from '../models/ProductsModel'import controller requireModel from 'symphony-joy/controller' @ // register model@ async { let dispatch = thisprops; // invoke model's method await ; } { let products = = thisprops; return <div > <div>PRODUCTS</div> <div> products </div> </div> ; }
- 注册model,
@requireModel(ModelClass)
注册Controller需要依赖的Model,通常只需要在model的入口Controller上注册一次,重复注册无效。 - 获取model的状态, 只有controller类型的Component才能绑定Model中的状态,在使用
@controller(mapStateToProps)
声明Controller时,第一个参数mapStateToProps
是一个回调函数,回调函数参数state
为store的整个状态,使用state[namespace]
来获取特定model的状态。 - 调用model的方法,
store.dispatch(action)
发送action对象到model的方法中,action对象中的type属性格式为namespace/methodname
,namespace
为Model类中定义的namespace,methodname
是Model类中定义的方法名称,action对象中同样可以包含其它业务参数, 例如上面例子中的pageIndex
。
Model API
namespace
model将会被注册到store中,由store统一管理model,在store中不能存在两个相同的namespace
的model。
initState
在创建新的store时,作为store的初始状态,在之后的model的运行过程中使用的是store中对应的state, 所以请勿直接使用model.state
来获取和更新model的状态,提供了setState(nextState)
和getState()
方法来操控state。
setState(nextState)
setState(nextState)
更新model的状态,nextState
是可以是当前model状态的一个子集,内部将使用浅拷贝的方式合并当前的状态,并更新store的state。
getState
getState()
获取当前model的状态,async
函数运行中,store的状态可能已经发生了改变,可使用该方法,获取最新状态。
getStoreState()
getStoreState()
获取当前store的状态,和getState()
方法类似。
dispatch(action)
和redux的store.dispatch(action)
的使用一样,我们可以通过该方法发送一个普通action对象到store。
Dva Model
我们同时兼容dva风格的model对象,使用方法和上面一样,model对象的定义请参考 Dva Concepts ;
Router
使用方法请参考:react-router-4
我们并未对react-router-4做任何的修改,仅仅只是封装了一个外壳,方便统一导入和调用。
import Switch Route from 'symphony-joy/router'
TODO
- 完善使用文档
- 添加例子和测试案例