从第一代码农写下第一行代码开始到上个世纪的80年代的软件危机,码农一直在考虑一个问题,怎么让代码写起来更容易、更简单、更舒适?抛开大牛、大神(大牛、大神哪那么容易找到啊 _…)级别的人员,而且大厂工作,讲究的是一个协同合作开发,代码不是你想怎么写就怎么写的,自然而然就需要形成一套统一的协同方案及规范开发,这样的好处:一是方便管理(如成员变动),二是减少运维成本,三是提高开发质量,四是起到一个学习共勉,五是作为学习开发的参考
正所谓:“新人入团队,什么都不会” 的思想主导着老员工的心态,这种心态是可以理解的,这里没有贬低新人啊,初入团队的成员多少都有几天空白期,多少需要几天去适应,不管是初出 “校庐”,还是进军市场几年的老战士,几乎所有的码农在写代码的时候,都是以自我为思想的,什么都是按着自己的标准,这样的方式故能顺利完成业务的开发,但不利于整个团队开发,其他成员对其设计思想并不了解,在此声明一下,本文并没有贬低任何人员,只是基于个人工作经历,发现的一些开发弊端,给予抒出而已,提供处理这些弊端的一种方式之一,也用于自己在后续开发工作中做个比较
团队协作是一件非常严谨的工作,也是一个技术团队发展壮大的基石,当然也是最难的,可持久发展的,不是一蹴而就能形成的,成员之间的完美协作最起码需要半年到一年的工作沉淀(企业对开发者工作贡献能力的尺量),对于代码组织者就需要对这样的工作量化,提供一套技术方案思想,适用于企业开发前端主管职务人员参考
新人入团队,什么都不会
此处并不是贬低之意,实在是新人的进入多少有个适用团队的周期,进入新公司,融入新团队,就应当要去适应新团队的开发模式,不可能让所有人去适应你吧 ^_^...
市场上,80%~90%的码农写的代码都是 Cow Code(牛仔代码),尤以外包公司出身的码农,其特别的 CV 法则(Ctrl+C、Ctrl+V),从来不讲究代码风格,甚至有的时候,一个月前我写的代码,一个月后回过头来看(脑补:WC,这那个 SB 写的代码),只有上帝知道他写的是什么,这个问题在前端领域最为突出:
很早就有人来想办法解决这个问题,在软代时代就已经有解决这个问题的法宝 ——— 组件化,当然那时候不是那么叫的,是通过两个原则来规范这个问题的,这两个原则就是:内聚性和耦合性
意思就是:哥,我想按时回家哄妹子!!!你怎么写代码我不管,你的功能全在这你这儿实现(内聚性),不要让我还帮写你那块功能;另外,哥,求你了,你代码不要 block (影响)我的代码(低耦合性)
既然解决问题的思路在这儿,前端大牛一代代前赴后继的在这条路上狂奔下去,这也是前端组件化开发的前因,至于何为组件化,观字译意,就是将整体划分,按一块一块封装,组件就是一个个独立携带功能单元块,当然,在 react 中组件化就比较宏观啦,因为 react 的思想是万物皆组件,任何事物都可以看成是一个组件,组件化的好处:解耦,平台化,结构单一,复用性,编译集成
高内聚,低耦合
高内聚低耦合是软件工程中的一种概念,是判断软件设计好坏的标准,主要用于程序的面向对象而设计的,观察其内聚性是否过高,耦合度是否过低,目的是使程序模块的可重用性、移植性大大增强
高内聚:尽可能让每个组件内部完成单一事件;低耦合:减少组件内部调用另外一个组件的事件,高内聚低耦合可以使得项目可拓展性、可移植性在技术层面上能够更加的灵活,最大化重用组件,减少开发者 UI 层的开发工作,集中精力完成业务逻辑的设计及实现
前端的组件是前端页面的一部分,由 HTML、CSS、JavaScript 三种编程语言组合而成,相对于面向对象思想 OOP 中的对象对比,前端组件的语义要素会更丰富一点,前端组件中的要素有:属性 Properties、状态 State、方法 Methods、继承 Inherit、特性 Attribute、配置 Config、事件 Event、生命周期 Leftcycle、子组件 Children
Properties
和 Attribute
在英文翻译都是 “属性” 的意思,在前端组件中,Properties
是组件具备的属性,而 Attribute
通常用于 HTML DOM
的属性;State
可以看成 JavaScript
声明的变量,与 Properties
不同的是,变量是可以进行赋值与计算的,而属性是不变的,可以将 Properties
看成 JavaScript
声明的常量
Config
配置也可以看成 JavaScript
构造函数的参数,是组件中一个一次性生效的数据,不可以被更改
标签设置 | 代码设置 | 代码改变 | 用户输入改变 | 是否支持 |
---|---|---|---|---|
N | Y | Y | property | |
Y | Y | Y | attribute | |
N | N | N | Y | state |
N | Y | N | N | config |
Methods
和 Event
同样是方法动作,在前端组件中,Methods
可以看成 JavaScript
声明的方法,而 Event
是 HTML DOM
节点事件,Leftcycle
是用于观测组件引用过程中的状态:创建 create、挂载 mount、数据更新 update、销毁 destroy 时进行的回调事件
Children
是组件的内容部分,通常也被看成是组件,结合 Inherit
可以继承父组件的属性 Properties
和方法 Methods
组合键 Window + R 输入 cmd
回车,选择非 C 盘
下找到一个项目目录,例:E:\wwwroot
目录下执行如下命令,通过 react-cli
脚手架新建项目,并同步加载好相关依赖,最后运行项目:
E:\wwwroot>npx create-react-app react-demo
E:\wwwroot>cd react-demo
E:\wwwroot>react-demo>npm start
当你看到运行如下效果,则表示项目运行成功啦,浏览器自动运行:http://localhost:3000 就可以打开 react-demo
|-- react-demo
|-- node_modules react 项目所需要的相关依赖包
|-- public react 项目入口资源文件
|-- src react 项目入口源码文件
|-- .eslintcache react 语法规则配置缓存
|-- .gitignore git 忽略文件配置项
|-- package-lock.json react 所有依赖指引锁定配置
|-- package.json react 所有依赖指引配置
|-- README.md react 项目文档
通过编辑器(推荐 VS Code)打开项目,先调整项目 src 目录内容,删除 App.css、index.css、App.js、App.test.js、logo.svg、reportWebVitals.js、setupTests.js,并改造 index.js 内代码如下:
import React from 'react';
import ReactDOM from 'react-dom';
ReactDOM.render(
Hello React!
,
document.getElementById('root')
);
由于 create-react-app
创建的 react
应用是将 webpack
的配置项隐藏的,可以通过 npm run eject
命令将所有内建的配置暴露出来,选择 y (yes) 继续,如果报出 npm
关联错误,记得到项目的根目录,删除项目中的 .git 隐藏文件夹,配置暴露后的项目结构:
|-- react-demo
|-- config react 项目 webpack 相关配置文件
|-- node_modules react 项目所需要的相关依赖包
|-- public react 项目入口资源文件
|-- scripts react 项目启动脚本文件
|-- src react 项目入口源码文件
|-- .eslintcache react 语法规则配置缓存
|-- .gitignore git 忽略文件配置项
|-- package-lock.json react 所有依赖指引锁定配置
|-- package.json react 所有依赖指引配置
|-- README.md react 项目文档
集成 Antd 组件库
通过指令 npm install antd --save
安装 Antd
组件库,并在根入口引入 Antd
样式表,在 App.js
内引用 Antd
相关组件,并运行
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import 'antd/dist/antd.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
ReactDOM.render(
, document.getElementById('root')
);
reportWebVitals();
import { Button } from 'antd';
import './App.css';
function App() {
return (
);
}
export default App;
PS:在使用 antd
组件库之前请落实产品交互,因为 antd
目前的两个版本:3.x 和 4.x 有些功能迭代并不同步,想好再选择
自定义主题色
根据文档说明,用户自定义 antd
主题色需要引用 craco
和 craco-less
两个插件,基于 less-loader
的 modifyVars
来进行主题配置,变量和其他配置方式可以参考如下:
@primary-color: #1890ff; // 全局主色
@link-color: #1890ff; // 链接色
@success-color: #52c41a; // 成功色
@warning-color: #faad14; // 警告色
@error-color: #f5222d; // 错误色
@font-size-base: 14px; // 主字号
@heading-color: rgba(0, 0, 0, 0.85); // 标题色
@text-color: rgba(0, 0, 0, 0.65); // 主文本色
@text-color-secondary: rgba(0, 0, 0, 0.45); // 次文本色
@disabled-color: rgba(0, 0, 0, 0.25); // 失效色
@border-radius-base: 2px; // 组件/浮层圆角
@border-color-base: #d9d9d9; // 边框色
@box-shadow-base: 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 9px 28px 8px rgba(0, 0, 0, 0.05); // 浮层阴影
虽然 antd
提供了一套非常优秀的主题色,但往往不敬人意的时候,企业往往想的是自个独有的色调,这就需要开发者们对项目框架进行主题配置,前提还需要将引用 antd.css
方式调整为 antd.less
,调整项目 src
目录:
E:\wwwroot>react-demo>npm install @craco/craco --save-dev
E:\wwwroot>react-demo>npm install craco-less --save-dev
|-- src
|-- App.js // 调整引用相对应的 Less 文件
|-- App.less
|-- index.js // 调整引用相对应的 Less 文件
|-- index.less
|-- reportWebVitals.js
import React from 'react';
import ReactDOM from 'react-dom';
import 'antd/dist/antd.less';
import Root from './root';
import reportWebVitals from './reportWebVitals';
import './index.less';
ReactDOM.render( , document.getElementById('root'));
reportWebVitals();
import { Button } from 'antd';
import './App.less';
function App() {
return (
);
}
export default App;
在项目根目录下新建 craco.config.js
配置文件,修改项目配置文件 package.json
const CracoLessPlugin = require('craco-less');
module.exports = {
plugins: [
{
plugin: CracoLessPlugin,
options: {
lessLoaderOptions: {
lessOptions: {
modifyVars: {
'@primary-color': '#1DA57A' // 这里修改了主题色
},
javascriptEnabled: true,
},
},
},
},
],
};
{
"name": "react-demo",
......
"scripts": {
"start": "craco start",
"build": "craco build",
"test": "craco test",
"eject": "craco eject"
},
......
"devDependencies": {
"@craco/craco": "^6.4.3",
"craco-less": "^2.0.0"
}
}
PS:craco-less
非常方便的集成 less
,当然也可以通过 npm run eject
暴露的 webpack
配置修改
集成路由、状态管理
E:\wwwroot>npm install react-router-dom@5.x --save
E:\wwwroot>npm install redux --save
E:\wwwroot>npm install --save react-redux
E:\wwwroot>npm install redux-saga --save
PS:需要说明的是 react-router-dom
从 5.x 升级到 6.x+ 后,Switch 重命名为 Routes,component/render 被 element 替代
调整项目 src
结构,优化文件管理,规范化命名,便于项目管理:
|-- src
|-- assets # 存放项目相关静态资源,如:图片
|-- components # 存放项目通用组件
|-- mock # 暴露出项目所需的模拟数据
|-- pages # 项目的主页面:布局、404、登陆
|-- IndexPage # 项目的布局页面,通常引用 antd 的布局组件
|-- InvalPage # 项目的意外页面,不匹配相关路由的展示页
|-- LoginPage # 项目的登陆页面,有些项目登陆放在布局头部
|-- panes # 存放项目布局用到的版面组件
|-- redux # 项目状态管理目录:管理项目的整体状态
|-- utils # 项目工具集
|-- index.js # 项目主要入口文件
|-- index.less # 项目全局样式表
|-- reportWebVitals.js # web-vitals的库
|-- root.js # 项目容器组件文件
|-- route.js # 项目的路由配置
分别在 IndexPage
、InvalPage
和 LoginPage
下创建对应的 index.js
和 index.less
文件,编辑内容如下:
import './index.less';
function IndexPage() {
return (布局组件)
}
export default IndexPage;
import './index.less';
function InvalPage() {
return (404组件)
}
export default InvalPage;
import './index.less';
function LoginPage() {
return (登录组件)
}
export default LoginPage;
编辑 root.js
路由根文件,引入相关页面,基于 react-router-dom
路由对象进行路由配置
import { BrowserRouter as Router, Route } from 'react-router-dom';
import IndexPage from './pages/IndexPage';
import InvalPage from './pages/InvalPage';
import LoginPage from './pages/LoginPage';
function Root() {
return (
);
}
export default Root;
这个时候,可以通过 http://localhost:3000/ 和 http://localhost:3000/login 分别访问到首页和登录页面啦,匹配不到路由的展示 404
在项目 src/redux
下创建:store.js
、reducer.js
、saga.js
编辑如下,并调整路由根组件 root.js
import { createStore, applyMiddleware } from 'redux';
import createSagaMiddleware from 'redux-saga';
import reducer from './reducer';
export default function configureStore(state) {
const sagaMiddleware = createSagaMiddleware();
const createStoreWithMiddleware = applyMiddleware(sagaMiddleware)(createStore);
return {
...createStoreWithMiddleware(reducer, state),
runSaga: sagaMiddleware.run
};
}
import { combineReducers } from 'redux';
export default combineReducers({
});
import { all } from 'redux-saga/effects';
export default function* rootSaga() {
yield all([
]);
}
import React from 'react';
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import { Provider } from 'react-redux';
import Store from './redux/store';
import Sagas from './redux/saga';
import IndexPage from './pages/IndexPage';
import InvalPage from './pages/InvalPage';
import LoginPage from './pages/LoginPage';
const store = Store();
store.runSaga(Sagas);
function Root() {
return (
);
}
export default Root;
完善项目的登陆页面 src/pages/LoginPage
,创建:index.less
样式、state.js
状态、action.js
执行、reducer.js
迭代、saga.js
监听、server.js
服务、handle.js
交互
引用 antd
表单组件创建一个登陆表单,编辑项目 src/pages/LoginPage/index.js
如下:
import { Form, Input, Button } from 'antd';
import { connect } from 'react-redux';
import { ConfigProvider } from 'antd';
import zhCN from 'antd/lib/locale/zh_CN';
import './index.less';
import * as handle from './handle';
function LoginPage(props) {
const [formData] = Form.useForm();
const handPush = values => {
handle.handPush(props, values, result => {
console.log(result);
});
};
return (
{ offset: 8, span: 16 }}>
)
}
export default connect(({ login }) => ({
}))(LoginPage);;
表单提交的时候,需要执行一个事件 handPush
,所以需要在 handle.js
声明这个事件,集中处理,逻辑清晰
import { FETCH_LOGIN_PUSH } from './action';
// 监听页面点击事件,去找到需要执行的方法,返回执行结果
export function handPush(props, data, callback) { props.dispatch({ type: FETCH_LOGIN_PUSH, payload: data, callback }) }
在调用事件执行方法时,声明执行体 action.js
,声明 ATION_ALL_TYPE
用于执行更新状态
export const FETCH_LOGIN_PUSH = 'FETCH_LOGIN_PUSH'; // 监听初始化用户登陆请求行为
export const ATION_ALL_TYPE = {
};
通过迭代来更新状态,声明迭代体 reducer.js
,映射到 saga.js
监听,在状态管理 src/redux
中需要引用
import { ATION_ALL_TYPE } from './action';
import initState from './state';
export default (state = initState, action) => {
if (Object.prototype.toString.call(ATION_ALL_TYPE[action.type]) === '[object Function]') {
return ATION_ALL_TYPE[action.type](state, action.payload);
}
return state;
};
import { combineReducers } from 'redux';
import login from '../pages/LoginPage/reducer';
export default combineReducers({
login,
});
声明监听器 saga.js
,调用后端服务接口,回调返回登陆结果,在状态管理 src/redux
中需要引用
import { take, fork, call, put } from 'redux-saga/effects';
import { FETCH_LOGIN_PUSH } from './action';
import { sendLogin } from './server';
import { message } from 'antd';
// 监听页面用户表单提交,执行用户登陆接口,返回登陆结果
function* fetchLoginPush() {
while (true) {
const { payload, callback } = yield take(FETCH_LOGIN_PUSH);
const response = yield call(sendLogin, payload);
response.code === 200 && callback && callback(response);
response.code !== 200 && message.error(response.msg);
}
}
export default [
fork(fetchLoginPush),
];
import { all } from 'redux-saga/effects';
import WatchLoginModal from '../pages/LoginPage/saga';
export default function* rootSaga() {
yield all([
...WatchLoginModal,
]);
}
声明用户登陆接口服务 server.js
,引用集成封装请求方法和配置常量
import { request } from '../../utils/request';
import { API } from '../../utils/constant';
/**
* 异步调用后端声明接口:表单提交执行用户登陆
* @returns
*/
export async function sendLogin(params) {
return await request(`${API}/user/login`, { method: 'POST', dataType: 'json', params });
}
分别在项目的工具集 src/utils
下创建 request.js
及 constant.js
和 lodash.js
、storage.js
import { notification } from 'antd';
import { queryStringify } from './lodash';
import { ReadUseToken } from './storage';
// 声明一个请求错误机制,用于请求异常通知提醒
const codeMessage = {
200: '服务器成功返回请求的数据。',
201: '新建或修改数据成功。',
202: '一个请求已经进入后台排队(异步任务)。',
204: '删除数据成功。',
400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。',
401: '用户没有权限(令牌、用户名、密码错误)。',
403: '用户得到授权,但是访问是被禁止的。',
404: '发出的请求针对的是不存在的记录,服务器没有进行操作。',
406: '请求的格式不可得。',
410: '请求的资源被永久删除,且不会再得到的。',
422: '当创建一个对象时,发生一个验证错误。',
500: '服务器发生错误,请检查服务器。',
502: '网关错误。',
503: '服务不可用,服务器暂时过载或维护。',
504: '网关超时。',
};
/**
* 请求异常,服务器或网关异常导致的异常,进行异常通知
* @param {*} error fetch 请求异常错误
*/
function errorHandler(error) {
const { response } = error;
// 根据异常错误码匹配异常错误信息,并在客户端进行通知操作
if (response && response.status) {
const errorText = codeMessage[response.status] || response.statusText;
const { status, url } = response;
notification.error({
message: `请求错误 ${status}: ${url}`,
description: errorText,
});
} else if (!response) {
notification.error({
description: '您的网络发生异常,无法连接服务器',
message: '网络异常',
});
}
return response;
}
/**
* 请求成功,返回请求结果,否则抛出请求错误异常
* @param {*} response fetch 请求返回结果
* @return {*} 返回 fetch 请求结果
*/
function checkStatus(response) {
if (response?.status >= 200 && response?.status < 300) {
return response;
}
const error = new Error(response.statusText);
error.response = response;
throw error;
}
// 用于处理 fetch 请求返回结果进行 json 格式化操作
async function parseJSON(response) {
const data = await response.json();
return { data, headers: response.headers };
}
// 用于处理 fetch 请求返回结果进行 blob 格式化操作
async function parseBlob(response) {
const data = await response.blob();
return { data, headers: response.headers };
}
/**
* 通过请求接口 API,进行数据请求操作,通过 Promise 异步方式返回请求结果
* @param {String} url fetch 请求接口 API 地址
* @param {Object} options fetch 请求参数:请求方式 method、请求头 headers 等
* @returns {Object} 返回 fetch 请求结果:data | err
*/
export function request(url, options) {
url = options.method.toLocaleLowerCase() === 'get' && options.params ? url + `?${queryStringify(options.params)}` : url;
let headers = !options.body ? { 'Content-type': `application/${options.dataType || 'x-www-form-urlencoded'}; charset=UTF-8`, } : {};
options.headers = headers;
if (!options.isVire) {
let { token, validtime } = ReadUseToken(), nowtime = new Date().getTime();
// 如果 token 本地存在,进行时间对比,若效果当前时间表示 token 已经过期啦
if (validtime && validtime < nowtime) {
}
headers['token'] = token || 'Basic dGVzdF9jbGllbnQ6dGVzdF9zZWNyZXQ=';
}
if (!options.body) {
// POST 请求,需要判断传入类型是否为 JSON 格式,否侧通过 querystring 将请求 body 数据转化为字符串格式
if (options.method.toLocaleLowerCase() === 'post' || options.method.toLocaleLowerCase() === 'put' || options.method.toLocaleLowerCase() === 'delete' || options.method.toLocaleLowerCase() === 'patch') {
let body = options.params ? options.params : {};
body = options.dataType === 'json' ? JSON.stringify(body) : queryStringify(body);
options.body = body;
}
}
return fetch(url, options)
.then(checkStatus)
.then(parseJSON)
.then(({ data }) => data)
.catch(err => errorHandler(err));
}
/**
* 通过请求接口 API,进行数据请求操作,通过 Promise 异步方式返回请求结果
* @param {String} url fetch 请求接口 API 地址
* @param {Object} options fetch 请求参数:请求方式 method、请求头 headers 等
* @returns {Object} 返回 fetch 请求结果:data | err
*/
export function download(url, options) {
url = options.method.toLocaleLowerCase() === 'get' && options.params ? url + `?${queryStringify(options.params)}` : url;
let headers = {};
options.headers = headers;
if (!options.isVire) {
let { token, validtime } = ReadUseToken(), nowtime = new Date().getTime();
// 如果 token 本地存在,进行时间对比,若效果当前时间表示 token 已经过期啦
if (validtime && validtime < nowtime) {
}
headers['token'] = token || 'Basic dGVzdF9jbGllbnQ6dGVzdF9zZWNyZXQ=';
}
if (!options.body) {
// POST 请求,需要判断传入类型是否为 JSON 格式,否侧通过 querystring 将请求 body 数据转化为字符串格式
if (options.method.toLocaleLowerCase() === 'post' || options.method.toLocaleLowerCase() === 'put' || options.method.toLocaleLowerCase() === 'delete' || options.method.toLocaleLowerCase() === 'patch') {
let body = options.params ? options.params : {};
body = options.dataType === 'json' ? JSON.stringify(body) : queryStringify(body);
options.body = body;
}
}
return fetch(url, options)
.then(checkStatus)
.then(parseBlob)
.then(data => data)
.catch(err => errorHandler(err));
}
export const APP_LINE = 0; // 1 表示线上环境,0 表示测试环境
export const API = APP_LINE === 0 ? '/api-dev' : '/api-pod';
/**
* 通过跌倒对象 KEY 值,将 JSON 对象参数转化为地址来 GET 请求参数方式
* @param {Object} data
* @returns {String} 返回请求参数格式的字符串
*/
export function queryStringify(data) {
return Object.keys(data).map(function (key) {
return ''.concat(encodeURIComponent(key), '=').concat(encodeURIComponent(data[key]));
}).join('&');
}
/**
* 对日期进行计算,精确到秒
* @param {Date} datetime 被计算日期
* @param {Number} second 计算值:正数表示之后,负数表示之前
* @returns {Date} 返回日期计算后的日期对象
*/
export function datatimeCount(datetime, second) {
return new Date(datetime.getTime() + second * 1000);
}
import { datatimeCount } from './lodash';
/**
* 登录成功后,将用户登录 Token 存储到本地缓存中
* @param {String} token
*/
export function SaveUseToken(token) {
let datetime = new Date(), second = 24 * 60 * 60;
let validtime = datatimeCount(datetime, second).getTime();
sessionStorage.setItem('use_token', JSON.stringify({ token, validtime }));
}
/**
* 获取登录成功后的 Token 信息
* @returns
*/
export function ReadUseToken() {
let itemdata = sessionStorage.getItem('use_token');
if (!itemdata) return { token: '', validtime: '' };
let { token, validtime } = JSON.parse(itemdata);;
return { token, validtime };
}
至此,还缺少一个后台登录接口,这里通过 node.js
编写一个用户登录接口,组合键 Window + R 输入 cmd
回车,就在上述项目同一个磁盘目录下 E:\wwwroot
,执行如下,创建一个 Node 服务:
E:\wwwroot>mkdir interface # 创建一个接口目录
E:\wwwroot>cd interface # 进入到这个目录
E:\wwwroot\interface>npm init -y # 初始化一个服务
E:\wwwroot\interface>cd.>index.js # 创建服务入口文件
# 一下这一步,可以通过任意编辑器打开我的电脑,进入 E:\wwwroot 盘下的接口目录的 index.js 文件
E:\wwwroot\interface>code . # 使用 VS Code 打开当前项目
E:\wwwroot\interface>npm install express
E:\wwwroot\interface>npm install body-parser
E:\wwwroot\interface>npm install jsonwebtoken
E:\wwwroot\interface>node index.js # 编辑如下后执行运行后端
并编辑后端接口主程序文件 index.js
,内容如下,这里创建两个接口,第一个用于查看服务是否启动成功
const express = require('express');
const bodyparser = require('body-parser');
const app = express();
const jwt = require('jsonwebtoken');
app.use(bodyparser.urlencoded({ extended: false }));
app.use(function(req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'PUT, GET, POST, DELETE, OPTIONS');
res.header('Access-Control-Allow-Headers', 'X-Requested-With');
res.header('Access-Control-Allow-Headers', 'Content-Type');
next();
});
app.get('/', (req, res) => { res.send({ code: 0, msg: '登录成功' }); });
app.post('/user/login', (req, res) => {
let { username, password } = req.body;
try {
let token = jwt.sign({ username, password }, 'WOAINI', { expiresIn: 60 * 60 * 2 });
res.send({ code: 200, msg: '登录成功', token: token });
} catch (e) {
console.log(e)
}
});
app.listen(3300, () => { console.log('server starting sucess, address: http://127.0.0.1:3300'); });
执行成功后,在浏览器中输入:http://127.0.0.1:3300,如果打印 {"code":0,"msg":"登录成功"}
表示成功
回到前台 http://localhost:3000,此时的前台是无法调用后端接口的,那是因为存在跨域问题
通过插件配置跨域问题,项目执行 npm install http-proxy-middleware --save
,并在项目的 src
根目录下创建 setupProxy.js
,配置信息如下:
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function (app) {
app.use(
createProxyMiddleware('/api-dev', {
target: 'http://127.0.0.1:3300',
changeOrigin: true,
pathRewrite: {
'^/api-dev': ''
}
})
)
}
回到前端 http://localhost:3000/login,按 F12 键,输入任意账号密码点击登录,查看浏览器控制台打印信息如下,表示请求成功:
{code: 200, msg: '登录成功', token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2N…cwNH0.dkKIAdGiQuEDaxBnIPHuCFyG5bCqAJRBN4ARI7MuV00'}
通常将 src/pages/IndexPage
组件作为一个容器组件,也是一个布局 layout
组件,布局采用中台布局,调整路由配置,左侧菜单路由就是布局组件的子路由,右侧内容映射子路由匹配组件,调整布局组件路由配置 root.js
import React from 'react';
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import { Provider } from 'react-redux';
import Store from './redux/store';
import Sagas from './redux/saga';
import IndexPage from './pages/IndexPage';
import InvalPage from './pages/InvalPage';
import LoginPage from './pages/LoginPage';
import menuRoute from './route';
const store = Store();
store.runSaga(Sagas);
function Root() {
return (
{
menuRoute.length > 0 ? menuRoute.map(item => (
)) : null
}
);
}
export default Root;
通过 route.js
来管理中台路由映射相应模板文件,如下例内容:
import WhomePane from './panes/WhomePane';
import CasesPane from './panes/CasesPane';
import CasdbPane from './panes/CasdbPane';
import EnshePane from './panes/EnshePane';
import HisrcPane from './panes/HisrcPane';
import QuickPane from './panes/QuickPane';
......
export default [
{ href: '/', name: '首页', component: WhomePane },
{ href: '/cases', name: '合作案例列表', component: CasesPane },
{ href: '/cases/:id', name: '合作案例详情', component: CasdbPane },
{ href: '/workbench/collect', name: '我的收藏', component: EnshePane },
{ href: '/workbench/chronicle', name: '选号历史记录', component: HisrcPane },
{ href: '/redbook/numerical', name: '快捷选号中心', component: QuickPane },
......
];
细细分化,分发处理,对于前期开发,充分的利用高内聚、低耦合思想,简化前端开发工作,提高开发效率;于后期运维,也能更好的定位代码,快速进行产品需求迭代,减少运维难点和成本
对于人事变动,也能够很好的进行工作交接,确保公司的开发进程不受影响
最后声明
以上内容完全是个人在工作中通常遇到的一些问题,在捆绑这些问题的时候,通过集成 react
全家桶系列,指定一定的规范和统一性,能更好的减少新人入手的时间,确保企业项目的开发进度,当然,前端实际开发中远远不至于这些问题,做事情往往是要求精益求精的,此文仅仅是提供一种思想,并非强制使用,作为一个企业的前端管理者,有必要协调组员开发工作,以及管控企业产品开发周期