GithubHelp home page GithubHelp logo

blog's Introduction

blog

梁少峰的个人博客

订阅请点 watch, 收藏请点 star, 不要点 fork

想在微信上订阅?欢迎关注公众号"前端控"

2018年

  1. JSON.parse 三种实现方式
  2. 前端之问,程序员之问,职业之问
  3. 浏览器缓存、CacheStorage、Web Worker 与 Service Worker
  4. Vue SSR Demo

2017年

  1. 单页面应用路由实现原理:以 React-Router 为例
  2. 图解 HTTPS:Charles 捕获 HTTPS 的原理
  3. React 事件代理与 stopImmediatePropagation
  4. 最小编辑距离问题:递归与动态规划
  5. preact 源码学习系列之二:组件的渲染与更新
  6. preact 源码学习系列之一:JSX解析与DOM渲染
  7. V8引擎探索:如何注入全局变量
  8. webpack源码学习系列之三:loader 机制
  9. webpack源码学习系列之二:code-splitting(代码切割)
  10. webpack源码学习系列之一:如何实现一个简单的webpack
  11. 如何实现一个异步模块加载器--以requireJS为例

2016年

  1. Mixin、多重继承与装饰者模式
  2. 你不知道的回调、异步与生成器
  3. 数组遍历、for...of、Interator接口与迭代器模式
  4. vue源码学习系列之十一:组件化原理探索(父子组件通信)
  5. vue源码学习系列之十:组件化原理探索(动态props)
  6. vue源码学习系列之九:组件化原理探索(静态props)
  7. vue早期源码学习系列之八:如何实现"v-repeat"列表渲染
  8. vue早期源码学习系列之七:如何实现"v-if"条件渲染
  9. vue早期源码学习系列之六:如何实现计算属性
  10. vue早期源码学习系列之五:批处理更新DOM
  11. vue早期源码学习系列之四:如何实现动态数据绑定
  12. vue早期源码学习系列之三:如何写一个watch库
  13. vue早期源码学习系列之二:如何监听一个数组的变化
  14. vue早期源码学习系列之一:如何监听一个对象的变化
  15. 如何编写一个vue自定义指令
  16. 如何开发一个移动web UI组件库:适配篇
  17. 如何开发一个移动web UI组件库:vue插件篇
  18. 如何开发一个移动web UI组件库:构架篇
  19. Hybrid App中web资源的离线存储与更新机制研究
  20. 我为什么要研究Hybrid App
  21. 利用hammer.js解决300ms延时
  22. JSBridge实现原理探索:以toast为例
  23. 真机远程调试:chrome://inspect
  24. thief系列之四:从实现getJSON中探索XHR和promise
  25. 浏览器history api的研究
  26. 浅谈http报文与请求体格式
  27. Mongodb与Mongoose的学习体会
  28. 自己动手写一个GithubStarManager
  29. thief系列之三:从实现链式调用中看类数组对象与级联
  30. babel初学者的一些常见误区
  31. 如何在redux中捕获处理ajax请求错误
  32. 使用react-transition-group引发的this.setState异步问题
  33. webpack打包bundle.js体积大小优化
  34. webpack打包bundle.js依赖分析
  35. thief系列之二:从获取DOM和增删类中看js如何构造一个类
  36. 异步回调更优雅的解决方式:async
  37. react-css-transition实战
  38. thief系列之一:总序&&从第一行代码开始
  39. 将数组转化成自定义hash键的对象
  40. redux使用的反思:状态存储
  41. 交互动画系列之三:react动画
  42. 交互动画系列之二:赛贝尔曲线
  43. 交互动画系列之一:解决bundle.js首次加载动画问题
  44. rap与mock:接口定义与生成平台
  45. try...catch、express与co错误捕获
  46. 自己动手写todolist的后端
  47. nodejs redis实战--如何写登录模块
  48. ie兼容性问题记录
  49. ES6 generator 、yield 与co
  50. shell.js与yargs命令行开发
  51. 与脚手架大战:回合2
  52. 与脚手架大战:回合1
  53. 自己动手写express中间件
  54. nodejs使用sequelize操作数据库
  55. nodejs使用mysql包操作数据库
  56. Selectivizr:让IE6~IE8支持CSS3高级选择器
  57. 终端交互——inquire.js
  58. 变量检查校验相关:is.js、validator 、JSVerbalExpressions
  59. How to remove "sudo" when npm install -g
  60. 一些小技巧的总结
  61. IE8 max-width失效引起的对meta标签的思考
  62. IE8和IE9跨域请求数据兼容性解决方案
  63. jquery.cursor.js:自己编写的光标控制插件
  64. es5-shim和es5-sham
  65. Error: Does Not Satisfy Its Siblings' peerDependencies Requirements
  66. 函数自定义属性
  67. 浏览器特性检测工具:Modernizr
  68. 利用缓存缩短npm install的时间
  69. ie8 chartjs兼容性解决方案
  70. ie8 placeholder兼容性解决方案
  71. ie8 上传文件后提示下载文件
  72. 从gulpfile.js的warning联想到软件的腐败
  73. redux组织代码之:分割action
  74. css样式融合与类继承
  75. react滚动加载之————react-lazy-load
  76. react组件动态处理className
  77. scss-lint实战碰到的一些问题以及解决方案
  78. 全局css的终结——css module
  79. 代码质量之:scss-lint
  80. 组件编写规范之:proptypes

2015年

  1. react-router 与 history的版本匹配问题
  2. react-native:寻找像写scss那样写react native 样式的方法
  3. 初探react-native踩到的一些坑以及解决方案
  4. 在react中使用箭头函数引发的对this的思考
  5. 代码质量之:eslint
  6. 单元测试之:mocha和chai
  7. redux精简代码之--actionType与redux-action-utils
  8. redux精简代码之--抽象select函数和mapDispatchToProps函数
  9. react 禁止“事件冒泡”
  10. config.js文件引入env字段区分测试和开发环境
  11. gulp-preprocess 让你的项目只需要一个配置文件
  12. 利用代码片段偷懒:dash snippets
  13. git技巧之 --set-upstream
  14. code review
  15. 记一次如何找到Object.assign()编译方法的历程
  16. babel配置文件的使用
  17. 初探lodash

捐助

为什么要捐助?一篇好文章可以帮助你节省大量的时间,而你的时间是相当宝贵的。 向文章的作者提供小额捐助,可以鼓励作者写出更好的文章。这是一种良性循环,现在就行动吧! 鼓励留言,让我认识你。

打赏

其他订阅方式

我们都知道,GitHub Blog 的订阅方式用起来实在是不尽人意,star 的话接收不到新 issue 通知,watch 的话有关无关通通都会接到通知,不想看的还得一个一个 unsubscribe,挺麻烦的。怎么办呢?下面是一些可供参考现成解决方案。

  1. github-blogs 优化意见收集, By yutingzhao1991
  2. 如何使用 Feed 订阅 GitHub Issues, By 孙士权

blog's People

Contributors

youngwind avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

blog's Issues

初探lodash

目录

  1. array
  2. chain

Array

1.1 _.chunk(array, [size=1])平均分配数组到一个新的数组中

console.log(_.chunk(['a', 'b', 'c', 'd', 'e'], 1));
console.log(_.chunk(['a', 'b', 'c', 'd', 'e'], 2));
console.log(_.chunk(['a', 'b', 'c', 'd', 'e'], 3));
console.log(_.chunk(['a', 'b', 'c', 'd', 'e'], 4));
============================
[ [ 'a' ], [ 'b' ], [ 'c' ], [ 'd' ], [ 'e' ] ]
[ [ 'a', 'b' ], [ 'c', 'd' ], [ 'e' ] ]
[ [ 'a', 'b', 'c' ], [ 'd', 'e' ] ]
[ [ 'a', 'b', 'c', 'd' ], [ 'e' ] ]

1.2 _.compact(array) 数组去除null、0、false、“”、undefined、NaN项

console.log(_.compact([0,1,'',2,NaN,false,undefined,null,'3']))
=================================
[ 1, 2, '3' ]

1.3. _.difference(array,[values]) 差异对比,返回array中不存在[values]中的值

console.log(_.difference([1,2,3,4,5,6],[4,2]));
console.log(_.difference([1,2,3,4,5,6],[4,6,7]));
console.log(_.difference([1,2,3,4,5,6],[1,2,3]));
=============================================
[ 1, 3, 5, 6 ]
[ 1, 2, 3, 5 ]
[ 4, 5, 6 ]

1.4. _.drop(array,[n=1]) 从数组头删除n个元素,相当于unshift方法

_.drop([1,2,3])     => [2,3]
_.drop([1,2,3],2)  => [3]
_.drop([1,2,3],5)  => []

1.5 _.dropRight(array,[n=1]) 从数组尾删除n个元素,相当于pop方法

_.dropRight([1, 2, 3]);
// → [1, 2]

_.dropRight([1, 2, 3], 2);
// → [1]

_.dropRight([1, 2, 3], 5);
// → []

_.dropRight([1, 2, 3], 0);
// → [1, 2, 3]

1.6 _.dropRightWhile
1.7 _.ropWhile

1.8 _.fill(array, value, [start=0], [end=array.length]) 从array[start]到array[end]替换为value,不包括array[end]

var array = [1, 2, 3];

_.fill(array, 'a');
console.log(array);
// → ['a', 'a', 'a']

_.fill(Array(3), 2);
// → [2, 2, 2]

_.fill([4, 6, 8], '*', 1, 2);
// → [4, '*', 8]

函数自定义属性

情景

假设我们要完成这样一个需求:页面内有一个按钮和一个数字,每次点击这个按钮,数字都会加1。以前我是这样写的。

var num = 0;
function add(){
   num ++;
   return num;
}
$('btn').click(function(){
  $('#num').html(add());
});

需求抽象来描述应该是这样的:有一个变量A和函数B,变量A只会被函数B改变,其他的函数也会读取变量A,但是不会改变它。上面的做法虽然也可以,但是却引入了两个全局变量。明明num和add是一体的,为什么非得占用两个命名空间呢?这种情况可以用函数自定义属性来解决。

函数自定义属性

add.num = 0;
function add(){
   return ++add.num;
}
$('btn').click(function(){
  $('#num').html(add());
});

在js中,函数是一种特殊的对象,但它毕竟也是对象,可以拥有自己的属性值,这样就可以使命名空间更加整洁了。(PS:之所以一开始可以定义add.num = 0,那是因为函数声明被提升了。)

代码质量之:eslint

前言

以前写代码一般只关注正确与否,但是随着技术的不断深入,开始重视代码规范与否,再加上进来着力推进团队的code review,所以有了eslint的这个探索,发现这是个好东西,跟大家分享一下。

使用方法

安装

npm install eslint

初始化

eslint --init

初始化之后会生成一个配置文件.eslintrc,所有跟eslint相关的配置都写在这儿。

使用

// 检查test1.js文件
eslint test1.js

踩到的一些坑

因为目前我常用的技术栈是react+redux+webpack+babel+gulp,所以eslint势必要集成到这里面去。

1. 如何让eslint支持jsx语法

当前eslint的稳定版本并不支持jsx,但是可以通过插件eslint-plugin-react完成。

2. 如何在webpack打包的时候进行eslint检查

解决方案:eslint-loader
按照里面的流程走就可以了,但是发现下面的这个配置好像是多余的。

module.exports = {
  eslint: {
    configFile: 'path/.eslintrc'
  }
}

估计原因是因为eslint会自动从当前目录开始自动搜索.eslintrl配置文件。

3. 提示require is not defined 和module is not defined

2015-12-26 9 10 37

google、github、stackoverflow了一轮都没找到答案,后来在eslint的官网说明中找到了答案。(这个教训了我了解一个新东西的时候记得先把它的官方说明文档过一遍😢)

2015-12-26 9 59 52

大概的原因是因为require和module都属于common.js里面的东西,webpack打包的时候能识别,但是假如我把环境只设定为浏览器的话就不能识别了,所以得在环境添加上common.js配置项。
大概长这个样子。

"env": {
    "es6": true,
    "browser": true,
    "commonjs": true,
  },

4. 提示error React is defined but never used no-ununsed-vars

当我们在写react的时候势必要引入react,但是文件中不一定会显示的调用react,那么eslint就会报错,“error React is defined but never used no-ununsed-vars”。因为eslint只会检查原本的js文件,不会检查编译过后的js文件。所以解决这个问题有以下两个方案。
(1)var React = require("react"); // eslint-disable-line no-unused-vars
eslint提供了忽略某些文件某些代码行的功能,
2016-01-01 10 03 58
(2)直接使用eslint-plugin-react插件,具体情况可以参考这个。eslint/eslint#4821

5. switch case 缩进问题

eslint默认检查switch case语句的时候是要求没有缩进的,也就是这样的。

switch (action.type) {
case ActionTypes.ADD:
  return Object.assign({}, state, {
    data: state.data + 1
  });
default :
  return state;
}

这能忍?!
解决办法是在indent配置空格

"indent": [
      1,
      2,
      {
        "SwitchCase": 1
      }
    ]

参考资料:http://eslint.org/docs/rules/indent

5. 不识别部分es6语法

我在redux项目中想这样子写reducer。

return {
        ...state,
        currentQuestionNum: state.currentQuestionNum + 1,
        hoverOption: -1,
        selectedOption: -1
      };

eslint在进行语法检查的时候说不认识这个"...."。
解决方案:babel-eslint
这个可以让eslint支持所有babel能支持的语法

在react中使用箭头函数引发的对this的思考

前言

用es6一段时间了,某次想把组件内部的函数都改写成箭头函数。比如

render : function(){
  console.log(this.props);
}

改写成下面这样的

render () => {
  console.log(this.props);  // 报错,this是undefined
}

结果显示this是undefined,我就感觉箭头函数的this有坑,研究了一番,发现还真的不一样。

箭头函数this指向与普通函数不同

箭头函数的this指向函数定义时的作用域,普通函数的this指向函数调用时的作用域。
因为箭头函数没有自己的上下文。
完整地例子参考这里:http://jsbin.com/vetecagupa/edit?js,console
注:在非严格模式执行

这个例子会输出1,因为普通函数的this指向函数被调用时候的作用域,也就是demo

var x = 10;
var demo = {
    x : 1,
    test1 : function() { console.log(this.x); },
    test2 : function() {
        this.test1();
    }
};

demo.test2();  // 1  

这个例子会输出10,因为test1函数在定义的时候处于global作用域,调用的时候this维持不变。

var x = 10;
var demo2 = {
    x : 1,
    test1 : () => { console.log(this.x); },
    test2 : function() {
        this.test1();
    }
};

demo2.test2();  // 10

这个例子会报错,因为函数test2定义的时候处于global作用域,所以调用的时候this指向global,而global中不存在test2方法。

var x = 10;
var demo3 = {
    x : 1,
    test1 : () => { console.log(this.x); },
    test2 : () =>{
        this.test1();
    }
};

demo3.test2();  // error: this.test1 is not a function

回到render函数

搞清楚了箭头函数的this问题,但是由于我还没搞清楚react定义组件的时候作用域的问题,所以暂时没办法搞明白如何在组件中使用箭头函数,以后有时间再研究研究,未完待续。。。。

Selectivizr:让IE6~IE8支持CSS3高级选择器

问题

IE6~IE8不支持CSS3的高级选择器,比如nth-child(),nth-of-type()等等,非常的不方便,selectivizr帮我们完成了这件事情。

解决方案

1. 使用方法

// index.html
<link rel="stylesheet" href="/css/index.css"/>
<table>
  <tr>
    <td>1</td>
    <td>2</td>
    <td>1</td>
    <td>2</td>
  </tr>
  <tr>
    <td>3</td>
    <td>4</td>
    <td>3</td>
    <td>4</td>
  </tr>
  <tr>
    <td>5</td>
    <td>6</td>
    <td>5</td>
    <td>6</td>
  </tr>
  <tr>
    <td>7</td>
    <td>8</td>
    <td>7</td>
    <td>8</td>
  </tr>
</table>

<script src="/js/jquery-1.12.0.js"></script>
<script src="/js/selectivizr2.js"></script>
// index.css
tr:nth-child(3){
    background-color: red;
  }

2. 结果

IE8下效果
2016-03-13 10 42 05

3. 必须要注意的事情

  1. 原库selectivizr已经很久没有人维护了,而且也没有bower版本,使用起来很不方便。所以有了fork版本selectivizr2
  2. selectivizr必须搭配下面js库的任意一个使用。
  • jQuery (1.3+)
  • Dojo (1.5.0+)
  • Prototype (1.6.1+)
  • Yahoo UI Library (2.8.0+)
  • DOMAssistant (2.8.0+)
  • MooTools (1.3+)
  • NWMatcher (1.2.3+)

3.样式必须是link:css引入的,不能是行内样式,也不能是内联样式。因为selectivizr的工作原理是**通过ajax请求css文件,然后重新解析css文件,然后操作DOM元素添加上相应的类名。**如下图所示。
2016-03-13 11 27 50
4.由于css文件是ajax请求得来的,由于js同源策略的限制,css请求的位置必须跟html页面同域。(media.xxx.com和www.xxx.com不是同域)这个问题比较严重,因为在大型网站中,为了加快网页响应速度,一般都会通过多级域名加快静态资源下载速度。或许可以用跨域的思路来解决它,有待研究。

从gulpfile.js的warning联想到软件的腐败

缘由

昨天在看《程序员修炼之道》,其中说到“破窗理论”是导致程序腐败的原因。忽然联想到前两天跟同事在处理某些gulp的warning时候的分歧。

情况

2016-02-18 5 26 51
这是我们在启动一个项目装包时候碰到的warning(其实这个warning我已经看到过好多次了,不过一直忽视它)当时我认为时间紧迫,这些warning并不影响开发,不必理会。同事则认为应该改过来。

体会

其实这些warning虽然不影响功能,但是却是一个糟糕的做法,在最求足够好的程序员眼中是不可忍受的(当然,足够好也应该充分考虑时间因素)。当遇到“破窗”的时候,正确的做法是修复它。如果时间不允许,则应该设法让其不扩大影响(比如不再使用这种糟糕的做法)或者让其更加醒目以便有时间的时候改进(比如添加todo注释)

组件编写规范之:proptypes

前言

随着组件写得越来越大,越来越复杂,组件对自身的自定就显得越来越重要,要是一个组件没有任何关于自身的说明,别人怎么调用你的呢?

组件必须写proptypes

参考react的官方说明文档:
http://facebook.github.io/react/docs/reusable-components.html#prop-validation
组件可以通过proptypes校验传进来的数据类型,当向 props 传入无效数据时,JavaScript 控制台会抛出警告。除了数据校验以外,proptypes也是一个很好的说明组件调用api的地方。
注意为了性能考虑,只在开发环境验证 propTypes。虽然文档中列举了各个规则,但是在这里却没有明确指出如何实现只在开发环境严重propTypes这一个功能。后来我找到了集成在webpack当中方法。

如何做到“只在开发环境验证propTypes"?

react官方说明文档:
https://facebook.github.io/react/downloads.html
里面写到:

We provide two versions of React: an uncompressed version for development and a minified version for production. The development version includes extra warnings about common mistakes, whereas the production version includes extra performance optimizations and strips all error messages.
If you're just starting out, make sure to use the development version.

Note: by default, React will be in development mode. To use React in production mode, set the environment variable NODE_ENV to production (using envify or webpack's DefinePlugin). A minifier that performs dead-code elimination such as UglifyJS is recommended to completely remove the extra code present in development mode.

显然,react本身是有两个版本的:开发版本和线上版本。开发版本会进行错误检测,包括proptypes的检测,但是线上版本不会。其中的区别在于环境变量。后一段话指出,可以通过envify或者webpack定义环境变量达到目的。因为我用的是webpack,所以我找到了webpack对应的解决方案。

new webpack.DefinePlugin({
  "process.env": {
    NODE_ENV: JSON.stringify("production")
  }
});

具体参考例子:webpack/webpack#292 (comment)

终端交互——inquire.js

起因

前些日子在用yeoman自定义自己的脚手架的时候忽然对终端交互产生了兴趣。
这些下拉框,单选框,复选框到底是怎么做的呢?
2016-03-12 5 16 13
后来了解到yeoman用的是**inquirer.js**。

使用方法

1. 安装

npm install inquirer

2. 使用

var inquirer = require("inquirer");
var validator = require("validator");  // 我使用了validator校验手机号

var questions = [

 //是否类型
  {
    type: "confirm",
    name: "sex",
    message: "Are you male?",
    default: false
  },
// 输入类型,添加校验
  {
    type: "input",
    name: "phone",
    message: "What's your phone number",
    validate: function (value) {
      return validator.isMobilePhone(value, 'zh-CN') ? true : "Please enter a valid phone number";
    }
  },

  //select下拉选项
  {
    type: "list",
    name: "weight",
    message: "How much is your weight",
    choices: ["Large", "Middle", "Small"],
    filter: function (value) {
      return value.toLowerCase();
    }
  },
];

inquirer.prompt(questions, function (answers) {
  console.log(answers);
});

3. 结果

2016-03-12 10 10 35

react 禁止“事件冒泡”

标题的说法其实不太准确,因为react原生的事件系统本身就是事件代理的,意味着事件会一直冒泡到document进行绑定。所以,使用普通的**event.stopPropagation();**是没有办法在react中实现阻止事件“冒泡”类似的操作的。
stackoverflow和github issue上都有相关的讨论,比如

  1. http://stackoverflow.com/questions/24415631/reactjs-syntheticevent-stoppropagation-only-works-with-react-events
  2. https://github.com/facebook/react/search?q=stopPropagation&type=Issues&utf8=%E2%9C%93

最后我找到了一个比较简单地解决方法,就是 react-native-listener

// before use react-native-listener
<a style={hotSpotStyle} onClick={this.handleClick}></a>
// after use react-native-listener
<NativeListener key={index} onClick={this.handleClick}>
      <a style={hotSpotStyle}></a>
</NativeListener>

参考地址:https://www.npmjs.com/package/react-native-listener

一些小技巧的总结

最近项目中用得比较多的ejs渲染和表单,有些小问题比较有趣,值得记录下来。

1. 表单自动提交

在form表单中有且只有一个input框的前提下,当你在这个唯一的input框下按下enter键的话,表单会自动提交,url上会带上表单参数,页面会自动刷新 奇怪的是,当页面的input框多于1个就不会出现这样的情况。有个人专门对此作了试验,参考这里

2. ejs后端渲染的时候前端js如何拿到数据

我承认,在用ejs后端渲染的时候前端js想要拿到渲染变量值得方法真的不太优雅,不过有时候也是没有办法的事儿。因为ejs解析完成之后只可能传输纯文本,所以想要接收Object类的对象需要进行一些转换。

// 后端渲染的时候先将变量字符串化
var tempDemo = '<%= JSON.stringify(demoObject) %>';

// 前端js执行的时候再把字符串转化成JSON
var demo = JSON.parse(tempDemo);

3. ejs渲染逻辑是否过重?

以前做某个项目的时候就用过ejs,当时把很多的判断逻辑都写在ejs当中去,代码长得像这样。

<%if(myData.testArr){%>
    <%for(var i=0;i<myData.testArr.length;i++){%>
        <input type="checked"<%if(myData.testArr[i].checked){%> checked<%}%>/>
    <%}%>
<%}%>

代码中存在大量的if和片段冗余,可读性真的非常差!!比js字符串拼接还差!修改起来也非常麻烦。
所以这次做项目的时候我特意将这些复杂的判断逻辑交给js去处理,ejs只完成类似for循环输出之类的功能,感觉这样用模板渲染比较合理一些。
参考资料:http://www.toobug.net/article/how_to_design_front_end_template_engine.html

给你的select添加上placeholder

我们都知道input和textarea都有placeholder属性,那么能不能在select下拉框中也实现类似的功能呢?答案是可以的。
demo

<select name="source" id="source">
   <option value="" disabled="" selected="" hidden="">请选择来源</option>
   <option value="1">个人</option>
   <option value="2">学校</option>
   <option value="3">平台</option>
</select>

参考资料:http://stackoverflow.com/questions/5805059/how-do-i-make-a-placeholder-for-a-select-box/5859221#5859221

react组件动态处理className

前言

在组件开发的时候我们常常会碰到类似这样的情况:满足某个特定条件的时候给某元素添加上一个类,否则不添加。之前一直用的方法是这样的:

render:function () {
  var demoClassName = "wrap";
  if (this.props.active) {
    demoClassName += " active";
  }
  return (
    <div className={demoClassName}></div>
  )
}

抛却局部css的概念不说,这样子写的代码好搓啊!肯定有更好的解决方案,后来我在这里找到了答案。大概的样子是这样的。

render: function() {
  var cx = React.addons.classSet;
  var demoClassName = cx({
    'wrap': true,
    'active': this.props.active,
  });
  // same final string, but much cleaner
  return <div className={demoClassName}></div>;
}

不过应该存在将局部css和这种动态加载类名结合起来的方法,以后有时间要研究一下。

nodejs使用mysql包操作数据库

问题

这年头,不会点node都不好意思说自己是前端。ok,下面显示的是一个添加用户和显示所有用户的一个简单demo。

准备条件

  1. express
  2. express generator生成的项目
  3. npm install mysql --save
  4. 电脑安装了mysql
  5. 创建数据库test和数据表mytable,mytable有两个字段。id:int(4), name:varchar(20)(当然,名字你可以随便取,下面相应的改变代码就好了)

使用方法

1. 把mysql的配置写在配置文件中

// config.js
module.exports = {
  // MySQL数据库联接配置
  mysql: {
    host: '127.0.0.1',
    user: 'root',
    password: '',
    database:'test', // 数据库名字
    port: 3306
  }
};

2. 添加路由

// routers/users.js
// 增加用户
router.get('/addUser', function (req, res, next) {
  user.add(req, res, next);
});

// 返回所有用户
router.get('/allUser', function (req, res, next) {
  user.all(req, res, next);
});

3. 定义sql查询语句

// 定义文件 controller/userSqlMapping.js
var user = {
  insert:'INSERT INTO mytable(id, name) VALUES(?,?)',
  queryAll: 'select * from mytable'
};

module.exports = user;

4. 编写控制器中间件

// controller/user.js

"use strict";

// 实现与MySQL交互
var mysql = require('mysql');
var config = require('../common/config.js');
var $sql = require('./userSqlMapping');

module.exports = {

  // 添加用户
  add: function (req, res, next) {
    var param = req.query || req.params;
    var connection = mysql.createConnection(config.mysql);
    connection.connect();
    connection.query($sql.insert, [param.id, param.name], function (err, rows, fields) {
      if (err) {
        res.json({
          code: '1',
          msg: '操作失败'
        });
      }
      res.json({
        code: 200,
        msg: "增加成功"
      });
    });
    connection.end();
  },

  // 返回用户信息
  all: function (req, res, next) {
    var connection = mysql.createConnection(config.mysql);
    connection.connect();
    connection.query($sql.queryAll, function (err, rows, fields) {
      if (err) {
        res.json({
          code: '1',
          msg: '操作失败'
        });
      }
      res.json({
        code: 200,
        msg: rows
      });
    });
    connection.end();
  }
};

5.演示结果

添加用户
2016-03-14 8 15 52

查询用户
2016-03-14 8 17 01

查看数据库
2016-03-14 8 17 50

问题

  1. 每回都需要打开和关闭mysql连接,这显然是性能上的浪费,而且也会造成代码上的冗余,应该寻找解决方案。
  2. 查询语句是通过硬编码预先设定好的,这显然不够灵活,不能满足以后千奇百怪的各种sql操作,应该有封装好的包可以使用。
  3. 错误码是随便写的,是否有业内通用的错误码?而且错误码应该定义在一个统一的地方。
  4. 错误抛出与res返回错误提示应该是什么样的关系?
  5. 没有进行参数的校验。

参考资料:
http://www.alloyteam.com/2015/03/sexpressmysql/

利用缓存缩短npm install的时间

起因

在工作中npm install [package]是家常便饭,但是很多时候装包都消耗不少时间,特别是网络不好的时候。其实每次安装一个包,流程是这样的:

  1. npm从register上面抓包
  2. 放到本地缓存
  3. 从本地缓存解压到node modules

既然曾经安装过的包都在本地了(如图),为什么再次安装的时候不能直接在本地缓存中拷贝过来,从而节省很多时间呢?
2016-02-29 4 08 47

解决办法

1. npm --cache-min

npm自己提供了cache机制,但是并不完善。

(1)如果指定模块不在缓存目录,那么 npm 会连接 registry,下载最新版本。这没有问题,但是如果指定模块在缓存目录之中,npm 也会连接 registry,发出指定模块的 etag ,服务器返回状态码304,表示不需要重新下载压缩包。
(2)如果某个模块已经在缓存之中,但是版本低于要求,npm会直接报错,而不是去 registry 下载最新版本。

2. 其他工具

  1. npm-proxy-cache
  2. local-npm
  3. npm-lazy

三种工具我都试过。
npm-proxy-cache和local-npm都没能成功缩短时间(暂时排除不出来那里的问题),而且local-npm运行的时候会在当前目录生成很多文件。
npm-lazy倒是弄好了,拿express测了一下,不使用缓存需时超过1分钟,使用缓存7秒就可以了。

npm-lazy的使用方法

安装

npm install -g npm_lazy

配置

npm config set registry http://localhost:8080/

启动服务

npm_lazy

参考资料:

  1. http://www.ruanyifeng.com/blog/2016/01/npm-install.html
  2. https://addyosmani.com/blog/using-npm-offline/

es5-shim和es5-sham

起因

在做项目的时候发现ie8不兼容Array.prototype.forEach方法,找到了两个解决方案,jquery.each和es5-shim

解决方案

1. jquery.each

$.each(array, function(key, value){
   // array[key] === value;
});

2. es5-shim

第二种解决方案是检测是否有这个方法,如果没有,自定义它,es5-shim帮我们做好了这个事情。

安装

bower install es5-shim --save

使用

<script src="./es5-shim.js"></script>
<script src="./es5-sham.js"></script>
// load your script

参考资料:
http://stackoverflow.com/questions/412447/for-each-javascript-support-in-ie

ie8 chartjs兼容性解决方案

原因

项目中要用到数据可视化,我选择了Chart.js,确实挺好用的。但是因为Chart.js是用canvas实现的,IE8及以下不支持canvas。官方给出的解决方案在这里
2016-02-27 11 27 32

Attention!

1. chartjs初始化必须在页面刚刚加载的时候完成

如果只是引入excanvas的话,在IE8中还是没法工作。

Initialise charts on load rather than DOMContentReady when using the library, as sometimes a race condition will occur, and it will result in an error when trying to get the 2d context of a canvas.

此处指出,用excanvas的话,初始化chartjs的操作必须在页面刚刚加载的时候就进行,否则程序会在获取canvas上下文的时候出错。
我们去看看excanvas的官方例子

<head>
    <title>ExplorerCanvas Example 1</title>
    <!--[if IE]><script type="text/javascript" src="../excanvas.js"></script><![endif]-->
    <script type="text/javascript">
        function load() {
            canvas = document.getElementById("cv");
            // ....
        }
    </script>

</head>
<body onload="load();">
    <canvas id="cv" width="400" height="300"></canvas>
</body>

2. 取消动画

New VML DOM elements are being created for each animation frame and there is no hardware acceleration. As a result animation is usually slow and jerky, with flashing text. It is a good idea to dynamically turn off animation based on canvas support. I recommend using the excellent Modernizr to do this.

因为excanvas是采用VML实现的,没有硬件加速功能,所以动画会显示断断续续,并且文字会出现闪烁,因此,如果浏览器不支持canvas的话,最好把动画关闭了。

code review

code review很难

code review很难,主要基于以下原因。

  1. 太忙了,连新需求都没完成,哪有时间review。
  2. 惯性,培养新的良好习惯是需要一番挣扎的。

但是,code review很重要

除了一般的HTML、CSS、JS规范,在进行 code review的时候还需要注意以下几点。

  1. 代码是否简单易懂?
  2. 是否存在多余或者重复的代码?
  3. 代码是否尽可能地模块化(组件化)?
  4. 是否有被注释掉的代码?
  5. 是否有可以被库函数代替的代码?
  6. 是否有可以删除的日志或调试代码?
  7. 无效的参数值是否能够处理?
    ……

单元测试之:mocha和chai

前言

本文说4件事。

  1. 简单例子
  2. 简单例子升级版
  3. 测试reducers
  4. 参考资料

简单例子

mocha是众多自动化测试框架之一,chai也是众多断言库之一。

安装

npm install mocha -g
npm install chai -g

编写test.js

// test.js 演示对字符串的split方法的测试
var assert = require('chai').assert;
describe("string#split", function () {
  it("should return an array", function () {
    assert(Array.isArray('a,b,c'.split(',')));
  });
  it('should return the same array', function () {
    assert.equal(['a', 'b', 'c'].length, 'a,b,c'.split(',').length, 'arrays have equal lenght');
    for (var i = 0; i < ['a', 'b', 'c'].length; i++) {
      assert.equal(['a', 'b', 'c'][i], 'a,b,c'.split(',')[i], i + 'element is equal');
    }
  })
});

执行test.js

mocha test

执行结果

2015-11-29-mocha-test


简单例子升级版

上面的代码是不是很多的冗余?假如有很多个describe或者it,该如何组织划分呢?

编写test-pro.js

// test-pro.js
var expected, current;

// 在每个describe之前执行
before(function () {
  expected = ['a', 'b', 'c'];
});
describe('String#split', function () {
  // 在每一个it之前执行
  beforeEach(function () {
    current = 'a,b,c'.split(',');
  });

  it('should return an array', function () {
    assert(Array.isArray(current))
  });


  it('should return the same array', function () {
    assert.equal(expected.length, current.length, 'array have equal length');
    for (var i = 0; i < expected.length; i++) {
      assert.equal(expected[i], current[i], i + 'element is equal');
    }
  });

});

执行结果

2015-11-29-mocha-test2


测试reducers

拆分reducers

原先我的多个reducer是写在一个js文件当中,然后再通过combinereducers结合起来。
后来在写测试用例的时候发现这样不好,所以把每个reducer拆成一个js文件。
然后在rootreducer结合起来,这样就可以针对不同的reducer写单元测试了。

reducer样例add.js

// 假设我有一个add.js的reducer
var ActionTypes = require('../constants/ActionTypes');

function search(state, action) {
  if (!state) {
    state = 0;
  }
  switch (action.type) {
    case ActionTypes.ADD_TAG:
      return state + 1;

    default:
      return state
  }
}

module.exports = search;

编写相应的测试文件search-test.js

// 测试文件search-test.js
var assert = require('chai').assert;

var search = require('../src/reducers/add');

describe("test-search#search", function () {
  it("should return + 1", function () {
    var state = 1;
    var nextState = 2;
    assert.equal(nextState, search(state, {type: "ADD_TAG"}));
  });

  it("should return self", function () {
    var state = 1;
    var nextState = 1;
    assert.equal(nextState, search(state, {type: "Others"}));
  });
});

以此类推,每个reducer写一个测试文件,然后统一放在test目录下面,比如我的目录结构是这样的。
2015-11-29-mocha-test3
2015-11-29-mocha-test4

执行测试

mocha --compilers js:babel-core/register  --recursive
// mocha有很多配置参数,这里的compilers表示测试前用babel对其进行编译,因为我的reducer用了ES6语法。
// --recursive表示级联测试,会搜索并执行本目录下所有的测试

执行结果

2015-11-29-mocha-test5

小技巧:假如不想每次测试都输入这么多东西,可以将这行命令写入package.json的script当中,比如像这样。
2015-11-29-mocha-test6

那么以后只要执行npm test,就相当于执行相对应的命令了。

参考资料

1. [http://mochajs.org/](http://mochajs.org/) 2. [https://github.com/chaijs/chai](https://github.com/chaijs/chai) 3. [http://www.kuqin.com/shuoit/20131204/336779.html](http://www.kuqin.com/shuoit/20131204/336779.html) 4. [http://www.shaynegui.com/javascript-unit-test-chaijs/](http://www.shaynegui.com/javascript-unit-test-chaijs/)

代码质量之:scss-lint

前言

继上篇 #13 之后,我找到了这个scss-lint工具,用来控制scss代码的质量,效果还不错。

使用方法

安装

gem install scss-lint 

在安装的时候我碰到一个问题。

ERROR:  Could not find a valid gem 'scss-lint' (>= 0), here is why:
          Unable to download data from https://rubygems.org/ - SSL_connect retur
ned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed (
https://rubygems.org/latest_specs.4.8.gz)

解决方法:

gem source -a http://rubygems.org/

参考资料:sds/scss-lint#320

使用

scss-lint demo.scss

集成到gulp中之gulp-scss-lint

本来我是希望集成到webpack中的,就像eslint一样。但是没找到合适的loader,所以暂时只能集成到gulp中了。找到一个sasslint-loader,但是不太成熟,而且并非基于scss-lint,所以最后没有采用。

安装gulp-scss-lint

npm install gulp-scss-lint --save-dev

定义scss-lint 任务

gulp.task('scss-lint', function () {
  return gulp.src('src/**/**/*.scss')
    .pipe(cache('scsslint'))
    .pipe(scsslint({
      'config': '.scss-lint.yml'
    }));
});

这里用了gulp-cache提高性能
scss-lint有默认的配置,.scss-lint.yml中的配置会覆盖默认配置。

将scss-lint任务放到gulp-watch中

gulp.task('watch', function () {
  gulp.watch([
    'src/**/**/*.*'
  ], ['scss-lint', 'webpack-dev']);
});

效果图

2016-01-02 2 20 00

config.js文件引入env字段区分测试和开发环境

问题由来

在用redux进行某个项目开发的时候碰到这样的一个问题:我使用redux-devtools作为测试开发工具,像这样。

index.js
const Index = React.createClass({

  render: function () {
    return (
        <div className="container">
          {this.props.children}
          <DebugPanel top right bottom>
            <DevTools store={store}
                      monitor={LogMonitor}
                      visibleOnLoad={true}/>
          </DebugPanel>
        </div>

    )
  }
});

那么问题来了,每次上线的时候都需要删除DebugPanel这一部分代码,等本地bug fix的时候又需要重新加上,这怎么能忍?

解决方法

思路:在config中配置环境字段,然后在js中进行if判断。

config.js
module.exports = {
  // 项目环境
  env: 'dev',
};
index.js
import Config from '../config.js';
const Index = React.createClass({

  /**
   * 根据配置文件中的env字段区分测试环境和正式环境
   * 假如是测试环境,则显示redux-devtools
   * @param config {object} 配置
   * @returns {*}
   */
  returnDevTools: function (config) {

    if (config.env === "dev") {
      return (
          <DebugPanel top right bottom>
            <DevTools store={store}
                      monitor={LogMonitor}
                      visibleOnLoad={true}/>
          </DebugPanel>
      );
    } else {
      return null;
    }
  },

  render: function () {
    return (
        <div className="container">
          {this.props.children}
          {this.returnDevTools(Config)}
        </div>
    )
  }
});

这样看来,凡是跟环境相关的都可以通过类似的方法进行解决。

react滚动加载之——react-lazy-load

前言

以前用jquery开发的时候经常使用jquery.lazyload进行图片的滚动加载,但是到了react体系之后就不太好了,因为不能直接操作DOM元素。所以就找了这个组件react-lazy-load

使用方法

1. 安装

npm install --save react-lazy-load

2. 编写组件

import React from "react";
import LazyLoad from "react-lazy-load";

const Table = React.createClass({
  render: function () {
    return (
      <div>
        <LazyLoad>
          <h1>我是延时加载出来的</h1>
          <img src="http://pic2.ooopic.com/01/03/51/25b1OOOPIC19.jpg" alt=""/>
          <img src="http://img.taopic.com/uploads/allimg/130501/240451-13050106450911.jpg" alt=""/>
          <img src="http://pic.nipic.com/2007-11-09/200711912453162_2.jpg" alt=""/>
          <h1>我是延时加载出来的</h1>
        </LazyLoad>
      </div>
    );
  }
});

module.exports = Table;

3. 问题

这样子的写法有一个问题,那就是页面render的时候,组件table就会render,只是没显示出来而已。这意味着,如果这个组件需要异步请求数据,那么这个异步请求还是没有办法实现滚动到了再请求的。我想了一个解决方案,那就是:“在要滚动加载的组件中取出,然后把写在其父组件当中,亲测有效。

4. 优化

多重LazyLoad能够更好地引用滚动加载。拿上面的组件举例。上面的一个组件包含3张图片,每一张图片都比较高,所以,以每张图片为滚动加载最小单位,而不是以组件为最小单位,显然能够更好地发挥滚动加载。代码大概改成这个样子。

const Table = React.createClass({

  render: function () {
    return (
      <div>
        <h1>我是延时加载出来的</h1>
        <LazyLoad>
          <img src="http://pic2.ooopic.com/01/03/51/25b1OOOPIC19.jpg" alt=""/>
        </LazyLoad>
        <LazyLoad>
          <img src="http://img.taopic.com/uploads/allimg/130501/240451-13050106450911.jpg" alt=""/>
        </LazyLoad>
        <LazyLoad>
          <img src="http://pic.nipic.com/2007-11-09/200711912453162_2.jpg" alt=""/>
        </LazyLoad>

        <h1>我是延时加载出来的</h1>
      </div>
    );
  }
});

nodejs使用sequelize操作数据库

问题

鉴于直接使用mysql包操作数据库带来的各种不方便,见 #43 ,我找到了sequelize,试用了一下,觉得还不错。

使用过程

1. 安装

npm install sequelize --save

sequelize支持如下数据库:MySQL, MariaDB, SQLite, PostgreSQL and MSSQL,不同的数据库需要安装各自的包,因为我操作的是mysql,所以要安装mysql包

npm install mysql --save

2. 创建连接

#43 说过,每次操作都需要打开和关闭连接既耗费性能,也代码冗余。mysql包本身提供方法创建连接池。

var pool  = mysql.createPool({
  connectionLimit : 10,
  host            : 'example.org',
  user            : 'bob',
  password        : 'secret'
});

传统的开发模式是:首先在主程序(如Servlet、Beans)中建立数据库连接;然后进行SQL操作,对数据库中的对象进行查 询、修改和删除等操作;最后断开数据库连接。使用这种开发模式,对于一个简单的数据库应用,由于数据库的访问不是很频繁,只需要在访问数据库时创建一个连 接,用完后就关闭它,这样做不会明显的增大系统的开销。但是对于一个复杂的数据库应用,情况就完全不同:频繁的建立、关闭数据库,会极大的降低系统的性 能,增大系统的开销,甚至成为系统的瓶颈。另外使用这种传统的模式,还必须管理数据库的每一个连接,以确保他们能正确关闭,如果出现程序异常而导致某些连 接未能关闭,将引起数据库系统中的内存泄露,最终不得不重启数据库。因此采用运行速度更快、数据库访问效率更高的数据库技术,以提高系统的运行效率将是至 关重要的。
为了解决这一问题,在JDBC2.0中提出了JDBC连接池技术,通过在客户之间共享一组连接,而不是在它们需要的时候再为它们生成,这样就可以改善资源使用,提高应用程序的响应能力。

sequelize创建连接池

// 配置写在了配置文件中
var sequelize = new Sequelize(config.mysql.database, config.mysql.user, config.mysql.password, {
  host:config.mysql.host,
  dialect:'mysql',
  pool:{
    max:5,
    min:0,
    idle:10000
  }
});

3. 定义数据结构(也就是model啦)

var User = sequelize.define('user', {
  firstName: {
    type: Sequelize.STRING,   // 定义为字符串型
    field: 'first_name'                // 这里的意思是数据库中字段为first_name,取出来的集合中的名字确实firstName
  },
  lastName: {
    type: Sequelize.STRING
  }
}, {
  freezeTableName: true   // table的名字与model的名字相同
});

4. 插入数据

User.sync({force: true}).then(function () {
  // Table created
  return User.create({
    firstName: 'John',
    lastName: 'Hancock'
  });
});

上面的代码有几个地方需要说明。

  1. sequelize采取promise模式。
  2. sync的意思是创建表,准确来说是创建model User代表的表,其中{force:true}的意思是如果之前存在这张表,则删除它。我们可以在源码中看到promise和这个参数发挥的作用。
/**
 * Sync this Model to the DB, that is create the table. Upon success, the callback will be called with the model instance (this)
 * @see {Sequelize#sync} for options
 * @return {Promise<this>}
 */
Model.prototype.sync = function(options) {
  options = options || {};
  options.hooks = options.hooks === undefined ? true : !!options.hooks;
  options = Utils._.extend({}, this.options, options);

  var self = this
    , attributes = this.tableAttributes;

  return Promise.try(function () {
    if (options.hooks) {
      return self.runHooks('beforeSync', options);
    }
  }).then(function () {
    if (options.force) {
      return self.drop(options);  // 这里啊亲!删除这张表哎呦喂!
    }
  })

我不单单想掺入数据,我还想更新,删除,各种查询怎么办?看这里

5. 存在问题

  1. 定义model的方法不通用。sequelize通过define方法定义model,但是这个定义却不能拿到其他的框架中去使用。数据结构应该是一种抽象的描述,不应该具体依赖于任何一种实现。有没有一种抽象地描述可以通用?schemas吗?
  2. 定义model和使用model的代码混在一起了。这就意味着:每次启动node项目,都会重新执行一遍model定义,难道真有这个必要吗?但是如果不执行model定义,下面的User.findOne又如何能够引用得到?

git 技巧之 --set-upstream

下面的情况我们经常遇到:

从develop分支checkout出feature-a分支,干完活之后git push origin feature.假如再次修改的话,还得再次git push origin feature.每次都要打全才能push成功。

但是,其实可以通过 --set-upstream 让remote的某个分支和本地的某个分支关联起来,这样只要git push一下,就会push到对应的远程分支了。

git branch --set-upstream my_branch origin/my_branch

更简单地方法

上面那个命令太长了,老记不住,后来找到一个更简单了。
第一次push的时候:

git push -u origin my_branch

之后就可以直接push和pull了。

ie兼容性问题记录

总会不时地碰到发现一个ie下的兼容性问题,但是又不成体系,就把它们当做集合写在这儿了。
#1.数组末尾多余逗号

var ary = [1,2,3,]

在IE8下会被解析成下面的语句

var ary = [1,2,3,null]

所以,请不要手贱多写逗号...定义对象的时候多写逗号倒是不会出错

var demo = {
   name:"lsf",
}
// 在IE8没问题,据说在IE6/7下会报错

与脚手架大战:回合1

事出有因

随着团队的扩大,项目的增多,脚手架的缺失显得愈发地不可忍受。其问题主要有二:

  1. 每次新建项目都耗时耗力,特别是对于新员工来说,更是有更多的学习和操作成本。
  2. 项目规范难以推行。比方说,都知道需要用scss-lint,eslint等这些进行语法检查,但是大家新建项目的时候往往图快和方便而省略掉引入那些校验,如果能统一从脚手架中生成,只要保证脚手架是规范的,那么会很大程度提高项目的规范度。

yeoman的初尝试

一开始我是想自己写一个的,思路是这样的:

  1. 准备好项目架构文件
  2. 把文件copy到目的文件夹
  3. 执行npm install 和bower install 装包
  4. 执行gulp构建
  5. 执行npm run start 启动程序
    嗯,一切都规划地蛮好的。但是,有一个问题我一直没想通,也没google到,那就是,我如何让终端识别某个我自定义的命令generate-smart呢?也就是说,为啥我 npm install gulp -g之后,在终端输入gulp就能找到这个命令?一番挣扎无果之后我找到了yeoman,忽然发现这就是我要做的东西。一番欣喜若狂之后按照文档和api做了个demo出来。
var generators = require('yeoman-generator');

// 调用shell命令
var process = require('child_process');
var exec = process.exec;

module.exports = generators.Base.extend({
  constructor: function (args, options, config) {
    generators.Base.apply(this, arguments);
  },
  init: function () {
    // 这里还没想好怎么优化,只能先嵌套了!
    console.log('start copy');
    var copy = exec('cp -r ' + __dirname + "/demo/. " + this.options.env.cwd);
    copy.on('exit', function (code) {
      console.log('copy done!');
      console.log('start install bower dependiences');

      var bowerInstall = exec('bower install');
      bowerInstall.on('exit', function (code) {
        console.log('bower dependiences install done.');
        console.log('start install npm dependiences');

        var npmInstall = exec('npm install');
        npmInstall.on('exit', function (code) {
          console.log('npm dependiences install done.');
          console.log('start gulp');

          var gulp = exec('gulp');
          gulp.on('exit', function (code) {
            console.log('gulp done.');
            console.log('start the app....');
            var start = exec("npm run start");
            console.log("please visit http://localhost:3000");
          })
        })
      });
    });
  }
});

现在回头看这段代码,真的是惨不忍睹。。。

  1. 几乎没怎么使用yeoman提供的api,就用了继承和init,其他都是直接通过child_process调用子进程来执行命令的,好粗暴。。。
  2. 子进程嵌套子进程,没办法,那玩意儿是异步的,又没有promise模式,只能这么将就写着了。

反思

  1. 其实我想做脚手架的目的是为了方便公司团队内部用的,而公司团队内部所采用的项目架构与技术栈无非那么两三套,我有必要搞那么灵活度(意味着内部实现难度高)的yeoman吗?
  2. 自己尝试造一下轮子也可以长进很多知识啊。
    于是,贼心不死的我在学习了 inquirer #41 之后,又开始准备自己再写一次脚手架了。

参考资料:

  1. https://segmentfault.com/a/1190000004431257
  2. https://github.com/JST-CN/yeoman-cn/blob/master/wiki-cn/docs/Generators.md

redux精简代码之--抽象select函数和mapDispatchToProps函数

前言

在redux项目中,我们常常使用select函数和mapDispatchToProps函数,前者用于从store中getState(),然后输入到组件的props中去,后者用于给函数自动绑定上dispatch,不必每次调用执行函数都写dispatch。

碰到的问题

因为项目只要稍微大一点就需要结合使用react-router和container的概念。比如我在某个项目中就有以下两个container。

// container index
const Index = React.createClass({

  render: function () {
    return (
        <div className="container">
          {this.props.children}
        </div>

    )
  }
});

const select = (state) => ({
  promotionPage: state.promotionPage.data,
  isFetching: state.promotionPage.isFetching,
  productData: state.productData,
  productIsFetching: state.productData.isFetching,

});

const mapDispatchToProps = (dispatch) => ({
  actions: bindActionCreators(TemplateActions, dispatch)
});

module.exports = connect(select, mapDispatchToProps)(Index);
// container template
const Template = React.createClass({

  /**
   * 根据请求过来的数据合成区块
   * @param blocks {array} 区块数组
   * @returns {*} {JSX}
   */
  combineBlockList: function (blocks) {
    const _self = this;
    return blocks.map(function (block, index) {
      return (
          <div key={index}>
            <WapBlock block={block} type={_self.props.location.query.type}/>
          </div>
      )
    });
  },

  render: function () {
    const {promotionPage,isFetching,productDate, productIsFetching} = this.props;
      return (
          <div>
            {this.combineBlockList(promotionPage.blocks)}
          </div>
      )
  }
});

const select = (state) => ({
  promotionPage: state.promotionPage.data,
  isFetching: state.promotionPage.isFetching,
  productData: state.productData,
  productIsFetching: state.productData.isFetching,

});

const mapDispatchToProps = (dispatch) => ({
  actions: bindActionCreators(TemplateActions, dispatch)
});

module.exports = connect(select, mapDispatchToProps)(Template);

从上面两个container可以看出,select和mapDispatchToProps都是重复的代码,可以也应该抽象出来。
所以我就定义了一个bindProps.js

// bindProps.js
/*
 *   created by liangshaofeng on 2015年12月18日
 *   抽象出每个container所需要的select和mapDispatchToProps
 */

import {bindActionCreators} from 'redux';
import TemplateActions from '../action/template.js';

const select = (state) => ({
  promotionPage: state.promotionPage.data,
  isFetching: state.promotionPage.isFetching,
  productData: state.productData,
  productIsFetching: state.productData.isFetching,
});

const mapDispatchToProps = (dispatch) => ({
  actions: bindActionCreators(TemplateActions, dispatch)
});

module.exports = {
  select,
  mapDispatchToProps
};

然后再两个container中分别import进去就可以了。😊

jquery.cursor.js:自己编写的光标控制插件

项目说明

此插件基于jquery,仓库在这儿,主要用途如下

  1. 操作光标在文本中的位置(textarea和input)
  2. 在制定位置插入文字,选中制定的文字片段
    demo

使用方法

1. 安装

bower install jquery --save
bower install jquery-cursor --save

2. 引入

<script src="jquery.js"><script>
<script src="jquery-cursor.js"><script>

3. 使用

3.1 基本用法

$('#textAreaId').cursor({
  "text":"这是插入的一些文字,哪些位置被选中由下面两个参数决定",
  "beginPos":2,  // 开头从0开始计数
  "endPos":5    // 如果省略,则从beginPos一直选中到text的结尾
);

参考资料:
https://gist.github.com/quexer/3619237

记一次如何找到Object.assign()编译方法的历程

前言

我要用ES6的Object.assign()方法,但是存在兼容性问题,然后使用babel去编译它。没想到从babel6开始babel本身就不完成任何编译,需要安装插件来完成。
我在官网上寻寻觅觅了好久,安装了下面这一大堆插件。

    "babel-core": "^6.2.1",
    "babel-loader": "^6.2.0",
    "babel-preset-es2015": "^6.1.18",
    "babel-preset-react": "^6.1.18",
    "babel-preset-stage-0": "^6.1.18",
    "babel-runtime": "^5.8.25",

结果还是不行,各种百度和google都得不到答案,死的心都有了。。。
后来忽然想到直接在babel的github代码仓库中搜索,结果被我找到了这个。简直泪流满面啊!
2015-12-06 10 53 07

然后再结合babel的配置文件,比如这样就可以啦。
2015-12-06 10 56 11


结论:当你使用某个东西碰到解决不了的困难的时候,可以考虑一下直接在它的github仓库中搜索,或许会有意想不到的答案。

IE8 max-width失效引起的对meta标签的思考

起因

最近在使用max-width的时候发现在IE8下不兼容,具体可以参考这里

解决办法

1. 通过外包div实现

把图片放在div中,并且设置该div的宽度为你原来想让图片的max-width值。

.container { width: 765px;}
img { max-width: 100%;}

2. meta实现

第一种方法治标不治本,之所以出现图片max-width失效的,是因为页面渲染的时候采取了“接近标准模式”,这个跟“标准模式”和“怪异模式”都不同。他们之间的区别和联系可以参考wiki
文档声明meta标签属性影响浏览器将采取何种模式渲染,那么该如何配置这两项才能规避更多的兼容性问题呢?

// 采取H5的文档声明
  <!doctype html>
// 优先采取IE最新版本和Chrome
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
// 让360浏览器 采取极速模式
<meta name="renderer" content="webkit">

参考资料:

  1. http://fex.baidu.com/blog/2014/10/html-head-tags/
  2. http://padding.me/blog/2014/07/04/mode-or-standard/
  3. http://www.zhoujiping.com/archives/2016/01/quirks-standards.html

变量检查校验相关:is.js、validator 、JSVerbalExpressions

原因

在项目实际开发中,我们常常需要重复性地解决一下两个问题。

  1. 变量是否为空?是否为空对象?是否是数组?…… (对象的类型、存在性检测)
  2. 变量是否是电话,是否是url,是否是邮箱……(变量是否满足特定的正则)

有三个工具可以让我们避免重复造轮子。

  1. is.js
  2. validator
  3. JSVerbalExpressions

1. is.js

如果我要判断一个对象是否存在的话,可不是那么容易,请参考阮一峰老师的文章
但是在is.js里面只需要一句
//todo

初探react-native踩到的一些坑以及解决方案

前言

前端技术更新迭代的节奏非常之快,稍微学慢点都有可能被淘汰。最近对于以react和redux为核心的web端技术架构已经渐渐掌握,想要精通还需要很长的时间。react-native与react一脉相承,是我试水app开发的最佳切入点。刚刚开始学习,再加上从来没用过xcode,大大小小的坑肯定踩得不少,列举如下。
#1. react-native init 卡住不动

答案:很可能是vpn问题,请在翻墙的情况下找个好一点的网络。
#2. 每回修改代码都要command+R重新build一下,需要好几秒的时间,不能忍!

答案:在模拟器中选择hardware->shake gesture -> enable Live Reload。 如下图所示。
2015-12-20 4 08 11
#3. 代码出错信息在哪儿显示?调试输出信息在哪儿?怎么调试?

没错,一开始我都不知道在哪儿查看调试结果(哎,没用过xcode的伤不起啊!)
当build出错的时候,模拟器会直接报错,比如像这样。
2015-12-21 9 23 54
身为前端程序员,我第一反应就是寻找能不能在浏览器进行调试,答案是可以的。
在模拟器的hardware-> shake gesture -> debug in chrome,然后浏览器就会打开一个新的窗口,像这样。
2015-12-27 9 50 00
程序中的console.log可以直接在控制台看到,也可以打断点进行调试,太棒了!!
不过这里还有一个问题就是不能直接在浏览器中看到界面。(我暂时还没有找到能再浏览器中看到界面的方法,其实我主要的目的是调整样式啊!!网页开发的时候可以直接查看元素的布局,然后直接编辑实时看到效果的呀!没有这个开发效率瞬间降低了好多啊啊!!)
还好后来找到了好歹找到一个查看样式布局的地方(注意,只能查看,不能直接编辑),那就是hardware->shake gesture -> show inspector ,就能看到像下面这个样子了。
2015-12-20 4 06 08

接下来这方面还有几个点要研究一下:如何在真机上进行测试。如何在chrome上看到界面并且进行调试。(我也知道在chrome上完全地调试是不可能的了,因为ios各种复杂的手势交互chrome就不可能模拟到,所以能做到编辑样式我已经满足了。)
#4. react native支持的样式和原生css支持的样式有哪些区别。

http://reactnative.cn/docs/style.html#content
滚到最底部就可以看到了。
#5. 使用react-native-swiper的时候报错,找不到react-timer-mixin

react-native-swiper1.4版本以上修复了这个问题,升级版本就可以了。

IE8和IE9跨域请求数据兼容性解决方案

原因

IE8和IE9在实现跨域请求的时候使用XDomainRequest自己实现了一套,所以即使是使用jquery1.9.1版本也无法直接兼容IE8,IE9的跨域请求。StackOverflow上的两个问答说得很清楚。

  1. http://stackoverflow.com/questions/24206782/ajax-call-not-working-in-ie8
  2. http://stackoverflow.com/questions/3362474/jquery-ajax-fails-in-ie-on-cross-domain-calls#11267937

解决方案

1. 自己抽象定义一个ajax请求函数

这个方法在上面第二个链接已经有人做好了,但是我嫌有点麻烦,不想在每个项目中都要定义这样一个函数吗?

2. 重定义jquery的$. ajaxTransport方法

有人已经做好了,详见这里。https://github.com/MoonScript/jQuery-ajaxTransport-XDomainRequest

不过使用它有几点非常需要注意

  1. 只能是GET或者是POST请求
  2. POST请求的数据类型必须是text/plain
  3. 只支持HTPP和HTTPS协议
  4. 请求主体和被请求接口协议必须相同,也就是大家要么都是HTTP,要么都是HTTPS
  5. 必须是异步的

babel配置文件的使用

刚刚开始接触babel的时候,是在用webpack打包的时候使用的,比如这样子。
2015-12-18 3 44 42

后来因为要编译Object.assgin(这里还踩过一次坑,见 #3 ),把所有babel参数提出来放在.babelrc文件中,比如像这样就可以了。

.babelrl 文件
{
  "presets":["react","es2015","stage-0"],
  "plugins": ["transform-object-assign"]
}

ie8 placeholder兼容性解决方案

问题

placeholder属性是html5的新属性,IE9及以下不支持。
2016-02-27 11 05 14

解决办法

github上有很多placeholder的解决方案,我尝试了Placeholders.js

使用方法

安装

bower install placeholders --save

使用

<script src="{path}/placeholders.min.js"></script>

Bug

按照作者给的上述方法,在IE8下会出现这样的bug:首次加载页面的时候可以显示placeholder,但是再次刷新的时候却不能显示了。
我在这个issue找到了解决方案。

// 在页面加载的时候运行这段代码
window.setTimeout(function() {
    Placeholders.enable();
}, 100);

Bug's Bug

后来发现使用placeholder之后引入了另一个问题:因为placeholder的实现原理是初始化的时候将placeholder值直接写进去value属性当中,所以当我取值的时候就会出现把placeholder值直接当做实际值的情况。
临时性的解决方案是直接进行数据过滤。不过这样子的代码比较繁琐,而且要是用户的姓名真的叫做“请输入用户名”呢?更好的办法有待探索。

if(data.name === "请输入用户名"){
   data.name = "";
}

浏览器特性检测工具:Modernizr

原因

我在使用chart.js的时候碰到不少IE8的兼容性问题,其中包括动画问题 #29
chart.js官方文档推荐使用Modernizr来进行浏览器特性检测,进而决定是否显示动画。

使用方法

下载

Modernizr可以检测很多浏览器的特性,在它的官网可以随意勾选你所需要的检测特性,然后生成js文件,比如这次我只需要检测canvas,所以大概这样。
2016-02-29 6 57 44

使用

引入了modernize.custom.js文件之后,如果要检测某个特性,只需要这样。

 if (Modernizr.awesomeNewFeature) {
    console.log('this feature support!');
  } else {
    console.log('this feature do not support!');
  }

PS:开发环境中也可以直接选择development build,这样就包含所有特性的检测了。

scss-lint实战碰到的一些问题以及解决方案

前言

之前在项目中引入了scss-lint,参考 #19 。在用的时候踩到一些坑,记录在这个地方。

1. 属性排序

// 比如这样的代码会报错!
.refresh {
  margin-left: 10px;
  color: $color;
}

2016-01-12 6 13 37
显然不科学啊!margin-left当然应该排在color前面啊!
后来发现scss-lint默认属性排序是按照字母排序的......参考这里 sds/scss-lint#463
解决方法:重新设置属性顺序,参考这里

linters:
  PropertySortOrder:
    order: concentric

shell.js与yargs命令行开发

与终端的交互

一种编程语言是否易用,很大程度上,取决于开发命令行程序的能力。
忽然想搞明白平常用的那些npm包,它们是如何接受参数然后运行的。
#1. shell.js

关于nodejs如何调用shell进程,可以参考 #46 #47 ,里面介绍了两种方法。下面主要说一下shell.js一些比较方便但是不常用的方法。

  1. 检查某个文件夹是否存在
shell.test('-d','path');  // 当path为目录,返回true
shell.test('-e','path');  // 当path为目录或者文件,返回true
  1. 重写文件内容
"string".to('file');
  1. 添加文件内容到尾部
"string".toEnd('file');
  1. 从文件中读取字符串然后进行正则匹配替换
shell.sed(/a/g,'b','test.js');  // 把test.js里面的所有字母b替换成字母a然后返回
shell.sed('-i',/a/g,'b','test.js');  //替换之后重新写入覆盖源文件

#2. yargs

yargs可以将命令行的参数转化成对象,比如我有一个test.js文件

var argv = require('yargs').argv;
console.log(argv)

当我执行

node test.js --name lsf --emai [email protected]

终端输出

{ _: [], name: 'a', email: 'b', '$0': 'test.js' }

所有argv就可以很方便地提取到argv.name和argv.email.
如果是用process.argv去获取的话,是这样的

[ '/usr/local/bin/node',
  '/Users/youngwind/www/lazy-smart/test.js',
  '--name',
  'a',
  '--email',
  'b' ]

一点都不友好。

css样式融合与类继承

前言

前一阵子用了一下css-module,有一个问题没有解决,那就是同样一个元素添加多个类名的情况。比如我想要这样的效果。这种做法很常见,一般用于.title定义公共样式,title#{n}定义各自独有的样式。

<div class="title title1"></div>
<div class="title title2"></div>

如果希望通过css-module来完成的话,代码大概是这样。

import style from "./index.scss"
<div className={style.title, style.title1}></div>
<div className={style.title, style.title2}></div>

但是会报错,因为官方的css-module只能接受一个变量字符串。
#1. 通过类名解决

当时找到了两个解决方案。

  1. 字符串拼接。
<div className={style.title + " " + style.title1}></div>

但是这种方法好搓啊。。。肯定有更优雅的解决方案。
2. react-css-module
这个loader可以直接定义多个类名,具体参考这里
#2. 通过样式继承解决

后来跟别人交流这个问题的时候得出以下的帮助:

官方说法,css-module在设计的时候就没打算支持多个类名,因为在他们看来,每一个元素应该像一个对象一样,只添加一个className值,至于样式的融合应该通过样式的继承来完成。

嗯,细细想来,还是很有道理的。这样每个元素最多只有一个类名,简洁多了。

scss的继承与占位符

在我用scss的继承去实现它的时候发现一个有趣的问题。

.title {
  height: 100px;
}

.title1 {
  @extend .title;
  background-color: blue;
}

.title2 {
  @extend .title;
  background-color: pink;
}

scss-lint会报语法警告,如下。
2016-01-20 9 29 34
查阅了一下,才发现scss的继承和占位符的细微区别。
上面的scss代码会被编译成这样子。

.title, .title1, .title2 {
  height: 100px;
}

.title1 {
  background-color: blue;
}

.title2 {
  background-color: pink;
}

但是如果我们不需要用到.title这个类呢?如果这个类唯一存在的作用就是抽象出公共的title1和title2那一部分呢?那么,用占位符要更好。

// scss 写法
%title {
  height: 100px;
}

.title1 {
  @extend %title;
  background-color: blue;
}

.title2 {
  @extend %title;
  background-color: pink;
}

编译之后是这样子的。

.title1, .title2 {
  height: 100px;
}

.title1 {
  background-color: blue;
}

.title2 {
  background-color: pink;
}

区别在于不再有.title这个类了。

与脚手架大战:回合2

再接再厉

继回合1 #46 之后,开始准备回合2。这次我重新整理了一下思路。

  1. 我要实现的是一个简单的脚手架生成器,它只需要能够生成我指定的那两三种项目。
  2. 生成的方式与上一次思考的相同,也就是(拷贝文件→装包→构建→启动服务),而非大量地写文件。
  3. 终端交互采取inquirer,获取到参数之后传递给生成模块去处理
  4. 这次需要重点解决的问题有。
  5. 如何让shell认识我定义的命令?
  6. 如何优雅地调用shell的命令?

寻找方案

针对第一个难点,经人指点,我知道应该在package.json的bin字段中定义我这个命令。其实平常我们输入cd,ls这些命令,shell之所以认识,是因为它们在环境变量$path中能找到。但是全局安装的npm module并不在$path中啊。npm的解决方案是,对于全局安装的,有bin字段的包,**在安装的时候会主动建立一个链接,从$path指向该module的某个可执行文件。**就像如图所示的第二行箭头那样。
2016-03-20 11 34 47
其实这个bin字段还可以配置多个命令,详细的可以参考这里。ok,第一个难题可以解决了。
针对第二个难点,我搜索到了shell.js,关于shell.js,可以参考阮一峰老师的教程

开始敲代码

ok,解决方案敲定之后就开始撸起袖子敲代码了。
先写终端交互的cli.js,主要涉及inquirer.js

#!/usr/bin/env node
'use strict';

var inquirer = require("inquirer");
var lazySmart = require("./lazy-smart");
var shell = require("shelljs");

var questions = [

  // 项目名称
  {
    type: "input",
    name: "name",
    message: "input your project name",
    validate: function (value) {

      if (!value) {
        return "project name can not be null"
      }

      // 检查文件夹是否已存在
      var ls = shell.ls();
      if (ls.indexOf(value) !== -1) {
        return "File exists, please select another project name.."
      } else {
        return true;
      }
    }
  },

  // 选择项目架构类型
  {
    type: "list",
    name: "architecture",
    message: "select your project architecture",
    choices: ["ejs+gulp", "ejs+webpack"]
  },

  // git仓库名称
  {
    type: "input",
    name: "gitName",
    message: "input the repository name of git project.(make sure the repository is created and empty)",
    default: function (answer) {
      return answer.name;
    }
  },

  // git仓库所有者名称
  {
    type: "input",
    name: "gitOwner",
    message: "input the owner of git project.",
    default: function () {
      var userName = shell.exec('git config --global --get user.name').output;
      userName = userName.substring(0, userName.length - 1);
      return userName;
    }
  }
];

inquirer.prompt(questions, function (answers) {
  //把用户输入的参数传递给生成模块
  lazySmart.init(answers);
});

然后编写生成模块lazy-smart.js

// 调用执行命令行
var shell = require("shelljs");

// 支持在脚本中直接执行命令
require('shelljs/global');

// 解析获取命令行参数
var argv = require('yargs').argv;
// 初始化
exports.init = function (options) {

  exports.copy(options);
  exports.initGit(options);
  exports.install(options);
  exports.build(options);
  exports.run(options);
};
....

把功能模块划分好之后,剩下函数的编写就不难了,完整的代码在这里

至此,已经完成了第一套项目脚手架的搭建,之后第二,第三套就跟脚手架没啥关系了。通过这次自己手动写脚手架,主要有两个收获。

  1. 通过分析问题找准解决方案之后,敲代码才能效率高。
  2. 跟终端交互还是很有趣的。

遗留问题点:

  1. npm包的本地调试和发布流程不是非常顺畅,而且有个极其坑爹的.gitignore变成.npmignore的问题还不知道为啥。暂时找到的参考资料在这里
  2. shell.js的exce是串行执行的,当我想在在一个js文件调用多个不会结束自动结束的进程的时候容易出问题,比如先后开启npm run start 和gulp watch这些。有什么办法能够直接打开新终端窗口吗?或者结合child_process?这个可是每个都是并行执行的。

自己动手写express中间件

express本质上是由一系列中间件构成的,就像管道一样。

普通中间件

比如说,我要编写一个这样的中间件:假如请求中有date参数,那么给请求加上a参数,给返回加上b属性,然后交给下一个中间件处理。假如请求中没有date参数,直接返回message:"没有date“,结束响应。

// date.js
module.exports = function () {
  return function (req, res, next) {
    if (req.query.date) {
      req.a = "a";
      res.b = "b";
      //下一个中间件就可以读取到req.a和res.b参数了。
      next();
    } else {
      res.send({
        "code": 0,
        "message": "没有date"
      });
    }
  }
};

如果想在应用层级使用它,可以这样写:

var date = require('./date.js');
app.use(date());

如果想在路由层级使用它,可以这样写:

var date = require('./date.js')
router.use(date());

其实没有本质的区别。

错误处理中间件

错误处理中间件的特殊点在于它有四个参数。
准确地说,必须有四个参数才会被认为是错误处理中间件。如果只有三个参数,也会被认为是普通中间件,而且你也没法在里面进行错误处理。
当错误发生的时候,执行流会跳过所有的普通中间件,但是不会跳过错误处理中间件。

app.use(function(err, req, res, next) {
  console.error(err.stack);
  res.status(500).send('Something broke!');
});

如何触发错误?

  1. new Error然后next()传递
// 这是一个捕获404错误的中间件
// 注意,这是一个普通的中间件,它只产生错误,并不处理错误
// 通过next(err)把错误传递给下一个**错误处理中间件**
app.use(function (req, res, next) {
  var err = new Error('Not Found');
  err.status = 404;
  next(err);
});

2.new Error 然后throw

app.use(function (req, res, next) {
  var err = new Error('Not Found');
  err.status = 404;
  throw err;
});

参考资料:
http://expressjs.com/zh-cn/guide/writing-middleware.html

redux组织代码之:分割action

前言

刚刚开始用redux的时候因为应用比较小,而且是单枪匹马,所以所有的action都写在一个action.js里面。后来随着应用的膨胀以及多人协作,这样显然是不行的。所以,应该像分割reducer那样分割action.
#1. action

// postAction.js
// 点击刷新按钮
exports.invalidateReddit = function (reddit) {
  return {
    type: types.INVALIDATE_REDDIT,
    reddit: reddit
  };
};

/**
 * 根据文章类型发出请求
 * @param reddit {string} 文章的类别 reactjs/frontend
 * @returns {{type: null, reddit: *}}
 */
exports.requestPosts = function (reddit) {
  return {
    type: types.REQUEST_POSTS,
    reddit: reddit
  };
};
……

// numberAction.js
import {actionCreator} from "redux-action-utils";
import types from "../constants/ActionTypes.js";

exports.addTag = actionCreator(types.ADD_TAG, "state");

合成的action

// rootAction.js
import * as post from "./postAction.js";
import * as number from "./numberAction.js";

module.exports = {
  post: post,
  number: number
};

#2. 在container上应用他们

之前在container上绑定dispatch到action的时候是这样写的。

import action from "../action/action.js";
function mapDispatchToProps(dispatch) {
  return {
    action: bindActionCreators(action, dispatch),
  };
}

现在因为action是组合的action,所以大概要写成这个样子。

import actions from "../action/rootActions.js";
function mapDispatchToProps(dispatch) {
  return {
    postActions: bindActionCreators(actions.post, dispatch),
    numberActions: bindActionCreators(actions.number, dispatch)
  };
}

全局css的终结——css module

前言

长久以来,css的全局入侵性一直没得到有效的解决,反观js,早有Browserify和webpack等使代码变得模块化。问题的核心在于全局类的命名冲突上,幸好,webpack的css-loader提供了这样的一个解决方案,名字叫做 CSS Module

假设我们有大概下面这样的代码

// webpack.config.js
module: {
    loaders: [
      {test: /\.scss$/, loader: 'style!css!postcss-loader!sass?sourceMap'},
    ]
  },
// 组件 picker.js
var React = require("react");
require("./picker.scss");

var Picker = React.createClass({
  render: function () {
    var value = this.props.value;

    return (
        <h1 className="demo">{value}</h1>
    );
  }
});

module.exports = Picker;
//picker.scss 组件的样式
.demo {
  color: pink;
}

那么,最终渲染出来的DOM元素应该长这个样。
2016-01-09 4 02 59

如何应用css module?

// webpack.config.js
// 看到区别了吗?在于css-loader后面多了一些参数!
module: {
    loaders: [
      {test: /\.scss$/, loader: 'style!css?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]!postcss-loader!sass?sourceMap'},

    ]
  },
// 组件picker.js
var React = require("react");

// 区别在与这行!!
var styles = require("./picker.scss");

var Picker = React.createClass({
  render: function () {
    var value = this.props.value;
    return (
        // 还有这行!!如果有了解过react-native的肯定会发现这写法简直跟react-native如出一辙。
        <h1 className={styles.demo}>{value}</h1>
    );
  }
});

module.exports = Picker;
//picker.scss 组件的样式
// 没错,scss文件不需要任何修改
.demo {
  color: pink;
}

最后渲染的DOM元素是这样的。
2016-01-09 4 09 49
因为css最后被编译成下面的这样子了。
2016-01-09 4 11 14

结论

css module会确保每个类的全局唯一性,因此,给组件写样式的时候再也不用担心出现样式冲突了!
参考资料:

  1. https://github.com/css-modules/webpack-demo
  2. https://www.zhihu.com/question/34834178
  3. http://web.jobbole.com/83953/

ES6 generator 、yield 与co

起因

最近在看一些node项目的时候发现里面用到了ES6的generator函数,yield和tj的co库,花了一些时间搞明白它们之间的关系,下面用一些例子说明。

溯源

对于异步的操作,最常规的写法是回调函数,但是深度回调会出现可怕的金字塔。那么,如何用更好的书写方式来避免金字塔,又或者说,怎么样把异步的代码写得看起来好像同步那样子呢?
其中一种解决方案是promise模式,.then一直then下去。ok,从ES6开始,有两个新的特性,叫generator和yield,借助它们,我们能够更优雅地解决这个问题。

generator和yield简介

请看下面的代码

function* Hello(){
 yield 1;
 yield 2;
}
var hello = Hello();
console.log(hello.next());  // { value:1, done:false }
console.log(hello.next());  // {  value:2, done:false }
console.log(hello.next());  // { value:undefined, done:true }
  1. function后面的*号代表这是一个generator函数,而非普通函数,只有在generator函数中才能使用yield,在普通函数中使用yield会报错。
  2. generator函数的函数是分段的。第一次执行next的时候,程序会执行到第一个yield,然后返回{ value:1, done:false },表示yield后面返回1,但是函数Hello还没执行完,函数既不会退出,也不会往下执行。
  3. 当再次执行next的时候,从上次中断的地方接着执行,直到下一个yield或者函数结尾。

正是这种在单个函数内分步执行性质的引入,使得我们能够通过它来完成异步操作的"优化"。

假设有这样的例子

function delay(time, cb){
 setTimeout(function(){
   cb && cb()
 },time);
}

delay(200,function(){
  console.log('200ms done');
  delay(1000,function(){
    console.log('1200ms done');
    delay(500,function(){
       console.log('finish');
     });
  });
});

如何优化这个例子呢?

思路:根据generator的特性,如果我构造一个generator函数包含这三个异步操作,并且把他们各自的callback函数都设置为执行next()函数,这样不就可以实现"看起来是同步"的了吗?

function cl(){
  yieldDelay.next();
}

function* YieldDelay(){
  yield delay(3200,cl);
  console.log('3200ms done!');
  yield delay(4400,cl);
  console.log('4400ms done!');
  yield delay(5500,cl);
  console.log('5500ms done!');
}

var yieldDelay = YieldDelay();
yieldDelay.next();

ok。我们已经迈出了一大步了。不过这个写法看着还是有些别扭。

  1. 第一次执行需要我手动出发next()函数。
  2. 回调函数只是简单地执行next()函数,为什么不能把它更加抽象化,以至于不用定义这个回调函数呢?
    让我们先激动一小会儿,因为你在走tj大神曾经走过的路!

进一步优化这段代码

我们先想想思路,到底有什么办法能够做到呢?最开始的写法之所以会导致金字塔现象,是因为:函数a的执行里面包含执行函数b,所以函数b的执行里面也必须包含执行函数c……如果我们在函数a执行的时候只返回一个function,而这个function接收函数b作为参数。ok,我们先按照这个思路改造一下delay函数和generator函数

function delay(time){
  return function(fn){
    setTimeout(function(){
      fn();
    },time)
  }
}

co(function* (){
  yield delay(4200);
  yield delay(4000);
  yield delay(3000);
})(function(){
  // 回调函数
  console.log('all done!');
})

function co(GenFunc){
   return function(cb){
      //......先略过
   }
}

我们分析一下:

  1. co函数接收generator函数作为参数,然后返回一个函数,该函数接收回调函数。
  2. delay函数接收时间作为参数,返回一个函数,该函数接收回调函数。

再次理一下思路,我们应该如何编写//........先略过这一部分的内容呢?
yield特性可以让我们分阶段执行,暂停→开始→暂停→开始……**如果我们可以让第一次执行的结果是一个函数,而这个函数接收第二次执行本身作为cb函数,第二次执行的结果也是一个函数,而这个函数接收第三次执行本身作为cb函数……直到结束。好吧,说再多还不如来几行代码!

function co(GenFunc) {
  return function(cb) {
    var gen = GenFunc();  // 第一次执行的时候构造出对象
    next()    // 调用自定义的next方法
    function next() {
      var ret = gen.next();   
     // 在generator函数中走一步,delay函数返回一个函数赋给ret.value
      if (ret.done) {    
        // 判断ret.done是否为真,如果为真,说明generator函数执行完了,该调用回调函数了
        cb && cb();
      } else {
      // 如果ret.done为假,那么调用上一个返回的函数,并且把next函数传递给它作为回调函数
        ret.value(next);
      }
    }
  }
}

嗯,看起来有点绕,多看几遍就好了。
至此,你已经山寨了一个极其简单的co库。
当然tj的co库比这个复杂多了,但是原理就是这样,还可以传参数,支持promise

遗留问题:

  1. 该看看ES6原生支持的promise对象了。
  2. generator+co这样的模式确实可以优雅地解决金字塔问题,不过ES7中提供async函数,利用它,不需要依赖co库,也一样可以解决这个问题。

参考资料:

  1. http://es6.ruanyifeng.com/#docs/generator
  2. http://bg.biedalian.com/2013/12/21/harmony-generator.html
  3. http://www.ruanyifeng.com/blog/2015/04/generator.html

Error: Does Not Satisfy Its Siblings' peerDependencies Requirements

起因

我在装npm的时候不时会碰到这个错误:

Error: Does Not Satisfy Its Siblings' peerDependencies Requirements 

解决方案

通过npm update更新那个报错的包。注意:更新报错的包,而不是正在安装的包。

探索

问题解决之后,我深究了一下原因,发现peerDependencies这个字段的意义。

1. npm如何解决包的依赖?

先考虑一般的情况:module A 依赖于 moduleB 1.0版本,module C依赖于moduleB 2.0版本,那么这个没有问题,因为moduleB是属于dependence字段或者devdependence字段,moduleB会被安装在module A和module C的内部,各自使用不同的版本也不会冲突。
但是,我们考虑插件机制,比如gulp。gulp是一个构建工具,有许多围绕gulp开发的插件,开发者在开发此类插件的时候一般是不会把gulp本身包含在正在开发的包里面的。我们使用者之所以能够正常使用这些插件包,那是因为我们提前安装好了gulp包。也就是说,gulp插件包依赖于包以外的gulp包,这个时候,你就可以把gulp写在gulp插件包的peerdependence字段,**peerdependence里面的包安装路径与当前包同级。**当然,你也可以不这么做,直接显式地安装gulp也是可以的。

2. 那么问题来了

考虑在全局安装的条件下,module A的peerdependence是 module B 1.0版本,你以前已经安装过module A了,所以会默认安装module B 1.0版本(跟module A)同级。现在,你要安装module C,里面的peerdependence是module B 2.0版本,这是可能的,比如不同插件开发的时候依赖的版本很可能不一样。那么,就会造成版本冲突,因为module2.0跟原先已经安装的module1.0版本冲突了,而且是同一个位置,npm就会报上面的错误。

3. 为什么解决办法是更新或者重装?

为什么更新或者重装module B就可以解决问题。我们设想,module A插件先写,那时候的module B版本还很低,所以module A在peerdependence里面写上,需要不低于1.0版本的module B。过了一段时间,module B升级到2.0了,module C开发的时候是基于2.0的,所以它在自己的peerdependence里面写上,需要不低于2.0版本的module B。但是,此时你电脑里面的module B还是1.0版本。所以只需要更新module B到最新版本2.0,那么就可以同时满足module A和module C的需要了。

更新:

注意,从npm 3.0版开始,peerDependencies不再会默认安装了。

参考资料:

  1. https://futurestud.io/blog/how-to-fix-npm-package-does-not-satisfy-its-siblings-peerdependencies-requirements
  2. http://stackoverflow.com/questions/18875674/whats-the-difference-between-dependencies-devdependencies-and-peerdependencies
  3. http://luckydrq.com/2014-10-23/peer-dependencies/
  4. http://javascript.ruanyifeng.com/nodejs/packagejson.html#toc2

react-router 与 history的版本匹配问题

之前在项目中同时使用react-router和history,执行npm install的时候报错,如图所示。
2015-12-22 10 30 25
按照它给的报错提示,手动把package.json里面的history版本号改为1.13.1就好了。

利用代码片段偷懒:dash snippets

dash,大名鼎鼎,它的documenation功能大家都用得很多,但是snippets可能就用得比较少了。
一图胜千言。
demo


有了这个,就可以把一些常用的代码片段提前写在这儿,比如for循环啦,ajax请求啦等等,而且跟编辑器无关。只要给dash开通辅助功能,它就能在任何地方使用!

2015-12-13 4 09 07

gulp-preprocess 让你的项目只需要一个配置文件

问题由来

对于静态资源的路径,本地开发和正式线上是不同的,比如像这样。

<!-- 本地开发 -->
<script src="/bundle.vendor.js"></script>
<script src="/bundle.js"></script>
<!-- 线上 -->
<script src="http://media8.smartstudy.com/bundle.vendor.js"></script>
<script src="http://media8.smartstudy.com/bundle.js"></script>

以前的做法

通过ejs等模板渲染的时候完成替换,因为本地开发和线上的config.js是不一样的。但是,自从不使用开始使用react以及前后端分离之后,这种方法就不试用了。

新的方法

通过gulp-preprocess 插件在copy文件的时候进行替换。比如像这个

index.html (before)
<script src="<!-- @echo cdnUpload -->/bundle.vendor.js"></script>
<script src="<!-- @echo cdnUpload -->/bundle.js"></script>
index.html (after)
<script src="/bundle.vendor.js"></script>
<script src="/bundle.js"></script>

具体怎么做?

  1. 首先是配置全局的config.js
module.exports = {
  // 静态资源cdn地址
  cdnUpload: "",
};
  1. 然后是写gulpfile文件
// 配置变量替换
var preprocess = require('gulp-preprocess');
// 引入配置文件
var config = require('./src/config.js');
// 复制文件
gulp.task('copy', function () {
  return merge(
      gulp.src('src/index.html')
          .pipe(preprocess({
            context: config
          }))
          .pipe(gulp.dest('public'))
  );
});

这样就大功告成了!

关于js和css的替换

当然,这种替换可以用于然后文件的替换,只要符合preprocess定义的语法就能从config中读取变量进行替换。
但是,js通过import或者require直接引config.js就可以解决。本来写scss的时候也打算用它来配置一些全局的cdnUpload路径,但是后来发现完全没有这个必要!!因为在**_组件化开发**_的前提之下,scss跟配置相关的全局变量已经没有存在的必要了。

参考资料:

  1. https://github.com/jsoverson/preprocess
  2. https://www.npmjs.com/package/gulp-preprocess

redux精简代码之--actionType与redux-action-utils

前言

用过redux的人都知道,action和actionType中都存在大量的重复代码。
(没有了解过redux的请移步这里


精简actionType

actionType是定义action常量的地方,一般长这个样。

exports.ADD_TAG = 'ADD_TAG';
exports.SELECT_REDDIT = 'SELECT_REDDIT';
exports.INVALIDATE_REDDIT = 'INVALIDATE_REDDIT';
exports.REQUEST_POSTS = 'REQUEST_POSTS';
exports.RECEIVE_POSTS = 'RECEIVE_POSTS';

使用keymirror之后变成这样。

var keyMirror = require('keymirror');
module.exports = keyMirror({
  ADD_TAG: null,
  SELECT_REDDIT: null,
  INVALIDATE_REDDIT: null,
  REQUEST_POSTS: null,
  RECEIVE_POSTS: null,
});

精简部分action

action是定义action返回数据的地方,有很多函数会长下面这个样。(当然,像那些异步请求那么复杂的除外)

exports.addTag = function (state) {
  return {
    type: types.ADD_TAG,
    state: state
  }
};
exports.selectReddit = function (reddit) {
  return {
    type: types.SELECT_REDDIT,
    reddit: reddit
  }
};

使用redux-action-utils之后长这个样。

var {actionCreator, optionsActionCreator}= require('redux-action-utils');
exports.addTag = actionCreator(types.ADD_TAG, 'state');
exports.selectReddit = actionCreator(types.SELECT_REDDIT, 'reddit');

react-native:寻找像写scss那样写react native 样式的方法

前言

我深信:learn once,write anywhere. 目前react-native的样式是通过像下面这样子来创建的。

var styles = StyleSheet.create({
  //container
  container: {
    backgroundColor: '#F2F2F2',
    flex: 1,
  },
});

从前端开发的角度来看,这是内联样式啊!肯定要提到scss文件中去啦!所以我在寻找如何在react native的样式写到scss文件中的方法。
#1. react-native-css

此npm包号称可以把scss文件文件转化为js文件,然后再组件中require进去就可以了。但是我还有一个问题没有解决,导致目前还不能用这个。

React-native-cli doesn't use the node module ecosystem. The basic setup up is to have React-native running on one terminal, and the react-native-css on another. React-native-css will watch for changes and compile back to javascript.

我直接在terminal中执行 react-native-css就直接报错了,暂时没找到解决方法。

bash: react-native-css: command not found

更新》》》》》》》》》》》》
通过与作者issue,已经找到解决方案:react-native-css 需要全局安装,可能的原因是我的react-native-cli也是全局安装的,这个需要保持一致。
但是却引发了另一个问题,react-native-css不能watch住我的scss文件,有待解决....

而且还有另外一个问题。example中的例子是这样的。

description {
  margin-bottom: 20;
  font-size: 18;
  text-align: center;
  color: #656656;
}

如果在scss文件中这样写样式的话,肯定会报语法错误的啊。因为description不是合法的选择器。显然我需要的是差不多像下面这样的,也能成功转换的。这样就完全像在写scss了。learn once,write anywhere嘛~~

.description {
  margin-bottom: 20;
  font-size: 18;
  text-align: center;
  color: #656656;
}

未完待续。。。。

ie8上传文件后提示下载文件

问题

最近在做的项目需要兼容ie8,所以碰到不少兼容性问题。比如这个:在文件上传之后,会提示下载文件。

情况

后来google了一下才发现,在IE10及以下都会存在这个问题:上传文件成功后倘若服务器返回json数据,那么ie就不能正确解析这个数据,反而会把它当做文件来处理。

解决办法

在后端返回的时候自定义contype-type为"text/html",比如在node中这样写

res.setHeader('Content-Type', 'text/html');

参考资料:
http://www.oschina.net/question/223750_123703?fromerr=EtJSFnNr

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.