使用dva+antd快速构建单页面应用
项目结构及使用工具集原文地址: 个人博客或joescott.coding.me/blog `project |----- src 项目源代码 |----- dist 项目编译目标 |----- .roadhogrc 路霸运行配置文件 |----- lumen_api RESTful api代码目录 |----- mock 模拟数据服务目录 `src |--- index.js 入口js文件 |--- index.html 项目入口html文件 |--- router.js 路由文件 |--- routes 子路由目录, 下面每个子路由使用一个单独的文件夹 |--- components 组件目录,这里特指公共组件 |--- models model目录 |--- services 服务目录 |--- utils 工具包目录 |--- constants.js 常量文件,这个文件其实可放入utils目录,然后统一暴露出去 以上是项目中的总体目录结构。 下面详细介绍几个重要部分的结构。 此应用是当入口应用,入口在src/index.js, 配置在.roadhogrc中,当然roadhog还支持多入口模式,这里不涉及。 组件系统项目中组件分为两大类,容器组件和呈现组件。 容器组件容器组件对应于每个独立的route页面。每个容器组件都维护一个相关的state,所有的state改变都由容器最终执行。容器组件负责向其子组件(呈现组件)分配属性(props)。 该项目中,所有子组件仅作呈现组件,没有state,只有从父级组件传递下来的props。state由容器组件统一管理,然后分发到子组件中。 容器组件在该项目中以路由组件的形式存在,存放在src/routes下面对应的子目录中。每个容器组件使用的子组件(非共享的)都在路由组件目录中存放。而使用到的公共组件则存放在components目录下面。例如公共组件提供数据表的包装,下拉操作控件包装等等,在多个容器组件的子组件中会用到。都被抽离到components目录中。 容器组件的范本如下: // routes/users/index.js import React,{ PropTypes } from 'react' import { RouterRedux } from 'dva/router' import { connect } from 'dva' function Users({ location,dispatch,users,loading }) { } Users.propTypes = { menus: PropTypes.object,// ... } function mapStateToProps(state) { return { users: state.users,loading: state.loading.models.users,} } export default connect(mapStateToProps)(Users) 创建一个类Users,接收一些参数,用于类自己使用,后面会通过connect将state联系给这些参数。 将state和类的属性联系起来, 通过connect方法来实现导出组件 呈现组件项目中的呈现组件根据共享特性,分别存放于routers目录和components目录中。它们是无state组件,只从父组件获取到props。比如容器组件向呈现组件传入state相关的部分属性和相应的操作方法给呈现组件的props,一级级递归传下去。 而子组件的交互产生改变state的操作,则由子组件沿原路上传回给容器组件,最终由容器组件的具体方法来触发state的同步,以及UI的更新。 呈现组件的范本如下: import React,{ PropTypes } from 'react' // ... function XView ({ prop1,prop2,prop3,// ... }) => { // create XView propOpts const propOpts = { p1,p2,// ... } return ( <div {...propOpts}> <div>something to render</div> </div> ) } XView.propTypes = { // ... } export default XView 呈现组件和容器组件相比,就是没有使用connect进行state到prop建立联系。这很正常,因为呈现组件是无状态的的,它只有属性,从父层传下来的属性而已。 有了这样的呈现组件,那么就可以直接在父层调用: <XView {...props}> </XView> XView调用的时候,属性props会作为XView类构造函数的输入。 模型系统该应用的模型model按业务维度设计。模型设计有两种实现方式:
该应用使用后者。 模型位于src/models,每个独立的route都对应一个model,每个model包含如下属性:
除了上面的几个属性外,需要另外注意几个方法的使用:
基本的model范本如下: // models/users.js export default { namespace: 'users',state: {},subscriptions: {},effects: {},reducers: {} } 服务(services)有了上面的两个部分,基本的静态交互已经就绪,就剩下和真正的或模拟的API交互了,这部分抽离为services,即services提供异步数据获取。 这个层面的设计,相对比较简单,直接在utils中包装一个request类,提供fetch或ajax功能,然后services中直接将请求参数传入相应方法即可。返回请求的结果Promise。 mock服务roadhog使用json作为运行时配置,它提供了代理的配置,简单配置如下: "proxy": { "/api": { "target": "http://localhost:3004/",// "target": "http://192.168.200.30:8099/api","changeOrigin": true,"pathRewrite": { "^/api" : "" } } } 比如使用json-server+mockjs实现的mock服务,启动端口号为3004, 那么使用target指向3004端口,那么请求/api/xxx的时候就进入json-server提供的mock服务。 另外如果和api服务连调的话,同样可以将target指向真实api服务的base url。 例如上面注释掉的那行。 而在正式打包上线后,就不走proxy,免配置修改,直接生效。 API设计API采用lumen微框架实现的restful api,这块的不作过多介绍,如有兴趣自行搜索lumen官网查看,或参照lumen_api中的代码来查看。 总结整个设计下来, 开发流畅性非常不错。 开发体验也非常好。 暂时该项目不支持less,对图片的处理也稍逊色,后续待解决。 roadhog源码分析roadhog是对webpack功能作的一个封装,roadhog会读取自己的配置信息,然后转换为webpack的配置对象,最终调用webpack作项目打包。下面对roadhog源码作简单分析。 roadhog提供了三个命令:
result = spawn.sync( 'node',[require.resolve(`../lib/${script}`)].concat(args),{ stdio: 'inherit' } ); process.exit(result.status); 上面代码中的script的值为build,server或test,而args是roadhog命令后面的option选项。 Options: --debug Build without compress [boolean] [default: false] --watch,-w Watch file changes and rebuild [boolean] [default: false] --output-path,-o Specify output path [string] [default: null] --analyze Visualize and analyze your Webpack bundle. [boolean] [default: false] -h Show help [boolean] roadhog源码中还有一个异步post上报功能, 上报给阿里你当前的平台信息,git用户信息等。 不知道这个具体用于干啥的。 ^-^。 我们下面先看看build.js的逻辑。 roadhog buildbuild.js代码骨架如下: var _extends = Object.assign || function (target) { // Object.assign polyfill } exports.build = build; process.env.NODE_ENV = 'production'; var argv = require('yargs').usage() .option() .option() // ... function build(argv) { // the body of the build } if (require.main === module) { build(_extends({},argv,{ cwd: process.cwd() })); } 注意这里require.main === module判断模块是否为应用的主模块,类似于python的if name == “__main__“。 也就是说roadhog build实际上就是调用了build.js暴露出去的build方法。 argv分析
build函数分析path(lib/config/path.js) 该文件根据build.js当前工作目录,获取应用程序几个重要的相关文件或文件夹的绝对路径:
该方法根据环境获取应用程序当前目录下面的真实配置文件的内容:realGetConfig(‘.roadhogrc’,env,pkg,paths)。 默认使用.roadhogrc配置文件,env为当前环境模式,pkg为package.json文件内容,paths是上面的path相关的路径信息。 roadhog默认配置文件使用json格式的配置,允许在文件中使用注释: return (0,_parseJsonPretty2.default)((0,_stripJsonComments2.default)((0,_fs.readFileSync)(rcConfig,'utf-8')),'./roadhogrc'); 另外如果不使用.roadhogrc这种配置文件,还可以使用.roadhogrc.js文件,使用纯js来实现配置。返回一个配置对象就可以了。 使用.js配置文件可以允许在配置中使用js变量和方法。灵活度还是蛮高的。 如果两者都没有,roadhog依然可以正常使用,自定义配置对象为空对象而已。 另外配置文件中可以使用package.json中的包名称(name)和版本信息(version)。 分别使用$npm_package_name变量和$npm_package_version变量。 另外如果是test环境模式,可以注册babel。这块通过lib/utils/registerBabel.js代码中实现的: require('babel-register')({ only: ... presets: ... plugins: ... babelrc: ... }) roadhog配置转webpack配置 在获取了roadhog配置之后,就会将roadhog的配置转换成webpack的配置对象,毕竟底层使用的是webpack来打包的。 webpack.config.prod.js返回一个函数,该函数返回合并后的webpack对象。 // lib/config/webpack.config.prod.js export default function(args,appBuild,config,paths) { return { bail: true,entry: xxxx // ... } } roadhog除了提供默认的webpack配置,还支持用户自定义webpack配置覆盖roadhog默认配置, 在项目根目录下面建立webpack.config.js文件,该文件的模版如下: export default function (config,env) { const newConfig = {}; // merge or override return newConfig; } 接收的config为roadhog合并默认配置后的配置对象, env是环境模式。 也就是说完全可以利用所有webpack的功能来实现。 构建过程在构建之前,先递归读取构建目录中之前所有的.js文件和.css文件,记录原始文件尺寸,并清理原来的构建目录中的文件。 然后将这些尺寸信息传入构建过程,进行真实构建。 realBuild 真实构建函数实现非常简单,代码如下: function realBuild(previousSizeMap,resolve,argv) { if (argv.debug) { console.log('不压缩的方式构建'); } else { console.log('优化的方式构建'); } var compiler = (0,_webpack2.default)(config); var done = doneHandler.bind(null,previousSizeMap,resolve); if (argv.watch) { compiler.watch(200,done); } else { compiler.run(done); } } 到目前为止,roadhog的打包构建功能已经完全解读完了。归根结底就是webpack打包。 参考连接
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |