Grace's BLOG


  • 首页

  • 归档

  • 标签

淘宝云客服相关小功能使用

发表于 2017-09-05

绩效得分

提醒:%不用输入






实际得分:


税后薪资所得





计算明天绩效






结论:








预计明天绩效:


出勤率计算





总班次:小时






预计明天出勤率:


React交互二:Redux 以及库打包方法介绍

发表于 2016-10-27

Redux

上一章介绍了Flux,但是Flux也有一个问题就是一个动作里面处理逻辑的时候在派发一个事件,那无法处理,系统会中断报错。而本章要讲的Redux就很好的解决了这个问题。Redux 是 JavaScript 状态容器,提供可预测化的状态管理。
可以让你构建一致化的应用,运行于不同的环境(客户端、服务器、原生应用),并且易于测试。不仅于此,它还提供 超爽的开发体验,比如有一个时间旅行调试器可以编辑后实时预览。
Redux 除了和 React 一起用外,还支持其它界面库。
它体小精悍(只有2kB)且没有任何依赖。

Redux 和传统 Flux 框架的比较

viewer文件结构图
viewer文件结构图

从图片中可以看出来区别就是dispatch那部分拆成了Reducer和MiddleWare,实际操作起来区别还是蛮大的。可以从下面看出来的。

Action 发生了什么

直接看代码吧 /reduxEvent/actions/OPAction.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* Created by graceChen on 2016/7/8.
*/
var type="";
var OPAction = {
doAction:function(type,text){
this.type=type;
return{
type:type,
text:text
}
}
};
module .exports=OPAction;

可以看出和Flux有细微的区别,Flux需要通过aciton触发store,而这里是通过定义不同的方法,把数据从应用(注:这里之所以不叫 view 是因为这些数据有可能是服务器响应,用户输入或其它非 view 的数据)传到 store 的有效载荷。它是 store数据的唯一来源。一般来说你会通过 store.dispatch() 将 action 传到 store。
在Action里创建了一个函数,只是简单的返回一个action,在传统的 Flux 实现中,当调用 action 创建函数时,一般会触发一个 dispatch,而不同的是,Redux 中只需把 action 创建函数的结果传给 dispatch() 方法即可发起一次 dispatch 过程。

Reducer 怎么做

Action 只是描述了有事情发生了这一事实,并没有指明应用如何更新 state。而这正是 reducer 要做的事情。/reduxEvent/reducers/OPReducer.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* Created by graceChen on 2016/7/11.
*/
var initialState = {
item:{},
type:''
};
var OPReducer = function(state=initialState, action){
var item = state.item;
item[action.type]=action.text;
return Object.assign({},state,{type:action.type,item:item});
state.item[action.type] = action.text;
state.type = action.type;
return state;
};
module .exports= OPReducer;

在Redux里面,所有的 state 都被保存在一个单一对象中。上面写的就是根据项目的结构构思出来的对对象的一个比较简单的表现形式。
type:交互(事件)类型
item:不同类型里的不同数据(可以数组)

随着应用变得复杂,需要对 reducer 函数 进行拆分,拆分后的每一块独立负责管理 state 的一部分。这个时候就可以用到combineReducers,它的作用就是把一个由多个不同 reducer 函数作为 value 的 object,合并成一个最终的 reducer 函数,然后就可以对这个 reducer 调用 createStore。/reduxEvent/reducers/OPCombineReducers.js

1
2
3
4
5
6
7
8
9
/**
* Created by graceChen on 2016/7/11.
*/
var OPReducer = require('./OPReducer');
//使用redux的combineReducers方法将所有reducer打包起来
var OPCombineReducers = Redux.combineReducers({
OPReducer
});
module.exports = OPCombineReducers;

以逗号间隔一个一个引用进来就行,最后页面需要的时候直接引用一个OPCombineReducers即可。

Store 怎么做以及维护

Store 就是用来维持应用所有的 state 树 的一个对象。 改变 store 内 state 的惟一途径是对它 dispatch 一个 action。
区分Flux:Redux没有Dispatcher且不支持多个store。相反,只有一个单一的store和一个根级的reduce函数(reducer)。随着应用不断变大,你应该把根级的 reducer 拆成多个小的 reducers,分别独立地操作state树的不同部分,而不是添加新的stores。这就像一个React应用只有一个根级的组件,这个根组件又由很多小组件构成。
/reduxEvent/store/OPStore.js

1
2
3
4
5
6
7
8
9
var todoReducer = require('../reducers/OPCombineReducers');
var OPStore = Redux.createStore(todoReducer);
module .exports =OPStore;
```
createStore() 的第二个参数是可选的,用于设置state初始状态。这对开发同构应用时非常有用,服务器端redux应用的state结构可以与客户端保持一致那么客户端可以将从网络接收到的服务端 state 直接用于本地数据初始化。
``` bash
let store = createStore(todoApp, window.STATE_FROM_SERVER);

View 页面展示

这里只显示关键代码,实际项目中应用比较多比较广,全部js代码比较长,就不全贴出来了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
refreshDataHandler:function(){
//判断dispatch中的type是否和本地监听的事件名一致,如果一致则正常操作,否则不操作。
if (ReduxEvent.Action.type != EventName.TIME_CHANGE)
return;
console.log("时间轴事件派发开始:"+ReduxEvent.Action.type);
var timeValue = ReduxEvent.Store.getState().OPReducer.item[EventName.TIME_CHANGE];
var timeGap = timeValue.TimeGap;
if (timeGap*1%5==0) {
this.updateDevicesStaticInfo();
}
},
componentDidMount: function() {
this.unsubscribe = ReduxEvent.Store.subscribe(this.refreshDataHandler);//注册监听事件 以及返回注销方法
},
componentWillUnmount: function() {
this.unsubscribe();//注销事件
},

ReduxEvent.Store 这个是用直接打包出来的库直接引用的,下面会有介绍。html页面直接引用了一个js,即ReduxEvent.js文件,上面的各种js文件都没有引用,因为压缩打包成一个了。

参考文献
Redux 中文文档
react+redux教程

库打包

这里直接简单介绍下Redux的打包方法,已经写好了一个比较完善的机制,不需要再去修改它,直接可以各个项目中使用的,那就可以直接打包成一个库文件,到时候直接引用即可,方便简单,省时省力。

先定义一个ReduxEvent文件,把需要用到的Store和Action引用在一起,方便调用

/reduxEvent/ReduxEvent.js

1
2
3
4
5
6
7
8
9
10
/**
* Created by graceChen on 2016/7/20.
*/
var Store = require("./store/OPStore");
var Action = require("./actions/OPAction");
var ReduxEvent={
Store:Store,
Action:Action
}
module.exports = ReduxEve

另外写一个library.config.js打包文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* 文件创建日期:2016/7/20.
* 作者:gracechan
* 功能描述:打包库
* 编写配置参考:http://fakefish.github.io/react-webpack-cookbook/Authoring-libraries.html
*/
module.exports = {
externals:{
//第三方或者本地组件在此添加将不会被webpack打包,然后需要在页面中自行引入js库
"react":'React',
"react-dom":'ReactDOM',
"redux":"Redux",
"flux":"Flux",
},
entry:{//指定打包的入口文件,每有一个键值对,就是一个入口文件
ReduxEvent: ["./common/reduxEvent/ReduxEvent.js"]
},
output: { //配置打包结果,path定义了输出的文件夹,filename则定义了打包结果文件的名称
filename: "../react/[name].js",
libraryTarget: 'umd',
library: '[name]', //库名 引用里面文件的时候也是以库名来引用的
},
resolve: {//定义了解析模块路径时的配置,常用的就是extensions,可以用来指定模块的后缀,这样在引入模块时就不需要写后缀了,会自动补全
extensions: ['','.js','.jsx']
},
module: {//定义了对模块的处理逻辑,这里可以用
loaders: [//loaders定义了一系列的加载器,以及一些正则。当需要加载的文件匹配test的正则时,就会调用后面的loader对文件进行处理,这正是webpack强大的原因。
{ test: /\.js$/, loader: 'jsx-loader' },
{ test: /\.css$/, loader: "style!css" }
]
}
};

这个文件看起来应该很熟悉了,就是一开始第一篇文章《React结合Webpack使用》里面介绍的文件。

打开命令窗口,定位到这个打包文件目录下,运行 npm run wlib 即可打包文件。完成后可以去react文件夹下查看是否输出成功,然后在相应的html文件里面直接引用该文件即可使用!~

React交互一:Flux

发表于 2016-10-27

Flux

现在的很多在使用的系统都采取的是MVC模式,而前一篇介绍的React自称在MVC里面代表V 的部分,那么Flux就相当于添加M和C的部分。Flux是 Facebook 使用的一套前端应用的架构模式。

一个 Flux 应用主要包含四个部分:

  • The Dispatcher
    处理动作分发,维护 Store 之间的依赖关系

  • The Stores
    用来存储数据和处理逻辑部分

  • The Views
    控制React 组件,这一层可以看作 controller-views,作为视图同时响应用户交互

  • The Actions
    提供给 dispatcher 传递数据给 store

针对以上提出的概念,下面使用目前项目在使用的几个文件内容来实现这些功能,当然,网络上有很多这类开源的文件,实际项目中也是在这些开源的项目上再进行优化使用的。
单向数据流

单向数据流

先来了解下Flux的核心“单向数据流“怎么运作的:

Action -> Dispatcher -> Store -> View

更多时候 View 会通过用户交互触发 Action,所以一个简单完整的数据流类似这样:
viewer文件结构图

项目对于交互的整个流程如下:

  • 首先要有action,通过定义一些 action creator 方法根据需要创建 Action 将数据提供给 dispatcher

  • View 层(React组件)通过用户交互会触发 Action

  • Dispatcher 会分发触发的 Action 给所有注册的 Store 的回调函数

  • Store 回调函数根据接收的 Action 更新自身数据之后会触发一个 change 事件通知 View 数据更改了

  • View 会监听这个 change 事件,拿到对应的新数据并调用 setState 更新组件 UI

所有的状态都由 Store 来维护,通过 Action 传递数据,构成了如上所述的单向数据流循环,所以应用中的各部分分工就相当明确,高度解耦了。
这种单向数据流使得整个系统都是透明可预测的。

Dispatcher

一个应用只需要一个 dispatcher 作为分发中心,管理所有数据流向,分发动作给 Store,没有太多其他的逻辑(一些 action creator 方法也可以放到这里)。
Dispatcher 分发动作给 Store 注册的回调函数,这和一般的订阅/发布模式不同的地方在于:

  • 回调函数不是订阅到某一个特定的事件/频道,每个动作会分发给所有注册的回调函数

  • 回调函数可以指定在其他回调之后调用

参见comEventMessage/ComEventDispatcher.js

1
2
3
4
5
6
7
var Dispatcher = require('flux').Dispatcher;
var ComEventDispatcher = new Dispatcher();
var ComEventStore = require('./ComEventListStore');
ComEventDispatcher.register(function (action) {
ComEventStore.dispatch(action.actionType,action.text);
});//主要是这句话
module.exports = ComEventDispatcher;

Action

首先要创建动作,通过定义一些 action creator 方法来创建,这些方法用来暴露给外部调用,通过 dispatch 分发对应的动作,所以 action creator 也称作 dispatcher helper methods 辅助 dipatcher 分发。 参见comEventMessage/ComEventAction.js

1
2
3
4
5
6
7
8
9
10
11
12
13
var ComEventDispatcher = require('./ComEventDispatcher');
var ComEventAction = {
dispatch: function (eventname,data) {
ComEventDispatcher.dispatch({
actionType: eventname,
text: data
});
},
stopDispatch:function(){
ComEventDispatcher._stopDispatching();
}
};
module.exports = ComEventAction;

这里的dispatch就是action creator,通过用户的交互触发,比如按钮点击。当然不单单用户触发才调用,也可以通过计时器的方式等的,然后分发给Store初始数据。通过这一个动作我们可以看出动作其实就是用来封装数据和传递数据的。

Store

Store中包含应用的状态和处理逻辑,不同的Store可以管理应用中不同部分的状态。本项目中只使用了一个Store,直接在相应React组件中直接监听相应actionType,然后做出不同的事件处理。(处理逻辑和Flex事件的处理处理逻辑类似的)如comEventMessage/ComEventListStore.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
var EventEmitter = require('events').EventEmitter;
var assign = require('object-assign');
var ComEventListStore = assign({}, EventEmitter.prototype, {
event_data: {},
getAll: function () {//获取所有数据
return this.event_data;
},
getOne:function(key){//依据key查找单个
if(arguments[1]=="layer"){
return ComEventData[key];
}else{
return this.event_data[key];
}
},
dispatch: function (eventname,data) {
if(data){
//alert(eventname+";"+data);
this.event_data[eventname]=data;
ComEventData[eventname]=data;
}
this.emit(eventname);
},
addEventListener: function(eventname,callback) {
this.on(eventname, callback);//注册监听
},
removeEventListener: function(eventname,callback) {
this.removeListener(eventname, callback);
}
});
module.exports = ComEventListStore;

在 Store 注册给 dispatcher 的回调函数中会接受到分发的 action,因为每个 action 都会分发给所有注册的回调,所以回调函数里面要判断这个 action 的 type 并调用相关的内部方法处理更新 action 带过来的数据,再通知 view 数据变更。

Store 里面不会暴露直接操作数据的方法给外部,暴露给外部调用的方法都是 Getter 方法,没有 Setter 方法,唯一更新数据的手段就是通过在 dispatcher 注册的回调函数。

View

View 就是 React 组件,从 Store 获取状态(数据),绑定 change 事件处理。如 customer_components/bottom_component/BottomTrafficButtonMenu.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/**
* 文件创建日期:2016/7/22.
* 作者:
* 功能描述:
*
*/
var lastType = null;
var BottomTraficButtonMenu=React.createClass({
getInitialState: function () {
return {
}
},
clearChoosed: function () {
if(lastType){
lastType.src=lastType.src.replace("_hover.png",".png");
}
},
componentDidMount: function() {
lastType = $("#ALLSubject")[0];
FluxComEvent.store.addEventListener(EventName.ILLEGAL_CLICK,this.clearChoosed);
},
componentWillUnmount: function() {
FluxComEvent.store.removeEventListener(EventName.ILLEGAL_CLICK,this.clearChoosed);
},
subjectSwitchHandler:function(event){
if(lastType){
lastType.src=lastType.src.replace("_hover.png",".png");
}
lastType = event.currentTarget,
lastType.src=lastType.src.replace(".png","_hover.png");
$("#wfDSO").css("color", "#FFFFFF");
FluxComEvent.action.dispatch(EventName.BOTTOM_TYPE_CLICK, event.target.id);
},
render:function(){
return(
<div id='div_subjectSwitch' className="div_subjectSwitch_style">
<img id='ALLSubject' className="menuImg_style" src='./modules/dsp/gisapi/assets/brn_1_hover.png' title='整体指标' onClick={this.subjectSwitchHandler}/>
<img id='CONGESTION' className="menuImg_style" src='./modules/dsp/gisapi/assets/brn_2.png' title='拥堵专题' onClick={this.subjectSwitchHandler} />
<img id='SEGMENT_FLOW' className="menuImg_style" src='./modules/dsp/gisapi/assets/brn_4.png' title='流量专题' onClick={this.subjectSwitchHandler }/>
<img id='SEGMENT_SPEED' className="menuImg_style" src='./modules/dsp/gisapi/assets/brn_3.png' title='速度专题' onClick={this.subjectSwitchHandler} />
<img id='TIMING' className="menuImg_style" src='./modules/dsp/gisapi/assets/brn_6.png' title="时间" onClick={this.subjectSwitchHandler} />
<img id='SATURATION' className="menuImg_style" src='./modules/dsp/gisapi/assets/brn_5.png' title='饱和度专题' onClick={this.subjectSwitchHandler} />
</div>
)
}
});
module.exports = BottomTraficButtonMenu;

一个View可能关联多个Store来管理不同事件的处理。需要注意的就是,当组件移除的时候事件监听也应该一起移除掉。

更多资料
Flux chat 很简洁明了的一个 Slide

React结合Webpack使用

发表于 2016-08-25

准备工作

因为要使用webpack,需要先在电脑上安装nodeJs软件,并配置好电脑环境变量,即path中增加一条nodejs的安装路径(windows 7系统是自动添加的)。
本文操作均按照本人已经在实际项目中编写的路径中填写,react相关根目录为viewer,即react相关代码均在该目录下编写。

安装webpack

1
$ npm install webpack -g

参数-g表示我们将全局(global)安装 webpack, 这样你就能使用 webpack 命令了.
webpack 也有一个 web 服务器 webpack-dev-server, 我们也安装上

1
$ npm install webpack-dev-server -g

方便我们修改后界面可以及时自动刷新。
全局安装:就是再电脑的任何一个文件夹下打开命令窗口都可以使用该命令。
实际安装路径:C:\Users\帐户名\AppData\Roaming\npm\node_modules

配置package.json

如果是第一次运行,则直接在相应目录(viewer)下执行npm init 会自动生成该文件,生成后需要修改该文件的script的键值如下:

1
2
3
"scripts": {
"start": "webpack-dev-server --hot --colors"
},

现在在命令窗口下执行 npm (run) start 相当于 webpack-dev-server.当项目变得相当复杂时, 你可以使用这种技巧隐藏其中的细节.

另外,如果需要在该文件中添加注释,则要在description中添加,如:

1
2
3
"description": {
"start": "开启小型服务器使用热加载",
},

编写webpack配置文件

webpack 使用一个名为 webpack.config.js 的配置文件(默认), 在相应目录下创建该文件. 配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//用于提取多个入口文件的公共脚本部分,然后生成一个 xxx.js 来方便多页面之间进行复用。
module.exports = {
entry:{//指定打包的入口文件,每有一个键值对,就是一个入口文件
},
output: { //配置打包结果,path定义了输出的文件夹,filename则定义了打包结果文件的名称
},
devServer: {//热加载服务器的配置
inline: true,
port: 7777
},
resolve: {//定义了解析模块路径时的配置,常用的就是extensions,可以用来指定模块的后缀,这样在引入模块时就不需要写后缀了,会自动补全
extensions: ['','.js','.jsx']
},
module: {//定义了对模块的处理逻辑,这里可以用
loaders: [//loaders定义了一系列的加载器,以及一些正则。当需要加载的文件匹配test的正则时,就会调用后面的loader对文件进行处理,这正是webpack强大的原因。
},
};

指定相应的入口文件和出口文件以及对相应文件的加载器,比如我们写的js文件是jsx语法需要通过jsx-loader来加载的。
一开始说了这个文件是默认的webpack配置文件名称,如果想要名称自定义也可以通过如下命令处理:

1
$ webpack --config library.config.js

一般来说,是不会修改默认的文件的,但是做到后面会发现,有些事件等情况可以完全打包成一个独立的库处理,那么就需要经常使用这种类似的命令,每次输入这样一行的命令难免会觉得繁琐,这个时候就可以在package.json配置文件中添加script键值以及相应注释以便其他开发人员了解。

1
2
3
4
5
6
"scripts": {
"wlib": "webpack --config library.config.js"
},
"description": {
"wlib":"按照library.config.js配置打包文件,启动方法: npm run wlib (只会运行一次 不改变默认配置文件)",
},

安装依赖

以后要是多个项目需要用到的话,建议直接全局安装,需要引用哪个直接使用 npm link加载进来,避免到时候另一个项目需要用到又要重新下载安装一遍,费时。

同时注意:如果想要删除其中一个可以使用npm unlink 但不要使用右键删除这个方式,这个方式会导致全局的这个资源包也会被删除掉,之后又得重新下载。

安装React

1
$ npm install react react-dom --save

–save和–save-dev可以省掉你手动修改package.json文件的步骤。
npm install module-name –save 自动把模块和版本号添加到dependencies部分
npm install module-name –save-dev 自动把模块和版本号添加到devdependencies部分

安装jsx,css的loader

1
$ npm install webpack jsx-loader css-loader style-loader --save

然后配置 webpack.config.js 来使用安装的 loader.

1
2
3
4
5
6
module: {//定义了对模块的处理逻辑,这里可以用
loaders: [//loaders定义了一系列的加载器,以及一些正则。当需要加载的文件匹配test的正则时,就会调用后面的loader对文件进行处理,这正是webpack强大的原因。
{ test: /\.js$/, loader: 'jsx-loader' },
{ test: /\.css$/, loader: "style!css" }
]
},

介绍到这里,那么可以看下配置文件还剩哪里没有填写,就是入口文件和出口文件,入口文件就是我们自己编写的jsx文件,这里直接以实际项目中为例,先看下项目文件结构图:
viewer文件结构图
入口文件为 main.js 出口文件可以自定义,这样的写法是main里面所有引用的其他文件(js、css等)打包成一个文件。

1
2
3
4
5
6
entry:{//指定打包的入口文件,每有一个键值对,就是一个入口文件
main:["./component/main.js"]
},
output: { //配置打包结果,path定义了输出的文件夹,filename则定义了打包结果文件的名称
filename: "export/[name].js",
},

入口文件写法格式为 key:value 形式,出口文件可以直接通过[name]的写法直接获取到key。

综上已经可以打包一个简单的项目js出来了,然后 直接在 html 页面引入这个js标签即可。

本篇文章参考其他blog如下:

WebPack实例与前端性能优化
如何使用 Webpack 模組整合工具

gracechan6

gracechan6

Life is as good as youe mindset!

4 日志
5 标签
© 2018 gracechan6