原文发表于【抹桥的博客-基于 React 的前端项目开发总结】css
咱们的项目主要选用如下技术开发,再配合一些其它辅助工具。html
react前端
react-routernode
reduxreact
react-reduxwebpack
因为项目是先后端分离的,因此咱们须要一套完整的开发环境,须要包括如下功能:nginx
数据 Mockweb
Webpack 实施编译刷新redux
方便先后端联调后端
基于这些需求,咱们基于 Express, Webpack, Webpack-dev-middleware 搭建了这套完整的开发环境。
能够看到,浏览器全部的请求都被本地的 Node.js 服务拦截。对于静态资源请求,都委托给 webpack-dev-middleware
来处理,对于接口请求根据不一样的环境来决定要作的操做。
当 ENV = 'development'
时,也就是开发环境,那么就直接读取本地的 mock 数据来渲染页面。
当 ENV = 'api'
时,也就是咱们认为的联调环境,这个时候对于接口请求由 node.js 转发到须要联调的真实后端服务地址上,从而避免直接调用所产生的跨域问题。
这样就能够直接用本地开发代码和后端联调,能大大提升效率,省去了每次须要往服务器上构建部署的步骤。
先后端是分开部署的,全部的静态资源都放在 CDN (example.cdn.com)上面。
也就是说咱们的页面在 example.cdn/index.html 这里,可是请求的接口在 example.163.com/api/xxx,咱们确定不能让用户去直接去访问 example.cdn.com/index.html,这样不合理,并且由跨域问题存在。
那么访问 example.dai.163.com 的时候,怎么拿到咱们的 HTML 页面呢?
看下图:
在客户端和后台服务之间架设一台 Nginx, 咱们访问的 example.dai.163.com 有两种请求:
HTML 页面资源
接口请求
这两种请求都先通过 nginx,在这里作判断,若是是页面请求那么由 nginx 转发到 CDN, 不然交给后端服务来响应接口请求。
拿到页面之后,其它全部的 css, js 等静态资源都是直接请求到 CDN ,这里没什么说的。
咱们是借助 redux 来管理数据流的。
咱们来看这张图。
首先,经过 middleware
和 reducer
生成 store
, 而后得到项目的初始 state
,经过初始 state
去渲染页面的初始状态。
以 Home
页面为例,首先 Home
经过 react-redux
提供的 connect
方法拿到初始 state
做为 Home
的 prop
传递给 Home
. 而 Home
由多个不一样的子组件组成,这些组件的须要数据再由 Home 经过 props 传递给本身的子组件。
当 Home
的初始状态加载完毕后,咱们须要向后端请求去拿去一些用户数据。这时,咱们分发一个下面这种格式的 action
:
{ types: ['home/start','home/success','home/failure'], payload: { api: ... }, meta: { isApi: true } }
全部的 action
都会按照咱们制定的循序经过一个个 middleware
.
在这里,咱们的 action
会被 callApiMiddleware
经过 meta
里面的 isApi
标识命中,并去作相应的事情。
好比在这个中间件里面,咱们去作了真实的接口请求,在请求成功或失败的时候分发对应的 action
,以及作一些统一的业务逻辑。好比咱们对后端返回的接口中 code
值有统一的约定,假设 1 为成功, 2 为失败, 3 为未登陆。那么咱们就能够在中间件中去处理这些业务逻辑。
当请求成功,并渲染页面后,假设用户点击了一个按钮,这个按钮须要唤起 native
的一些功能,好比说拍照。那么咱们分发一个唤起拍照功能的 camera/start
的action
:
{ types: ['sdk/start','sdk/success','sdk/failure'], payload: { command: ... }, meta: { isSDK: true } }
一样的道理,这个 action
会被 EpaySDKMiddleware
所识别并处理,在调起 native 的时候, 为了保证安全性,咱们须要向后发起一个请求去拿签名,这个时候就能够在 EpaySDKMiddleware
里面分发一个接口请求的 action
,那么这个 action
一样须要走一遍全部的 middleware
. 那么这个接口请求的 action
就会像上面的流程同样,经过 callApiMiddleware
去处理。
中间件的存在,使整个流程变得很是清晰,接口的请求的中间件就只作接口请求,调用 native 接口的中间件就只作对 native 的调用,当对 native 接口的调用须要作后端接口请求的时候,去分发一个 action
走接口请求的中间件。
每一个中间件只专一于本身的事情,既方便后续的维护,同时也提供了一个很好的拓展方式。
假设咱们由以下的一个路由配置。
{ component: App, path: '/', onEnter: initLogin(store), indexRoute: { getComponent(nextState, cb) { require.ensure([], require => { cb(null, require('../views/Home').default) }, 'Home') }, onEnter: initHome(store) }, childRoutes: [ createActivateRoute(store), { path: 'test', indexRoute: { getComponent(nextState, cb) { require.ensure([], require => { cb(null, require('../views/Test').default) }, 'Test') } } }, ... ] }
那结合 react-route
咱们来看一个完整的流程。当咱们浏览器里面输入 example.dai.163.com/index.html/#/ 的时候。
首先,经过最上面 线上环境 一节提到的内容,拿到页面须要 html,css,js.
而后渲染 Provide
和 Router
组件,分别提供 store
的注入和路由的控制。
此时触发根路径的路由匹配,而后加载根组件 APP
, 而后根据路由匹配规则匹配到 IndexRouter
, 加载 Home
组件。
后面的事情就和前面数据流转一节讲的是同样的了。
在先后端彻底分离的基础上,借助一套完善的开发环境,能够大大提升的咱们的开发效率,下降先后端联调的成本。
同时借助于 Redux 思想,实现单向数据流,让咱们能够实现一个很是清晰的数据流向。而且,借助于中间件,咱们能够更加有效的控制数据流转的过程。为后面项目的扩展提供无限的想象空间。