写在前面

花几天时间做了个小东西,不得不提,麻雀虽小,但五脏俱全,充分体现出一个全栈工程师在小项目上高效的产出能力 (^-^)。简单介绍下:

架构适宜

如果你是一个前端开发工程师,并且懂一点Node和数据库。有一天,你的老板逼你快速开发一个移动端的商城加一个管理后台,请不要慌张,装上我的轮子跑跑看。

快速搭建

本打算弄个脚手架工具,但是出于教学的目的,还是一步步地告诉大家怎么搭这个全栈式的框架。

express生成服务端雏形

会点Node的应该对express不陌生,模版引擎我习惯使用ejs,所以执行下面命令:

$ express -e myapp && cd myapp && npm install

这样服务端的雏形就有了:

|----myapp|----bin/|----node_modules/|----public/|----routes/|----views/|----app.js|----package.json

设计和部署数据库

Mac 上推荐使用 MySQLWorkBench 设计和管理数据库,当然要是你够牛逼,不用GUI工具也行,直接敲命令也可以玩。设计好数据库表关系之后,导出.sql文件并生成数据库。

node连接mysql

node连接mysql需要用的三方的mysql库,先安装:

$ npm install mysql -save
不妨搞个配置文件:config.db.js
/*** @desc mysql数据库配置文件**/var config = {host: 'localhost',port: 3306,user: 'root',password: '你的数据库密码',database: '你的数据库名称',
};module.exports = config;
连连看
var mysql = require('mysql'),config = require('./config.db');
var con = mysql.createConnection(config);

也来耍耍MVC

虽然后端不是强项,但也不能太失水准,设计模式上怎么也搞个MVC,看新的目录结构:

|----myapp....|----database/          /*管理数据模型(即数据模型)*/|----config.db.js    /*连接配置*/|----user.db.js      /*用户模型,以这个为例*/|----route/             /*路由+业务逻辑处理*/|----services/       /*业务逻辑处理(即控制器)*/|----user.ctrl.js  /*用户控制器,以这个为例*/|----index.js      /*默认路由*/|----api.js        /*API入口*/|----helper.js       /*后端使用的工具方法*/|----views/             /*模版文件(即视图)*/|----index.ejs       /*前台入口*/|----admin.ejs       /*后台入口*/
user.db.js举例
/*** @desc 用户 数据模型* @author Jafeney <692270687@qq.com>**/var mysql = require('mysql'),helper = require('../routes/helper'),config = require('./config.db');var con = mysql.createConnection(config);/*用户模块 构造方法*/
var User = function(user) {this.props = user.props  //参数集合,借鉴react设计思想
};/*获取全部数据,测试接口使用,正式上线时请关闭*/
User.prototype.getUserAllItems = function(callback) {var _sql = "select * from user where u_del=0";helper.db_query({connect: con,sql: _sql,name: 'getUserAllItems',callback: callback})
}module.exports = User
helper.js放什么

其实后端开发过程是用到的工具方法都可以放进去,这里先举例3个常用的(当然有些方法前端也能使用,建议分开存放,方便以后的归并)

/*** @desc 工具模块* @author Jafeney <692270687@qq.com>**/
var crypto = require('crypto');
module.exports = {// 获取本地时间字符串getTimeString: function(date) {return date.getFullYear() + '-' + (date.getMonth() + 1) + '-' +date.getDate() + ' ' + date.getHours() + ':' + date.getMinutes() +':' + date.getSeconds();},// MD5加密getMD5: function(str) {var md5 = crypto.createHash('md5');md5.update(str);return md5.digest('hex');},// 执行sql语句db_query(opt) {opt.connect.query(opt.sql, function(err, res) {if (err) {console.log(`${opt.name} err: + ${err}`);} else {console.log(`${opt.name} success!`);if (typeof(opt.callback) === 'function') {opt.callback(err, res);}}});}
}
user.ctrl.js举例
/*** @desc 用户 控制器* @author Jafeney <692270687@qq.com>**/var User = require('../../database/user.db');module.exports = {// 模块初始化init: function(app) {app.get('/user', this.doGetUserAllItems)},// 获取所有用户信息doGetUserAllItems: function(req, res) {var props = {};  //默认参数为空var user = new User({props: props});user.getUserAllItems(function(err, data) {if (data.length) {return res.send({code: 200,data: data})} else {console.log(err)return res.send({code: 500,message: '出错了'})}})}
}

还是前后端分离吧

做前端的时候,最希望看到的就前后端分离和解耦,好吧入乡随俗,也来体验下后端怎么写restful接口

配置一层单独的路由

为了区分视图路由和API路由,我们给API提供一层单独的路由,在app.js里加这两行:

var api = require('./routes/api');
app.use('/api', api);
api.js长啥样
var express = require('express');
var router = express.Router();
var fs = require('fs');var FS_PATH_SERVICES = './routes/services/';
var REQUIRE_PATH_SERVICES = './services/';router.options('*', function (req, res, next) {next();
});try {var list = fs.readdirSync(FS_PATH_SERVICES);for (var e; list.length && (e = list.shift());) {var service = require(REQUIRE_PATH_SERVICES + e);service.init && service.init(router);}
} catch(e) {console.log(e);
}module.exports = router;

好了,到这里后端算是布置好了,重启node服务,可以测试一下api接口,比如: http://localhost/api/user 去测试用户接口是否正常

配置前端工程

前端,是时候表演真正的技术了。抄上咱们的武器:React 、 React-Router 、 Redux 、 ES2015 、Less、Webpack…,向着硝烟奋起!

React环境搭建

目前国内react和vuex的PK正搞得火热,在我看来同为JS框架,两者的优势其实类似,只要能得心应手地解决实际问题,也无需你死我活。而我React用得比较顺手,这里就以React为例吧。

依赖的node_modules
"dependencies": {"babel-polyfill": "^6.16.0",   "immutable": "^3.8.1",          "isomorphic-fetch": "^2.2.1","react": "^15.4.1","react-dom": "^15.4.1","react-redux": "^4.4.6","react-redux-spinner": "^0.4.0","react-router": "^3.0.0","react-router-redux": "^4.0.7","redux": "^3.6.0","redux-immutablejs": "0.0.8","redux-logger": "^2.7.4","redux-thunk": "^2.1.0",
},
"devDependencies": {"babel-core": "^6.18.2","babel-loader": "^6.2.8","babel-preset-es2015": "^6.18.0","babel-preset-react": "^6.16.0","babel-preset-stage-0": "^6.16.0","css-loader": "^0.26.0","file-loader": "^0.9.0","img-loader": "^1.3.1","less": "^2.7.1","less-loader": "^2.2.3","style-loader": "^0.13.1","url-loader": "^0.5.7","webpack": "^1.13.3"
}

玩玩babel

react使用babel除了安装依赖,.babelrc的配置还有个注意点,为了支持JSX和ES2015的最新提案,presets需要这么写:

{ "presets": ["es2015","react","stage-0"] }

耍耍webpack

webpackgulp要好用不少,下面是这个架构下的webpack.config.js写法:

/*** @desc 项目webpack配置文件* @author Jafeney <692270687@qq.com>**/var webpack = require('webpack');
var path = require('path');
var nodeModulesPath = path.join(__dirname, '/node_modules');module.exports = {entry: {admin: './src/entries/admin',front: './src/entries/front',// 作为外部模块,不打包到webpack的主文件vendor: ['react', 'react-dom', 'redux'],},output: {path: path.join(__dirname, '/public/build'),publicPath: '/assets/',filename: '[name].bundle.js'},module: {noParse: [path.join(nodeModulesPath, '/react/dist/react.min'),path.join(nodeModulesPath, '/react-dom/dist/react-dom.min'),path.join(nodeModulesPath, '/redux/dist/redux.min'),],loaders: [{ test: /\.less$/, loader: 'style!css!less' },{ test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader' },{ test: /\.(gif|jpg|png)$/, loader: 'url?limit=8192&name=images/[name].[hash].[ext]' },{ test: /\.(woff|svg|eot|ttf)$/, loader: 'url?limit=50000&name=fonts/[name].[hash].[ext]' }]},plugins: [new webpack.DefinePlugin({'process.env': {NODE_ENV: JSON.stringify('production')}}),// new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false } }), // 版本上线时开启new webpack.optimize.CommonsChunkPlugin('common.js'),  // 抽取公共部分new webpack.optimize.OccurenceOrderPlugin(),new webpack.NoErrorsPlugin()]
}

注意入口文件有三个:admin、front和vendor。admin是管理后台的入口、front是前台商城的入口、vender则是把react、react-dom、redux这三个大的依赖模块单独抽离成一个文件,这样可以大大减小webpack打包后文件的大小。还有一个技巧是 commonsChunkPlugin() 这个插件,它可以再次抽取输入文件的公共部分,再次减小这三个文件的大小,然后利用浏览器的并行加载能力,稍稍加快整个项目的加载速度。

打包后的模块怎么引?

前面也说到在后端的Views目录里商城主页管理后台对应的模版视图分别是 index.ejsadmin.ejs,而webpack打包好的文件会作为静态资源放在public的build目录下:

商城视图入口 index.ejs(移动端)

<!DOCTYPE html>
<html><head><meta charset="UTF-8"><title id="page_title">你的网站名称</title><meta name="description" content="你的网站名称" /><meta name="keywords" content="商城,福利" /><meta content="yes" name="apple-mobile-web-app-capable" /><meta content="telephone=no" name="format-detection" /><meta content="email=no" name="format-detection" /><meta content="black" name="apple-mobile-web-app-status-bar-style"><link rel="shortcut icon" type="image/x-icon" href="http://xiangke.da56.com/static/img/xiangke.ico" media="screen" /><link href="http://xiangke.da56.com/static/img/xiangke.ico" rel="apple-touch-icon"><link rel="stylesheet" href="http://www.da56.com/static/css/loader.css"><script type="text/javascript">!function(j){function i(){j.rem=m.getBoundingClientRect().width/16,m.style.fontSize=j.rem+"px"}var p,o=j.navigator.appVersion.match(/iphone/gi)?j.devicePixelRatio:1,n=1/o,m=document.documentElement,l=document.createElement("meta");if(j.dpr=o,j.addEventListener("resize",function(){clearTimeout(p),p=setTimeout(i,300)},!1),j.addEventListener("pageshow",function(b){b.persisted&&(clearTimeout(p),p=setTimeout(i,300))},!1),m.setAttribute("data-dpr",o),l.setAttribute("name","viewport"),l.setAttribute("content","initial-scale="+n+", maximum-scale="+n+", minimum-scale="+n+", user-scalable=no"),m.firstElementChild){m.firstElementChild.appendChild(l)}else{var k=document.createElement("div");k.appendChild(l),document.write(k.innerHTML)}i()}(window);</script></head><body><div id="root"><div id="floatBarsG"><div id="floatBarsG_1" class="floatBarsG"></div><div id="floatBarsG_2" class="floatBarsG"></div><div id="floatBarsG_3" class="floatBarsG"></div><div id="floatBarsG_4" class="floatBarsG"></div><div id="floatBarsG_5" class="floatBarsG"></div><div id="floatBarsG_6" class="floatBarsG"></div><div id="floatBarsG_7" class="floatBarsG"></div><div id="floatBarsG_8" class="floatBarsG"></div></div></div><script src="/build/common.js"></script><script src="/build/vendor.bundle.js"></script><script src="/build/front.bundle.js"></script></body>
</html>

这里我简要说明一下,上面的 head 部分把移动端适配(包括rem布局)的工作都做了,有了它,移动端你直接就可以用rem进行布局了,具体怎么玩我下面会介绍。

可能有人对 floatBarsG 这一层有疑问。这其实是为了解决单页应用加载时的白屏做得CSS3加载动画,配合head的loader.css可以有一个不错的加载效果(你可以自己定制一套)。

后台不需要做移动适配,head部分就简单多了:

<!DOCTYPE html>
<html><head><meta charset="UTF-8"><title>管理后台</title><link rel="stylesheet" href="http://www.da56.com/static/css/loader.css"><link rel="shortcut icon" href="http://www.da56.com/src/images/icon.ico" /></head><body><div id="root"><div id="floatBarsG"><div id="floatBarsG_1" class="floatBarsG"></div><div id="floatBarsG_2" class="floatBarsG"></div><div id="floatBarsG_3" class="floatBarsG"></div><div id="floatBarsG_4" class="floatBarsG"></div><div id="floatBarsG_5" class="floatBarsG"></div><div id="floatBarsG_6" class="floatBarsG"></div><div id="floatBarsG_7" class="floatBarsG"></div><div id="floatBarsG_8" class="floatBarsG"></div></div></div><script src="/build/vendor.bundle.js"></script><script src="/build/admin.bundle.js"></script></body>
</html>

我的Redux玩法

redux也不是什么神秘的东西啦,不过相比 flux 确实好用不少,尤其是处理业务逻辑的能力和对store的管理都比较好用。

前端目录结构
|----src/                   /*前端代码尽在此目录下*/    |----components/          /*项目用到的组件*/|----containers/          /*页面容器*/|----admin/             /*管理后台的页面容器*/|----login.js         /*登录页面容器,以这个为例*/|----style.less       /*管理后台样式,统一写在这个less里*/|----front/             /*前台商城的页面容器*/|----basic/           /*基础样式*/|----global.less    /*全局通用样式以及变量*/|----reset.less     /*页面初始化的样式*/|----size.less      /*字体已经rem配置*/|----home.js          /*商城主页容器,以这个为例*/|----style.less       /*前台商城的样式,统一写在这个less里*/|----entries/             /*入口*/|----admin.entry.js     /*后台入口*/|----front.entry.js     /*前台入口*/|----mixins/              /*混入方法*/|----helper.js          /*前端使用的工具方法*/|----pure-render.js     /*加载优化*/|----redux/               /*redux*/|----actions/           /*actions*/|----reducers/          /*reducers*/|----configStore.js     /*store配置*/|----types.js           /*store定义*/|----routes/              /*前端路由*/|----admin.route.js     /*管理后台路由*/|----front.route.js     /*前台商城路由*/|----config.js            /*前端配置文件*/
关于布局

PC端随意些,可以用像素布局。这里说说移动端,正好结合 rem 说说这套布局的玩法:

前文在 head 部分已经给页面的 html标签定义了 data-dprfont-size作为基准单位。 再结合下面这套less版的尺寸方案:

// @desc    提供 750px尺寸的 尺寸 (包括字体大小)的一些常用方法
// 为什么不使用rem 设置字体?
// 参见 https://github.com/imweb/mobile/issues/3
@g-base: 46.875rem;
@g-font-base: 40rem;
.px2px(@name, @px){@{name}: round(@px / 2) * 1px;[data-dpr="2"] & {@{name}: @px * 1px;}// for mx3[data-dpr="2.5"] & {@{name}: round(@px * 2.5 / 2) * 1px;}// for 小米note[data-dpr="2.75"] & {@{name}: round(@px * 2.75 / 2) * 1px;}[data-dpr="3"] & {@{name}: round(@px / 2 * 3) * 1px}// for 三星note4[data-dpr="4"] & {@{name}: @px * 2px;}
}
.px2rem(@name, @px) {@{name}: (@px / 46.875) * 1rem;
}
//margin,padding, border可以使用这个设置两个值
.mpb(@name, @px, @py) {@{name}: (@px / 46.875) * 1rem (@py / 46.875) * 1rem;
}
.fontSize(@px) {.px2px(font-size, @px);
}.size(@thesize) {width: @thesize;height: @thesize;
}.size(@width, @height) {width: @width;height: @height;
}

大家知道UI给出的移动端设计稿一般是 2x 规格的,以 Iphone6的375宽度为例,设计给出的一般是750,那么我们在用rem布局时,宽度就是:

   750rem/@g-base

并且它会自动适配Iphone各个尺寸和常用的Android屏幕,省时省心。

React-Router怎么玩

React-Router也不神秘,其实就是前端路由的一层封装,配置也很简单。这里因为结合redux来使用,所以稍稍有点不同,拿前台商城为例吧:

front.entry.js

/*** @desc 商城入口* @author Jafeney <692270687@qq.com>**/
import React from 'react'
import { render } from 'react-dom'
// redux
import { Provider } from 'react-redux'
// router
import { Router, hashHistory } from 'react-router'
import { syncHistoryWithStore } from 'react-router-redux'
import routes from '../routes/front'
import configureStore from '../redux/configureStore'const store = configureStore(hashHistory)
const history = syncHistoryWithStore(hashHistory, store)render((<Provider store={store}><Router history={history} routes={routes} /></Provider>), document.getElementById('root')
)

front.route.js

/*** @desc 项目路由设置* @author Jafeney <692270687@qq.com>**/import React from 'react'
import { Route } from 'react-router'import Door from '../containers/front/door'
import Home from '../containers/front/home'const routes = (<Route><Route path="/" component={Door} /><Route path="/home" component={Home} /></Route>
);export default routes
Immutable管理你的reducers

Immutable之前也有单独介绍过,可以提高对象的取值效率,这里主要是和 reducer 结合使用,举个例子:

/*** @desc 轮播 reducer**/import Immutable from 'immutable';
import * as TYPES from '../types'
import { createReducer } from 'redux-immutablejs'export const carousel = createReducer(Immutable.fromJS({preload: false}), {[TYPES.CAROUSEL_UPDATE]: (state, action) => {return state.set('preload', true).merge(Immutable.fromJS(action.result))},[TYPES.CAROUSEL_CLEAN]: (state, action) => {return state.clear().set('preload', false)}
})

然后我们在页面里可以用 .get('@name') 来获取对象的属性。

注意:如果Immutable对象是个List,必须先map()一下,然后再用get()方法取值。

有个得心应手的组件库

React搞得快一年了,前段时间也自己写了个组件库 Royal,不过一直疲于新业务开发,没有很好地整理文档和维护,挺可惜的,不过我开发新项目还是把Royal运用起来,对于有问题的组件进行修改和优化。唉,也是力不从心,期待有人能帮我打理打理吧 ^o^。在此推荐几个时尚的组件库吧:

Antd

蚂蚁金服开发一个比较全面的React组件库,我以前也推荐过,确实蛮不错,唯一的痛点应该是它的源码,学习起来比较费劲。
文档地址: https://ant.design/docs/react/introduce

Material-UI

UI设计比较酷炫的一款React组件库, 官网地址: http://www.material-ui.com/

Grommet

扁平风格的React组件库,官网地址: https://grommet.github.io/

用git进行托管

三方托管代码是个好习惯,有效防止代码丢失或者出错后回滚。


/*Git 全局设置*/$ git config --global user.name "Jafeney"
$ git config --global user.email "692270687@qq.com"/*创建新版本库*/$ git clone git@code.aliyun.com:b2b/test.git
$ cd test
$ touch README.md
$ git add README.md
$ git commit -m "add README"
$ git push -u origin master/*已存在的文件夹或 Git 仓库*/$ cd existing_folder
$ git init
$ git remote add origin git@code.aliyun.com:b2b/test.git
$ git add .
$ git commit
$ git push -u origin master

添加.gitignore 阻止node_modules或编译后的文件等进入版本库

node_modules
.DS_Store
build

实例项目github地址: https://github.com/Jafeney/tms (代码仅供参考,切勿商用)


@欢迎关注我的 github 和 个人博客 -Jafeney

轮子篇:基于Node和React的全栈式架构相关推荐

  1. Node.js+Vue.js全栈开发王者荣耀手机端官网和管理后台(一)

    文章目录 [全栈之巅]Node.js+Vue.js全栈开发王者荣耀手机端官网和管理后台(一) 工具安装和环境搭建 初始化项目 基于ElementUI的后台管理基础界面搭建 创建分类(客户端) 创建分类 ...

  2. 基于NodeJS的全栈式开发(基于NodeJS的前后端分离)【转】

    随着不同终端(Pad/Mobile/PC)的兴起,对开发人员的要求越来越高,纯浏览器端的响应式已经不能满足用户体验的高要求,我们往往需要针对不同的终端开发定制的版本.为了提升开发效率,前后端分离的需求 ...

  3. Node.js + Vue.js 全栈开发王者荣耀手机端官网和管理后台

    前言 最近在跟着Johnny的全栈之巅系列视频教程学习使用NodeJS+Express+Element-UI+MongoDB等开发王者荣耀,服务端server,移动端web,admin,学到了不少东西 ...

  4. 小程序 | 基于WAMP的新闻网小程序开发(体验全栈式开发微信小程序)

    之前学习微信小程序开发,主要是基于JS.WXML.WXSS的前端开发,对于后端技术不精的我也是使用了微信开发者工具中的云开发功能,但是今天突发奇想,特别想体验一下全栈式开发微信小程序,学习了一下基于W ...

  5. 【全栈之巅】Node.js + Vue.js 全栈开发王者荣耀手机端官网和管理后台学习笔记(4.1-4.10)

    [全栈之巅]Node.js + Vue.js 全栈开发王者荣耀手机端官网和管理后台学习笔记(4.1-4.10) 本项目是 学习Bilibili 全栈之巅 视频教程相关源码和体会 https://git ...

  6. 【全栈之巅】Node.js + Vue.js 全栈开发王者荣耀手机端官网和管理后台学习笔记(3.11-3.12)

    [全栈之巅]Node.js + Vue.js 全栈开发王者荣耀手机端官网和管理后台学习笔记(3.11-3.12) 本项目是 学习Bilibili 全栈之巅 视频教程相关源码和体会 https://gi ...

  7. 学习【全栈之巅】Node.js + Vue.js 全栈开发王者荣耀手机端官网和管理后台笔记(2.10-2.12)

    [全栈之巅]Node.js + Vue.js 全栈开发王者荣耀手机端官网和管理后台 本项目是 学习Bilibili 全栈之巅 视频教程相关源码和体会 https://gitee.com/blaunic ...

  8. 学习【全栈之巅】Node.js + Vue.js 全栈开发王者荣耀手机端官网和管理后台笔记(2.8-2.9)

    [全栈之巅]Node.js + Vue.js 全栈开发王者荣耀手机端官网和管理后台 本项目是 学习Bilibili 全栈之巅 视频教程相关源码和体会 https://gitee.com/blaunic ...

  9. 学习【全栈之巅】Node.js + Vue.js 全栈开发王者荣耀手机端官网和管理后台笔记(1.1-2.5)

    [全栈之巅]Node.js + Vue.js 全栈开发王者荣耀手机端官网和管理后台 本项目是 学习Bilibili 全栈之巅 视频教程相关源码和体会 https://gitee.com/blaunic ...

  10. 【全栈之巅】Node.js + Vue.js 全栈开发王者荣耀手机端官网和管理后台学习笔记(3.6-3.10)

    [全栈之巅]Node.js + Vue.js 全栈开发王者荣耀手机端官网和管理后台学习笔记(3.6-3.10) 本项目是 学习Bilibili 全栈之巅 视频教程相关源码和体会 https://git ...

最新文章

  1. JSP由浅入深(1)—— 熟悉JSP服务器
  2. Unity游戏中的一些规范和优化建议
  3. php方法 隐藏手机号中间四位
  4. eclipse中git分支创建与合并(-)
  5. 获取指定文件目录路径下的所有文件
  6. deep-text-recognition-benchmark 项目训练data.mdb数据集,运行日志中,只显示训练了英文和数字
  7. ps怎么加底部阴影_ps影子(ps物体底部阴影怎么做)
  8. Andriod 获取手机CPU型号设备信息
  9. Ocelot.Authorization.Middleware.AuthorizationMiddleware[0] requestId: 0HMJ300E5APNA:00000002...
  10. Jsd2205面试题
  11. 最长上升子序列(LIS)问题的解决及优化
  12. 使用CVX进行Matlab仿真时出现的一些问题
  13. windows 2008/2012(64位) IIS配置asp程序 500 - 内部服务器错误。您查找的资源存在问题,因而无法显示。
  14. Android 软件升级
  15. 服务端监控工具:Nmon使用方法
  16. 讲好元宇宙故事 保利威开启MR直播新时代
  17. mini2440 安装OpenWrt 过程记录
  18. CNAS实验室运作和认可
  19. 如何靠网络快速打造品牌
  20. 泰国推进数字货币征税法案,菲律宾对数字货币宽容 | 区块链日报

热门文章

  1. 大学中的3大阴暗面, 一般人根本想象不到
  2. 华谊去电影化:用互联网思维跨界跨业态布局
  3. The Answer
  4. 攻防世界 Misc hong
  5. java咖啡口感_JAVA亮相上合峰会 与世界共享属于中国的咖啡味道
  6. python输出含省略号
  7. 拿下美团校招:MySQL InnoDB非聚簇索引知识点解析!
  8. 网关、网桥、路由器、集线器
  9. console与tty,uart的关系
  10. html 简单实现标签页,用html网页代码简单实现一个标签小工具