vtmim重构

先放一个占位符。vtmim是我接手的第一个我厂项目,在维护过程中感觉到如下问题:
5000行代码一个文件,动辄一个函数200行,文件代码冗余,经常看到if else里出现同样的代码,代码的注释文不对题,以及缺乏合理的模块管理。

原生nodejs爬mdn

作者的一道题目:

nodejs爬https://developer.mozilla.org/zh-CN/docs/Web/API, 把所有api对用的html文档保存在本地。

作者推荐使用node-fetch,cherrio,super-agent。因为我用过上述三者爬虫,这次打算使用原生api开发,刚好
测试一下最近学习的正则表达式有没有长进。

原材料 : https + fs + es6 + nodeV8.21

promise是上帝,promise是魔鬼

promise是上帝,只要是异步,完全可以用promise替代毁掉函数;promise是魔鬼,只要用了promise就得全盘用promise

自己对promise在实际使用中的理解

callback promise化

普通回调函数写法:

1
2
3
4
5
6
7
8
9
function myAwesomeAsyncMethod(callback){
http.get('url', (res)=>{
if(some condition){
callback('success')
}else{
callback('error')
}
})
}

promise化回调函数:

1
2
3
4
5
6
7
8
9
10
11
function myAwesomeAsyncMethod(callback){
return new Promise((resolve, reject)=>{
http.get('url', (res)=>{
if(some condition){
resolve('success')
}else{
reject('error')
}
})
})
}

如果用了promise需要全盘使用promise
这一点让人很不爽,一旦用了promise,代码中其他的异步操作统统需要promise化。

promsie配合async await一起用
单纯的用promise和Promise.then一步步链式调用本质上和callback并没有太大区别,但是配合async await则
可以将异步函数改造成形式上的同步函数, 举个板栗:

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
let fetchWebApi = async (url) => {
let data = await fetch(url)
let apiList = getApiList(data)

let fetchAndSave = (apiName) => {
return new Promise(async (resolve, reject) => {
let data
try {
data = await fetch(`https://developer.mozilla.org${apiName}`)
await writeToLocal(apiName, data)
resolve()
} catch (e) {
console.log('error')
reject()
}
})
}

let count = 0
console.log('apiList length : ' + apiList.length)
while (count < apiList.length) {
try {
let subApiList = apiList.slice(count, count + 100)
let tmp = []
for (let i = 0; i < subApiList.length; i++) {
tmp.push(fetchAndSave(subApiList[i]))
}
await Promise.all(tmp).then((result) => {
count += subApiList.length
})
} catch (e) {
console.log('error ' + e)
}
}
stats(resultList)
}

fetchWebApi(url)

理由很简单: await修饰的函数必须要promise化。需要注意的是,使用到await的函数必须是async,
promise是promise,async是async,二者promsie对象接受的函数当然也可以用async修饰,参照fetchAndSave方法。

https请求

如果目标url是http,需要使用http模块,如果是https,需要使用https模块。使用http模块调用
https url会报错。

实际问题中遇到重定向问题。[300, 400)之间的状态码是重定向,又细分301的永久重定向和302的临时重定向。对于2者和搜索引擎
的纠葛在此不做深入探讨。对于需求,只需要获取重定向后的url内容即可。

1
await fetch(res.headers.location)

res.headers.location中包含了重定向的url

获取数据用到的data事件很有趣,返回的data应该对应http的一个请求包。一堆请求包组成一个html文档。

清洗html文档,提取apiList

当获取完成包含api的html文档后,下一步是要提取apiList。cherrio是node的jquery,api和jquery,zepto大同小异。
但是我们的目标是没有蛀牙不使用第三方库。我改怎样做呢?

request.get返回html文档的字符串,如果在浏览器端,会被浏览器解析成dom树。服务端并不需要解析成dom树,只要从字符串中提取
api的路径。

一开始观察,发现关键字段在<code>API</code>中,正则用:

1
let reg = /<code>.+?<\/code>/ig  //惰性匹配

注意全局搜索和利用?实现惰性匹配,否则会将开头的<code>和结尾</code>之间的所有字符串一次性匹配。

坑爹是的,code之间的字段并不能100%当时api的路径,老老实实用href里的字段,

1
let reg = /"\/zh-CN\/docs\/Web\/API\/.+?"/ig

自从刷了hackerrank里的大部分正则基础题后,写这些正则变得砍瓜切菜一样简单。

分布提取api文档

一共有748个api,如果for循环一次性请求会挂掉。从作者那里偷学了while用法:

1
2
3
while(some condition){
await Promise.all(partOfApiList)
}

这里,Promise.all接受一个数组,数组的每个元素都是promise对象。于是又要promise化。

为什么只写入了747个文档?

可能是99%的情况下永远少写入一个文档,只有一次测试的时候,5个请求写入3个文档,其他都是写入4个文档。测试很久之后才反应过来,
fs.writeFile也是个异步方法,需要promise化。

至此,爬虫遇到的主要难点都写完了,我们实现了纯nodejs api爬虫。

附录:百行代码就不开git仓库了:

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
/**
* Created by pingfengafei on 2017/7/30.
* pure node.js api without any third part library
*
*/

const https = require('https')
const fs = require('fs')

const url = 'https://developer.mozilla.org/zh-CN/docs/Web/API'
const dir = './web-api'
const resultList = []

let fetch = (url) => {
return new Promise((resolve, reject) => {
https.get(url, async (res) => {
if (res.statusCode !== 200) {
if (res.statusCode >= 300 && res.statusCode < 400) {
let data = await fetch(res.headers.location)
resolve(data)
} else {
reject('error code ' + res.statusCode)
}
} else {
let responseData = ''
res.on('data', (data) => {
responseData += data
})
res.on('end', () => {
resolve(responseData)
})
}
})
})
}

let getApiList = function (content) {
//let reg = /<code>.+?<\/code>/ig //惰性匹配 枣糕,code不是精确匹配
let reg = /"\/zh-CN\/docs\/Web\/API\/.+?"/ig
let arr = []
let apiList = []
while (arr = reg.exec(content)) {
apiList.push(arr[0].substring(1, arr[0].length - 1))
}
return apiList
}

let writeToLocal = async (title, content) => {
return new Promise((resolve, reject) => {
fs.writeFile(`${dir}/${title.substring(20, title.length)}`, content, (err) => {
let obj = {}
if (err) {
obj = {
status: 'error',
api: title
}
reject()
} else {
obj = {
status: 'success',
api: title,
path: `${dir}/${title}`
}
}
resultList.push(obj)
resolve()
})
})
}

let stats = (list) => {
//todo 调查实际是748,最后却只输出了747个文档
console.log('resultList length : ' + list.length)
fs.appendFile('./stats.json', JSON.stringify(list), (err) => {
if (err) {
console.log(err)
}
})
}

let fetchWebApi = async (url) => {
let data = await fetch(url)
let apiList = getApiList(data)

let fetchAndSave = (apiName) => {
return new Promise(async (resolve, reject) => {
let data
try {
data = await fetch(`https://developer.mozilla.org${apiName}`)
await writeToLocal(apiName, data)
resolve()
} catch (e) {
console.log('error')
reject()
}
})
}

let count = 0
console.log('apiList length : ' + apiList.length)
while (count < apiList.length) {
try {
let subApiList = apiList.slice(count, count + 100)
let tmp = []
for (let i = 0; i < subApiList.length; i++) {
tmp.push(fetchAndSave(subApiList[i]))
}
await Promise.all(tmp).then((result) => {
count += subApiList.length
})
} catch (e) {
console.log('error ' + e)
}
}
stats(resultList)
}

fetchWebApi(url)

—————————–我是分割线———————————-

作者小小的修改了代码:更严谨的代码,更抽象的逻辑和更高的可复用性

js 的类与继承

js的继承真是天坑,看过红宝书的原型链和继承章节不知所云。各种继承方式,颇有种乱花渐欲迷人眼的感觉。

es6的class和extends给了我一丝曙光。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Foo {
constructor(name){
this.name = name
}
sayHello(){
console.log(`super name is ${this.name}`)
}
}

class Bar extends Foo{
sayHello(){
super.sayHello()
console.log(`sub name is ${this.name}`)
}
}

var bar = new Bar('aaa')
bar.sayHello()

这个简单例子包含了类,继承和调用父类。麻雀虽小却五脏俱全。不知道有没有人想的和我一样,类
只是个语法糖,在现有的环境下还是要被babel转换成es5的原型链继承方法。

wait,被babel转换成es5的原型链继承方法,babel翻译后的一定是优秀的继承方式,我仿佛找到了onepiece

先看看babel如何翻译Foo

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
'use strict' //严格模式

var _createClass = function () {
function defineProperties (target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i]
descriptor.enumerable = descriptor.enumerable || false
descriptor.configurable = true
if ('value' in descriptor) descriptor.writable = true
Object.defineProperty(target, descriptor.key, descriptor)
}
}

return function (Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps)
if (staticProps) defineProperties(Constructor, staticProps)
return Constructor
}
}()

function _classCallCheck (instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError('Cannot call a class as a function')
}
}

var Foo = function () {
function Foo (name) {
_classCallCheck(this, Foo)

this.name = name
}

_createClass(Foo, [
{
key: 'sayHello',
value: function sayHello () {
console.log('super name is ' + this.name)
}
}])

return Foo
}()

深入代码理清逻辑:

1
var Foo = function(){}()

还有这种写法???匿名函数先赋值给Foo,Foo和()结合在一起,执行Foo

看看关键函数_createClass

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var _createClass = function () {
function defineProperties (target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i]
descriptor.enumerable = descriptor.enumerable || false
descriptor.configurable = true
if ('value' in descriptor) descriptor.writable = true
Object.defineProperty(target, descriptor.key, descriptor)
}
}

return function (Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps)
if (staticProps) defineProperties(Constructor, staticProps)
return Constructor
}
}()

背后的原理:

1
2
Foo.prototype.key = value
Foor.key = value //静态方法

再看看继承

1
2
3
4
5
6
7
8
function _inherits (subClass, superClass) {
if (typeof superClass !== 'function' && superClass !== null) {
throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass)
}
subClass.prototype = Object.create(superClass && superClass.prototype,
{constructor: {value: subClass, enumerable: false, writable: true, configurable: true}})
if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass
}

回味原型链和继承

对象都有一个私有属性[[prptotype]],attention,这个私有属性有别于prototype,通常私有属性实现时用proto替代。
每个对象都有proto
函数对象拥有prototype对象,函数也是对象,自然也拥有proto

object.proto指向object的原型
object.prototype指向object的原型对象 ???

Object.defineProperty
Object.create
Object.getPrototypeOf
Object.setPrototypeOf

杂文

新版博客主题

受到icyfish小朋友的启发,决定给粗鄙的博客换一个主题。遍历了知乎上的优秀主题,选中了莫泊桑。自己写过一整年material-ui风格的网站,对于各种酷炫的交互都不感冒。诚如作者说的:大道至简。繁华过后,只希望有一片净土,写写文章,品品生活,足矣。完全不记得昨天icyfish小朋友推荐的就是莫泊桑,选中它,大约是因为人都会欣赏优秀的事物。

机械键盘

就在今早,又败了一个机械键盘。至此,我拥有一块plum 104黑轴,两块酷冷至尊87红轴和即将到来的酷冷至尊87青轴

都到这地步了,为什么不充值一波信仰,直接filco 双模一步到位?因为在前东家的时候,filco青轴给我留下了不如300块凯酷青轴的印象。
更真实的情况是,仿佛这一刻全世界的filco 双模忍者87青轴都缺货。zby小朋友一口气买了2块minila,一块青轴一块红轴。红轴的手感非常好,青轴的手感一般。她买键盘前
做的功课和买键盘的魄力已经执行力给我深深上了一课。

title date tags 失效

使用CRLF替换CR

md文件里换行导致编译后的文件也换行

hexo-renderer-marked/index.js下的breaks: false

关于react的思考

最近维护一个小功能掉坑里了。一个需求需要同时修改四处html字符串。服务端一处,客户端3处字符串拼接。此时我十分怀恋react。理论上如果框架设计的精妙,即使是SSR也只需要修改一个react文件,甚至仅需要
添加一个state对象。

高山仰止景行行止

我喜欢和聪明的人共事。在ctrip,我有很大几率遇到比我聪明的人,比如作者,比如xmy,和他们聊天时,能感觉到他们都在发光,睿智,逻辑,激情,理性。这让我又兴奋又后悔。
后悔自己游戏人生太久,渐渐地身体开始走下坡路,大约没有赶上他们的资本了,另一方面,就像我旁边的绿萝,即是不能,我也要快速的成长,虽不能至,心向往之,同时也要努力靠近。

if else debug 小技巧

今天领悟到了一个if else debug小技巧。例如

1
2
3
if(a){
do A
}

我们有时候会遇到一种情况:不想走a的逻辑或者一定走a的逻辑。有一天突然想到:

1
2
3
4
5
6
7
if(true || a){
do A
}

if(false && a){ //and 符号怎么都打不出来呢?
do A
}

这样写有个一好处,写完需求后,只要删除掉true或false后就能还原逻辑,不会造成过多修改代码造成的潜在bug风险。

.style and getComputedStyle

#.style vs window.getComputedStyle

1
<div className="filter-header-tabs" ref="filterHeaderTabs" style={{'width': '500px'}}>

1
2
3
4
.filter-header-tabs {
display: inline-block;
width: 85%;
min-width: 660px;
1
2
console.log(window.getComputedStyle(this.refs.filterHeaderTabs).width); // 660px
console.log(this.refs.filterHeaderTabs.style.width); // 500px

总结:
1:.style只能获得div标签上的style,无法获得css内的style和真实的style, 未设置的style都是""
2:window.getComputedStyle获取的是节点的真实style

#getDOMNode() vs findDOMNode() vs this.refs
从react14版本开始,getDOMNode被遗弃,使用findDOMNode替代
With this change, we’re deprecating .getDOMNode() and replacing it with ReactDOM.findDOMNode (see below). If your components are currently using .getDOMNode(), they will continue to work with a warning until 0.15.

##findDOMNode()和this.refs的区别
14版本又说了:
Starting with this release, this.refs.giraffe is the actual DOM node
从这个版本开始,this.refs等同于真实的dom节点,也就是说(部分地), findDOMNode9()等同于this.refs
14版本又说了:
Note that refs to custom (user-defined) components work exactly as before; only the built-in DOM components are affected by this change.

总结:
1:在dom节点(div, p, span…)上用findDOMNode()等同于this.refs
2:在React组件上用,this.refs获取的是组件的引用,findDOMNode()获取的是组件的dom树

作用在dom节点上

1
2
3
4
5
<div className="filter-header-tabs" ref="filterHeaderTabs">
//<div class="filter-header-tabs">...</div>
console.log(ReactDOM.findDOMNode(this.refs.filterHeaderTabs));
//<div class="filter-header-tabs">...</div>
console.log(this.refs.filterHeaderTabs);

作用在React组件上

callback和promise

对个人而言,刚开始学习js,最难的一点在理解回调函数上。滑稽与可笑,前端一年半了,在昨天晚上回去的路上才想通了回调函数,算是打通了js的任督二脉吧。

所谓回调函数,即在函数返回时被调用的函数。在js中函数可以为当做参数传递。简单的例子:

1
2
3
4
5
6
7
8
9
function b(callback){
var src = 'hello world';
//b do other steps;
//返回时调用了一次callback,即a函数
return callback(src);
}
b(a);
//匿名函数也一样
b(src=>console.log(src))

知乎上看到一个很有趣的理解回调函数的例子:

我去商店买东西,没有了,我给商店留下了电话号码。这里,电话号码就是回调函数。东西到货了,商家打电话给我。打电话这个过程就叫调用回调哈数。

打通理解回调函数后,es6的promise就好理解了:

Promise对象接受一个回调函数作为参数,该回调函数接受2个回调函数作为参数,第一个为pennding->sucess时调用,第二个参数为pending->failed时调用的;

1
2
3
4
5
var time = function(time){
return new Promise((sucess, failed)=>{
setTimeout(sucess, time,['hello','world','pingfengafe']);});
}
time(1000).then(src=>console.log(src));

spring13小结

马云讲过人离职的原因,钱给的少 || 呆的不开心。以前我一直只觉得第一个原因,如今第二个原因也满足了。到该走了时候了。

直到现在才开始写项目总结,想想蛮可笑的。写一写自己的项目心得吧。

一句话总结:学到很多,项目设计很乱,时间人为地很赶,自己进步一点。

这次项目需要写2个配置页面,使用2个带有get和put方法的Restful风格接口从服务器端获取J和上传JSON对象。

页面设计图:

就从router入口写吧。

1
2
<Route path="device_endpoint_feature" component={DeviceSingleFeatureContainer}/>
<Route path="device_frequency_feature" component={EventFrequencyFeatureContainer}/>

SingleFeature页面比较简单:

1.写了一个mapKeyToDisplayName方法,匹配key和显示的名字。
2.以前一直有个烦心事,用className方法生成新的class不知道取什么名字,有一天在看ant-design源代码,发现他们如下用,一举解决了命名问题:

1
const titleCls = className({title : this.state.showTitle});

3.下拉框设计难点:
UI设计稿中,异常度下拉框是个输入框,不是下拉框。考虑到未来的可扩展性,我建议UI改成了下拉框,被采纳。手写了个下拉框组件,有如下特点:
3.1 如果下拉框内容只有1条,则禁止触发onClick和onMouseOver事件,并且:

1
cursor:not-allow;

3.2 鼠标hover时展开,离开后下拉框收起。有2个难点:
onMouseOut, onMouseLeave的选择。二者都能相应鼠标离开事件,区别是,out只对被设置的DOM对象起作用,比如,out在父DOM节点上,移动到子DOM节点上就触发一次out,leave则是离开整个DOM节点才会触发。这里,选择onMouseLeave。

通过父组件position:realative,子组件absolution + z-index方式生成,遇到个问题,图片中上面的边框和下面的边框有个间隙,鼠标触碰到间隙后也会触发Leave事件,解决的办法是,用个大div包裹下面的dom并连接上面的dom,设置成透明即可。

4 关于重置逻辑:

1
this.setState({config:this.props.config})

5 关于保存逻辑:
在reducer中,如果后台返回了类似{ok:true}字段,则使用上传给后台的数据。

1
2
case PUT_DEVICE_SINGLE_FEATURE_CONFIG_SUCCESS:
return {config: action.data, status: 'put_device_single_feature_success'};

6:源于添加逻辑:
通过feature.disabled的值来判断,true在放置在添加栏内,false则放置在属性展示框内

7:关于小开关的逻辑:
感谢state和prop,让我实现各种逻辑得心应手。

8:关于Immutable对象的思考:
如果没有Immutable.JS,我也不知道该怎样去安全地去操作对象。

1
2
3
var a = {};
var b = a;
b.a = 1;

同样,如果没有this.state也存在被共用修改内容的危险。Immutable.JS一举解决了所有问题。实测效率不差。网上关于Immutable.JS有2种不好的观点,第一,api很难用,第二,体积有点大(其实是api太多了)。我觉得,类似于java风格的api,用着用着就习惯了。第二,(个人猜测)Immutable.JS是有野心的。api覆盖了js原生操作对象,数组的所有方法,这样,我们不妨脑洞大开,以后在React项目中,全部用Immutable.JS API操作对象(数组)。下一个项目打算这么干。

[后续补充], oh my dear,恰巧遇到了错误修改state姿势,导致的迷之bug,修改了半天。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
this.state = {
paginationConfig: {
total: 30,
selectedPage: 10
}
};
handlePaginationChange(page) {
let paginationConfig = Object.assign(this.state.paginationConfig);
paginationConfig.selectedPage = page;
this.setState({paginationConfig: paginationConfig});
}

shouldComponentUpdate(nextProps, nextState) {
return !(_.isEqual(nextProps, this.props) && _.isEqual(nextState, this.state));
}

一个分页组件的点击页面逻辑。
bug描述 : 分页组件失效。
bug追踪 : 从react-dev-tool中发现,this.sate.paginationConfig.selectedPage发生了变化,但是分页组件实现。分析可能是没有触发render,上游原因是shouldComponentUpdate, 分析可知:

1
paginationConfig.selectedPage = page; //共享对象,直接修改了state

导致_.isEqual(nextState, this.state)返回true.
先不忙着修改,此处应该还有更有趣的问题。
1:错误使用了Object.assign方法,正确姿势:

1
const paginationConfig = Object.assign({}, this.state.paginationConfig);

2.Object.assign本质上是浅拷贝

1
2
3
4
5
6
const obj = {a:1,b:{c:1}};
const cp = Object.assign({},obj);
cp.a = 2;
cp.b.c = 2;
console.log(obj.a); //1
console.log(obj.b.c); //2 assign是浅拷贝

吓得我赶紧换回Immutable,彻底清净了。

EventFrequency页面比较复杂:

这里坑比较多。总结起来有点:

  1. 设计不合理,不合理,不合理!
  2. 因为1的关系,操作json数据逻辑复杂
  3. UI设计没有适配低分辨率屏幕

关于1和3具体内容我不想过多讨论,但是直接导致了实际开发中,我抛弃设计稿,调整了排版布局,也导致了问题2,一度困扰了我整个周末。整个周末都没过好,在电影院看电影都在想如何操作数据。

  1. 接口字段名和API不一致
    小问题

  2. 一堆子的交互逻辑
    小问题,配合state和props好写,担心后来人看不懂为什么代码里要添加那么多开关,多写了几行注释。

  3. 频次数值和异常度每次只能修改其中1个。要突出timeFerature(分钟,小时,日)的作用

    2个很大问题。
    按照后台设计逻辑,只能传输频次数值(value)和异常度(anomaly)其中的一个。这个坑了。之前设计好的逻辑是,在container容器中统一分发给每个component内容,获取和修改的json所有key都相同,只有value不同。这种方式修改起来比较简单。但是现在接口每次只给一个value或anomaly,我在上传json前也做做判断,判断用户选择了哪个字段,我要剔除相应的字段。

为了适应前期的工作,我在get到数据后,在container组件里,手动添加了所有缺失的字段。按照设计理念,timeFeature可以全选,但不能全空,选择后对应显示对应的字段,关闭后,不选择对应的字段。

1.timeFeature开关逻辑

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
selectTimeFeature(type) {
//判断时间feature不能全为空
switch (type) {
case 'minute': {
if (!(this.state.config.day || this.state.config.hour || !this.state.config.minute)) {
return;
}
break;
}
case 'hour': {
if (!(this.state.config.day || !this.state.config.hour || this.state.config.minute)) {
return;
}
break;
}
case 'day': {
if (!(!this.state.config.day || this.state.config.hour || this.state.config.minute)) {
return;
}
break;
}
default :
return;
}

/**
* 向对象里添加字段和删除字段
* 删除: 删除key && value
* 添加: 添加key && value
*/
const reg = new RegExp(type, 'i');
let obj = {};
const newConfig = {};
const config = Immutable.fromJS(this.state.config.data).toJS();


if (this.state.config[type]) { //关闭时间按钮,去除timeFeature
_.forEach(config, (val, item1) => {
obj[item1] = {};
_.forEach(val, (val, item2) => {
if (item2 === 'disabled') {
obj[item1].disabled = val;
} else {
obj[item1][item2] = {};
_.forEach(val, (val, item3) => {
obj[item1][item2][item3] = {};
_.forEach(val, (val, item4) => {
if (!reg.test(item4)) {
obj[item1][item2][item3][item4] = val;
}
});
});
}
});
});

//组装newConfig
newConfig.data = obj;
newConfig.minute = (type !== 'minute') ? this.state.config.minute : !this.state.config[type];
newConfig.hour = (type !== 'hour') ? this.state.config.hour : !this.state.config[type];
newConfig.day = (type !== 'day') ? this.state.config.day : !this.state.config[type];

this.setState({config: newConfig, configChanged: true});

} else if (!this.state.config[type]) { //打开时间按钮,添加timeFeature
_.forEach(config, (val, item1) => {
_.forEach(val, (val, item2) => {
if (item2 !== 'disabled') {
_.forEach(val, (val, item3) => {
switch (type) {
case 'minute': {
config[item1][item2][item3].oneMinute = {
disabled: false,
value: item3 === 'anomaly' ? 1 : ''
};
config[item1][item2][item3].fiveMinute = {
disabled: false,
value: item3 === 'anomaly' ? 1 : ''
};
config[item1][item2][item3].fifteenMinute = {
disabled: false,
value: item3 === 'anomaly' ? 1 : ''
};
break;
}
case 'hour': {
config[item1][item2][item3].oneHour = {
disabled: false,
value: item3 === 'anomaly' ? 1 : ''
};
config[item1][item2][item3].sixHour = {
disabled: false,
value: item3 === 'anomaly' ? 1 : ''
};
break;
}
case 'day': {
config[item1][item2][item3].oneDay = {
disabled: false,
value: item3 === 'anomaly' ? 1 : ''
};
config[item1][item2][item3].sevenDay = {
disabled: false,
value: item3 === 'anomaly' ? 1 : ''
};
break;
}
default :
break;
}
});
}
});
});

//组装newConfig
newConfig.data = config;
newConfig.minute = (type !== 'minute') ? this.state.config.minute : !this.state.config[type];
newConfig.hour = (type !== 'hour') ? this.state.config.hour : !this.state.config[type];
newConfig.day = (type !== 'day') ? this.state.config.day : !this.state.config[type];

this.setState({config: newConfig, configChanged: true});
}
}

2.自动填充数据逻辑

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/**
* 后台的逻辑是anomaly或者value只能上传一个
*
* @param config
* @return newConfig
*/
autoFillConfig(config) {
const newConfig = Immutable.fromJS(config).toJS();
const anomaly = {
fiveMinute: {disabled: false, value: 1},
oneMinute: {disabled: false, value: 1},
sevenDay: {disabled: false, value: 1},
oneHour: {disabled: false, value: 1},
fifteenMinute: {disabled: false, value: 1},
sixHour: {disabled: false, value: 1},
oneDay: {disabled: false, value: 1}
};
const value = {
fiveMinute: {disabled: false, value: ''},
oneMinute: {disabled: false, value: ''},
sevenDay: {disabled: false, value: ''},
oneHour: {disabled: false, value: ''},
fifteenMinute: {disabled: false, value: ''},
sixHour: {disabled: false, value: ''},
oneDay: {disabled: false, value: ''}
};

for (const key in anomaly) {
if (!config.minute) {
if ((/minute/i.test(key))) {
delete anomaly[key];
delete value[key];
}
}
if (!config.hour) {
if ((/hour/i.test(key))) {
delete anomaly[key];
delete value[key];
}
}
if (!config.day) {
if ((/day/i.test(key))) {
delete anomaly[key];
delete value[key];
}
}
}

for (const akey in newConfig.data) { //sameIPGeo
for (const bkey in newConfig.data[akey]) {
if (bkey !== 'disabled') { //transaction createAccount
if (newConfig.data[akey][bkey].anomaly) {//value不存在
newConfig.data[akey][bkey].value = value;
} else if (newConfig.data[akey][bkey].value) {
newConfig.data[akey][bkey].anomaly = anomaly;
}
}
}
}
return newConfig;
}
  1. 我需要一个state记录每次修改的下拉框类型,以便上传数据时剔除没有被选中的字段。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    case 'changeType': {
    const configDropSelectedType = Immutable.fromJS(this.state.configDropSelectedType).toJS();
    if (!configDropSelectedType[feature][type][dropdownType]) {
    switch (dropdownType) {
    case 'value': {
    delete configDropSelectedType[feature][type].anomaly;
    configDropSelectedType[feature][type][dropdownType] = true;
    break;
    }
    case 'anomaly': {
    delete configDropSelectedType[feature][type].value;
    configDropSelectedType[feature][type][dropdownType] = true;
    break;
    }
    default:
    return;
    }
    this.setState({configChanged: true});
    }
    this.setState({configDropSelectedType: configDropSelectedType});
    break;
    }

4.比较config和记录修改下拉框类型的state,上传数据

1
2
3
4
5
6
7
8
9
10
11
12
13
//比较configDropSelectedType和config,去除多余的数据
const config = Immutable.fromJS(this.state.config).toJS();
for (const akey in config.data) {
for (const bkey in config.data[akey]) {
if (bkey !== 'disabled') {
if (this.state.configDropSelectedType[akey][bkey].value) {
delete config.data[akey][bkey].anomaly;
} else if (this.state.configDropSelectedType[akey][bkey].anomaly) {
delete config.data[akey][bkey].value;
}
}
}
}

学习到一个技巧:修改原生dom的class样式:

1
2
3
const node = this.refs.promotion_bonus_fraud;
node.classNmae = 'scene-content scene-content-2';
node.setAttribute('class', 'scene-content scene-content-2');

还有输入框内容校验等,不多赘述。

至此,项目写完了。全部原生代码,没有使用任何UI组件库。原因是,觉得material-ui下拉框太丑,引入ant-design dropdown会导致bundle.js(未压缩)多了6M,权衡下手写了个下拉框组件,花了点时间。
其次就是组织业务事件频次的数据上花了不少时间。再一次深刻的感受了下,不合理的设计会给实现带来很大的困难。

数组去重

#js数组去重
暴力做https://leetcode.com/problems/remove-duplicates-from-sorted-array/题,报超时错误。从题库的输入看,测试数据一个范围在[-999,9999],长度为21998的有序数组。剔除重复项后长度为10999。

####暴力代码:

1
2
3
4
5
6
7
8
9
10
11
var removeDuplicates = function(nums) {
for(var i = 0 ; i < nums.length; i++){
for(var j = i + 1; j < nums.length; j++){
if(nums[i] === nums[j]){
nums.splice(j,1);
j--; //nums剔除了重复数,相应的j的index-1
}
}
}
return nums.length;
};

chrome控制台用了274ms,一般ACM超时不都是1000ms么。
假装自己会计算算法时间,两次循环用了O(n*n),splice时间不知。

####Object keys方法:

1
2
3
4
5
6
7
8
var removeDuplicatesByObject = function(nums){
var obj = {};
for(var i = 0; i < nums.length; i++){
obj[nums[i]] = 0;
}
var arr = Array.from(Object.keys(obj)); //arr内容变成了string
console.log(arr.length);
}

平均耗时约12ms。

####es6 Set方法

1
2
3
4
var removeDuplicatesBySet = function(nums){
var arr = Array.from(new Set(nums));
console.log(arr.length);
}

耗时26ms

####看看lodash去重时间
4ms!!!

react autoFocus问题总结

react autoFocus问题总结

标签(空格分隔): react


有个组件,展开时input需要自动focus.

<MyComponent>
    <input autoFocus = {true} />
</MyComponent>

这样在组件加载后(componentDidMount)input可以自动focus了。
测试后发现,focus只会在mounted后触发,当nextProps来了之后就不能autoFocus了。

解决办法:

class MyComponent exetends React.Component{
    componentWillReceiveProps(nextProps){
         //make sure render returned before following code
         this.refs.input.focus();
    }
    componentDidMount(){
        this.refs.input.focus();
    }
    render(){
    <input ref = "input"/>
}

组件加载完成和收到nextProps后都调用一次focus(), ==,好像有更好的解决办法,=我查看一下react生命周期。15.4里componentDidUpdate()是个更好的选择。