youngwind / blog Goto Github PK
View Code? Open in Web Editor NEW梁少峰的个人博客
梁少峰的个人博客
最近在使用max-width的时候发现在IE8下不兼容,具体可以参考这里
把图片放在div中,并且设置该div的宽度为你原来想让图片的max-width值。
.container { width: 765px;}
img { max-width: 100%;}
第一种方法治标不治本,之所以出现图片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">
参考资料:
placeholder属性是html5的新属性,IE9及以下不支持。
github上有很多placeholder的解决方案,我尝试了Placeholders.js。
bower install placeholders --save
<script src="{path}/placeholders.min.js"></script>
按照作者给的上述方法,在IE8下会出现这样的bug:首次加载页面的时候可以显示placeholder,但是再次刷新的时候却不能显示了。
我在这个issue找到了解决方案。
// 在页面加载的时候运行这段代码
window.setTimeout(function() {
Placeholders.enable();
}, 100);
后来发现使用placeholder之后引入了另一个问题:因为placeholder的实现原理是初始化的时候将placeholder值直接写进去value属性当中,所以当我取值的时候就会出现把placeholder值直接当做实际值的情况。
临时性的解决方案是直接进行数据过滤。不过这样子的代码比较繁琐,而且要是用户的姓名真的叫做“请输入用户名”呢?更好的办法有待探索。
if(data.name === "请输入用户名"){
data.name = "";
}
鉴于直接使用mysql包操作数据库带来的各种不方便,见 #43 ,我找到了sequelize,试用了一下,觉得还不错。
npm install sequelize --save
sequelize支持如下数据库:MySQL, MariaDB, SQLite, PostgreSQL and MSSQL,不同的数据库需要安装各自的包,因为我操作的是mysql,所以要安装mysql包
npm install mysql --save
#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
}
});
var User = sequelize.define('user', {
firstName: {
type: Sequelize.STRING, // 定义为字符串型
field: 'first_name' // 这里的意思是数据库中字段为first_name,取出来的集合中的名字确实firstName
},
lastName: {
type: Sequelize.STRING
}
}, {
freezeTableName: true // table的名字与model的名字相同
});
User.sync({force: true}).then(function () {
// Table created
return User.create({
firstName: 'John',
lastName: 'Hancock'
});
});
上面的代码有几个地方需要说明。
/**
* 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); // 这里啊亲!删除这张表哎呦喂!
}
})
我不单单想掺入数据,我还想更新,删除,各种查询怎么办?看这里。
在工作中npm install [package]是家常便饭,但是很多时候装包都消耗不少时间,特别是网络不好的时候。其实每次安装一个包,流程是这样的:
既然曾经安装过的包都在本地了(如图),为什么再次安装的时候不能直接在本地缓存中拷贝过来,从而节省很多时间呢?
npm自己提供了cache机制,但是并不完善。
(1)如果指定模块不在缓存目录,那么 npm 会连接 registry,下载最新版本。这没有问题,但是如果指定模块在缓存目录之中,npm 也会连接 registry,发出指定模块的 etag ,服务器返回状态码304,表示不需要重新下载压缩包。
(2)如果某个模块已经在缓存之中,但是版本低于要求,npm会直接报错,而不是去 registry 下载最新版本。
三种工具我都试过。
npm-proxy-cache和local-npm都没能成功缩短时间(暂时排除不出来那里的问题),而且local-npm运行的时候会在当前目录生成很多文件。
npm-lazy倒是弄好了,拿express测了一下,不使用缓存需时超过1分钟,使用缓存7秒就可以了。
npm install -g npm_lazy
npm config set registry http://localhost:8080/
npm_lazy
参考资料:
最近在看一些node项目的时候发现里面用到了ES6的generator函数,yield和tj的co库,花了一些时间搞明白它们之间的关系,下面用一些例子说明。
对于异步的操作,最常规的写法是回调函数,但是深度回调会出现可怕的金字塔。那么,如何用更好的书写方式来避免金字塔,又或者说,怎么样把异步的代码写得看起来好像同步那样子呢?
其中一种解决方案是promise模式,.then一直then下去。ok,从ES6开始,有两个新的特性,叫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 }
正是这种在单个函数内分步执行性质的引入,使得我们能够通过它来完成异步操作的"优化"。
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。我们已经迈出了一大步了。不过这个写法看着还是有些别扭。
我们先想想思路,到底有什么办法能够做到呢?最开始的写法之所以会导致金字塔现象,是因为:函数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){
//......先略过
}
}
我们分析一下:
再次理一下思路,我们应该如何编写//........先略过这一部分的内容呢?
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
遗留问题:
参考资料:
此插件基于jquery,仓库在这儿,主要用途如下
bower install jquery --save
bower install jquery-cursor --save
<script src="jquery.js"><script>
<script src="jquery-cursor.js"><script>
$('#textAreaId').cursor({
"text":"这是插入的一些文字,哪些位置被选中由下面两个参数决定",
"beginPos":2, // 开头从0开始计数
"endPos":5 // 如果省略,则从beginPos一直选中到text的结尾
);
IE6~IE8不支持CSS3的高级选择器,比如nth-child(),nth-of-type()等等,非常的不方便,selectivizr帮我们完成了这件事情。
// 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;
}
3.样式必须是link:css引入的,不能是行内样式,也不能是内联样式。因为selectivizr的工作原理是**通过ajax请求css文件,然后重新解析css文件,然后操作DOM元素添加上相应的类名。**如下图所示。
4.由于css文件是ajax请求得来的,由于js同源策略的限制,css请求的位置必须跟html页面同域。(media.xxx.com和www.xxx.com不是同域)这个问题比较严重,因为在大型网站中,为了加快网页响应速度,一般都会通过多级域名加快静态资源下载速度。或许可以用跨域的思路来解决它,有待研究。
长久以来,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;
}
// 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元素是这样的。
因为css最后被编译成下面的这样子了。
css module会确保每个类的全局唯一性,因此,给组件写样式的时候再也不用担心出现样式冲突了!
参考资料:
IE8和IE9在实现跨域请求的时候使用XDomainRequest自己实现了一套,所以即使是使用jquery1.9.1版本也无法直接兼容IE8,IE9的跨域请求。StackOverflow上的两个问答说得很清楚。
这个方法在上面第二个链接已经有人做好了,但是我嫌有点麻烦,不想在每个项目中都要定义这样一个函数吗?
有人已经做好了,详见这里。https://github.com/MoonScript/jQuery-ajaxTransport-XDomainRequest
不过使用它有几点非常需要注意
前一阵子用了一下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. 通过类名解决
当时找到了两个解决方案。
<div className={style.title + " " + style.title1}></div>
但是这种方法好搓啊。。。肯定有更优雅的解决方案。
2. react-css-module
这个loader可以直接定义多个类名,具体参考这里
#2. 通过样式继承解决
后来跟别人交流这个问题的时候得出以下的帮助:
官方说法,css-module在设计的时候就没打算支持多个类名,因为在他们看来,每一个元素应该像一个对象一样,只添加一个className值,至于样式的融合应该通过样式的继承来完成。
嗯,细细想来,还是很有道理的。这样每个元素最多只有一个类名,简洁多了。
在我用scss的继承去实现它的时候发现一个有趣的问题。
.title {
height: 100px;
}
.title1 {
@extend .title;
background-color: blue;
}
.title2 {
@extend .title;
background-color: pink;
}
scss-lint会报语法警告,如下。
查阅了一下,才发现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这个类了。
一种编程语言是否易用,很大程度上,取决于开发命令行程序的能力。
忽然想搞明白平常用的那些npm包,它们是如何接受参数然后运行的。
#1. shell.js
关于nodejs如何调用shell进程,可以参考 #46 #47 ,里面介绍了两种方法。下面主要说一下shell.js一些比较方便但是不常用的方法。
shell.test('-d','path'); // 当path为目录,返回true
shell.test('-e','path'); // 当path为目录或者文件,返回true
"string".to('file');
"string".toEnd('file');
shell.sed(/a/g,'b','test.js'); // 把test.js里面的所有字母b替换成字母a然后返回
shell.sed('-i',/a/g,'b','test.js'); //替换之后重新写入覆盖源文件
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' ]
一点都不友好。
对于静态资源的路径,本地开发和正式线上是不同的,比如像这样。
<!-- 本地开发 -->
<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>
module.exports = {
// 静态资源cdn地址
cdnUpload: "",
};
// 配置变量替换
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'))
);
});
这样就大功告成了!
当然,这种替换可以用于然后文件的替换,只要符合preprocess定义的语法就能从config中读取变量进行替换。
但是,js通过import或者require直接引config.js就可以解决。本来写scss的时候也打算用它来配置一些全局的cdnUpload路径,但是后来发现完全没有这个必要!!因为在**_组件化开发**_的前提之下,scss跟配置相关的全局变量已经没有存在的必要了。
标题的说法其实不太准确,因为react原生的事件系统本身就是事件代理的,意味着事件会一直冒泡到document进行绑定。所以,使用普通的**event.stopPropagation();**是没有办法在react中实现阻止事件“冒泡”类似的操作的。
stackoverflow和github issue上都有相关的讨论,比如
最后我找到了一个比较简单地解决方法,就是 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>
我在装npm的时候不时会碰到这个错误:
Error: Does Not Satisfy Its Siblings' peerDependencies Requirements
通过npm update更新那个报错的包。注意:更新报错的包,而不是正在安装的包。
问题解决之后,我深究了一下原因,发现peerDependencies这个字段的意义。
先考虑一般的情况: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也是可以的。
考虑在全局安装的条件下,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就会报上面的错误。
为什么更新或者重装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不再会默认安装了。
参考资料:
继上篇 #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
本来我是希望集成到webpack中的,就像eslint一样。但是没找到合适的loader,所以暂时只能集成到gulp中了。找到一个sasslint-loader,但是不太成熟,而且并非基于scss-lint,所以最后没有采用。
npm install gulp-scss-lint --save-dev
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中的配置会覆盖默认配置。
gulp.task('watch', function () {
gulp.watch([
'src/**/**/*.*'
], ['scss-lint', 'webpack-dev']);
});
这年头,不会点node都不好意思说自己是前端。ok,下面显示的是一个添加用户和显示所有用户的一个简单demo。
// config.js
module.exports = {
// MySQL数据库联接配置
mysql: {
host: '127.0.0.1',
user: 'root',
password: '',
database:'test', // 数据库名字
port: 3306
}
};
// 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);
});
// 定义文件 controller/userSqlMapping.js
var user = {
insert:'INSERT INTO mytable(id, name) VALUES(?,?)',
queryAll: 'select * from mytable'
};
module.exports = user;
// 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();
}
};
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!');
});
如何触发错误?
// 这是一个捕获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
前端技术更新迭代的节奏非常之快,稍微学慢点都有可能被淘汰。最近对于以react和redux为核心的web端技术架构已经渐渐掌握,想要精通还需要很长的时间。react-native与react一脉相承,是我试水app开发的最佳切入点。刚刚开始学习,再加上从来没用过xcode,大大小小的坑肯定踩得不少,列举如下。
#1. react-native init 卡住不动
答案:很可能是vpn问题,请在翻墙的情况下找个好一点的网络。
#2. 每回修改代码都要command+R重新build一下,需要好几秒的时间,不能忍!
答案:在模拟器中选择hardware->shake gesture -> enable Live Reload。 如下图所示。
#3. 代码出错信息在哪儿显示?调试输出信息在哪儿?怎么调试?
没错,一开始我都不知道在哪儿查看调试结果(哎,没用过xcode的伤不起啊!)
当build出错的时候,模拟器会直接报错,比如像这样。
身为前端程序员,我第一反应就是寻找能不能在浏览器进行调试,答案是可以的。
在模拟器的hardware-> shake gesture -> debug in chrome,然后浏览器就会打开一个新的窗口,像这样。
程序中的console.log可以直接在控制台看到,也可以打断点进行调试,太棒了!!
不过这里还有一个问题就是不能直接在浏览器中看到界面。(我暂时还没有找到能再浏览器中看到界面的方法,其实我主要的目的是调整样式啊!!网页开发的时候可以直接查看元素的布局,然后直接编辑实时看到效果的呀!没有这个开发效率瞬间降低了好多啊啊!!)
还好后来找到了好歹找到一个查看样式布局的地方(注意,只能查看,不能直接编辑),那就是hardware->shake gesture -> show inspector ,就能看到像下面这个样子了。
接下来这方面还有几个点要研究一下:如何在真机上进行测试。如何在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版本以上修复了这个问题,升级版本就可以了。
随着团队的扩大,项目的增多,脚手架的缺失显得愈发地不可忍受。其问题主要有二:
一开始我是想自己写一个的,思路是这样的:
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");
})
})
});
});
}
});
现在回头看这段代码,真的是惨不忍睹。。。
参考资料:
之前在项目中引入了scss-lint,参考 #19 。在用的时候踩到一些坑,记录在这个地方。
// 比如这样的代码会报错!
.refresh {
margin-left: 10px;
color: $color;
}
显然不科学啊!margin-left当然应该排在color前面啊!
后来发现scss-lint默认属性排序是按照字母排序的......参考这里 sds/scss-lint#463
解决方法:重新设置属性顺序,参考这里
linters:
PropertySortOrder:
order: concentric
项目中要用到数据可视化,我选择了Chart.js,确实挺好用的。但是因为Chart.js是用canvas实现的,IE8及以下不支持canvas。官方给出的解决方案在这里。
如果只是引入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>
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的话,最好把动画关闭了。
在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进去就可以了。😊
总会不时地碰到发现一个ie下的兼容性问题,但是又不成体系,就把它们当做集合写在这儿了。
#1.数组末尾多余逗号
var ary = [1,2,3,]
在IE8下会被解析成下面的语句
var ary = [1,2,3,null]
所以,请不要手贱多写逗号...定义对象的时候多写逗号倒是不会出错
var demo = {
name:"lsf",
}
// 在IE8没问题,据说在IE6/7下会报错
用过redux的人都知道,action和actionType中都存在大量的重复代码。
(没有了解过redux的请移步这里)
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返回数据的地方,有很多函数会长下面这个样。(当然,像那些异步请求那么复杂的除外)
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');
//to do
最近在做的项目需要兼容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
假设我们要完成这样一个需求:页面内有一个按钮和一个数字,每次点击这个按钮,数字都会加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,那是因为函数声明被提升了。)
前些日子在用yeoman自定义自己的脚手架的时候忽然对终端交互产生了兴趣。
这些下拉框,单选框,复选框到底是怎么做的呢?
后来了解到yeoman用的是**inquirer.js**。
npm install inquirer
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);
});
code review很难,主要基于以下原因。
除了一般的HTML、CSS、JS规范,在进行 code review的时候还需要注意以下几点。
我深信: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;
}
未完待续。。。。
以前写代码一般只关注正确与否,但是随着技术的不断深入,开始重视代码规范与否,再加上进来着力推进团队的code review,所以有了eslint的这个探索,发现这是个好东西,跟大家分享一下。
npm install eslint
eslint --init
初始化之后会生成一个配置文件.eslintrc,所有跟eslint相关的配置都写在这儿。
// 检查test1.js文件
eslint test1.js
因为目前我常用的技术栈是react+redux+webpack+babel+gulp,所以eslint势必要集成到这里面去。
当前eslint的稳定版本并不支持jsx,但是可以通过插件eslint-plugin-react完成。
解决方案:eslint-loader
按照里面的流程走就可以了,但是发现下面的这个配置好像是多余的。
module.exports = {
eslint: {
configFile: 'path/.eslintrc'
}
}
估计原因是因为eslint会自动从当前目录开始自动搜索.eslintrl配置文件。
大概的原因是因为require和module都属于common.js里面的东西,webpack打包的时候能识别,但是假如我把环境只设定为浏览器的话就不能识别了,所以得在环境添加上common.js配置项。
大概长这个样子。
"env": {
"es6": true,
"browser": true,
"commonjs": true,
},
当我们在写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提供了忽略某些文件某些代码行的功能,
(2)直接使用eslint-plugin-react插件,具体情况可以参考这个。eslint/eslint#4821
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
我在redux项目中想这样子写reducer。
return {
...state,
currentQuestionNum: state.currentQuestionNum + 1,
hoverOption: -1,
selectedOption: -1
};
eslint在进行语法检查的时候说不认识这个"...."。
解决方案:babel-eslint
这个可以让eslint支持所有babel能支持的语法
昨天在看《程序员修炼之道》,其中说到“破窗理论”是导致程序腐败的原因。忽然联想到前两天跟同事在处理某些gulp的warning时候的分歧。
这是我们在启动一个项目装包时候碰到的warning(其实这个warning我已经看到过好多次了,不过一直忽视它)当时我认为时间紧迫,这些warning并不影响开发,不必理会。同事则认为应该改过来。
其实这些warning虽然不影响功能,但是却是一个糟糕的做法,在最求足够好的程序员眼中是不可忍受的(当然,足够好也应该充分考虑时间因素)。当遇到“破窗”的时候,正确的做法是修复它。如果时间不允许,则应该设法让其不扩大影响(比如不再使用这种糟糕的做法)或者让其更加醒目以便有时间的时候改进(比如添加todo注释)
我要用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代码仓库中搜索,结果被我找到了这个。简直泪流满面啊!
结论:当你使用某个东西碰到解决不了的困难的时候,可以考虑一下直接在它的github仓库中搜索,或许会有意想不到的答案。
用es6一段时间了,某次想把组件内部的函数都改写成箭头函数。比如
render : function(){
console.log(this.props);
}
改写成下面这样的
render () => {
console.log(this.props); // 报错,this是undefined
}
结果显示this是undefined,我就感觉箭头函数的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
搞清楚了箭头函数的this问题,但是由于我还没搞清楚react定义组件的时候作用域的问题,所以暂时没办法搞明白如何在组件中使用箭头函数,以后有时间再研究研究,未完待续。。。。
以前用jquery开发的时候经常使用jquery.lazyload进行图片的滚动加载,但是到了react体系之后就不太好了,因为不能直接操作DOM元素。所以就找了这个组件react-lazy-load。
npm install --save react-lazy-load
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;
这样子的写法有一个问题,那就是页面render的时候,组件table就会render,只是没显示出来而已。这意味着,如果这个组件需要异步请求数据,那么这个异步请求还是没有办法实现滚动到了再请求的。我想了一个解决方案,那就是:“在要滚动加载的组件中取出,然后把写在其父组件当中,亲测有效。
多重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>
);
}
});
刚刚开始接触babel的时候,是在用webpack打包的时候使用的,比如这样子。
后来因为要编译Object.assgin(这里还踩过一次坑,见 #3 ),把所有babel参数提出来放在.babelrc文件中,比如像这样就可以了。
.babelrl 文件
{
"presets":["react","es2015","stage-0"],
"plugins": ["transform-object-assign"]
}
继回合1 #46 之后,开始准备回合2。这次我重新整理了一下思路。
针对第一个难点,经人指点,我知道应该在package.json的bin字段中定义我这个命令。其实平常我们输入cd,ls这些命令,shell之所以认识,是因为它们在环境变量$path中能找到。但是全局安装的npm module并不在$path中啊。npm的解决方案是,对于全局安装的,有bin字段的包,**在安装的时候会主动建立一个链接,从$path指向该module的某个可执行文件。**就像如图所示的第二行箭头那样。
其实这个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);
};
....
把功能模块划分好之后,剩下函数的编写就不难了,完整的代码在这里
至此,已经完成了第一套项目脚手架的搭建,之后第二,第三套就跟脚手架没啥关系了。通过这次自己手动写脚手架,主要有两个收获。
遗留问题点:
在组件开发的时候我们常常会碰到类似这样的情况:满足某个特定条件的时候给某元素添加上一个类,否则不添加。之前一直用的方法是这样的:
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和这种动态加载类名结合起来的方法,以后有时间要研究一下。
在做项目的时候发现ie8不兼容Array.prototype.forEach方法,找到了两个解决方案,jquery.each和es5-shim
$.each(array, function(key, value){
// array[key] === value;
});
第二种解决方案是检测是否有这个方法,如果没有,自定义它,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
随着组件写得越来越大,越来越复杂,组件对自身的自定就显得越来越重要,要是一个组件没有任何关于自身的说明,别人怎么调用你的呢?
参考react的官方说明文档:
http://facebook.github.io/react/docs/reusable-components.html#prop-validation
组件可以通过proptypes校验传进来的数据类型,当向 props 传入无效数据时,JavaScript 控制台会抛出警告。除了数据校验以外,proptypes也是一个很好的说明组件调用api的地方。
注意为了性能考虑,只在开发环境验证 propTypes。虽然文档中列举了各个规则,但是在这里却没有明确指出如何实现只在开发环境严重propTypes这一个功能。后来我找到了集成在webpack当中方法。
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")
}
});
在项目实际开发中,我们常常需要重复性地解决一下两个问题。
有三个工具可以让我们避免重复造轮子。
如果我要判断一个对象是否存在的话,可不是那么容易,请参考阮一峰老师的文章
但是在is.js里面只需要一句
//todo
刚刚开始用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)
};
}
在用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>
)
}
});
这样看来,凡是跟环境相关的都可以通过类似的方法进行解决。
我在使用chart.js的时候碰到不少IE8的兼容性问题,其中包括动画问题 #29 。
chart.js官方文档推荐使用Modernizr来进行浏览器特性检测,进而决定是否显示动画。
Modernizr可以检测很多浏览器的特性,在它的官网可以随意勾选你所需要的检测特性,然后生成js文件,比如这次我只需要检测canvas,所以大概这样。
引入了modernize.custom.js文件之后,如果要检测某个特性,只需要这样。
if (Modernizr.awesomeNewFeature) {
console.log('this feature support!');
} else {
console.log('this feature do not support!');
}
PS:开发环境中也可以直接选择development build,这样就包含所有特性的检测了。
本文说4件事。
mocha是众多自动化测试框架之一,chai也是众多断言库之一。
npm install mocha -g
npm install chai -g
// 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');
}
})
});
mocha test
上面的代码是不是很多的冗余?假如有很多个describe或者it,该如何组织划分呢?
// 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');
}
});
});
原先我的多个reducer是写在一个js文件当中,然后再通过combinereducers结合起来。
后来在写测试用例的时候发现这样不好,所以把每个reducer拆成一个js文件。
然后在rootreducer结合起来,这样就可以针对不同的reducer写单元测试了。
// 假设我有一个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
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目录下面,比如我的目录结构是这样的。
mocha --compilers js:babel-core/register --recursive
// mocha有很多配置参数,这里的compilers表示测试前用babel对其进行编译,因为我的reducer用了ES6语法。
// --recursive表示级联测试,会搜索并执行本目录下所有的测试
小技巧:假如不想每次测试都输入这么多东西,可以将这行命令写入package.json的script当中,比如像这样。
那么以后只要执行npm test,就相当于执行相对应的命令了。
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]
最近项目中用得比较多的ejs渲染和表单,有些小问题比较有趣,值得记录下来。
在form表单中有且只有一个input框的前提下,当你在这个唯一的input框下按下enter键的话,表单会自动提交,url上会带上表单参数,页面会自动刷新 奇怪的是,当页面的input框多于1个就不会出现这样的情况。有个人专门对此作了试验,参考这里
我承认,在用ejs后端渲染的时候前端js想要拿到渲染变量值得方法真的不太优雅,不过有时候也是没有办法的事儿。因为ejs解析完成之后只可能传输纯文本,所以想要接收Object类的对象需要进行一些转换。
// 后端渲染的时候先将变量字符串化
var tempDemo = '<%= JSON.stringify(demoObject) %>';
// 前端js执行的时候再把字符串转化成JSON
var demo = JSON.parse(tempDemo);
以前做某个项目的时候就用过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
我们都知道input和textarea都有placeholder属性,那么能不能在select下拉框中也实现类似的功能呢?答案是可以的。
<select name="source" id="source">
<option value="" disabled="" selected="" hidden="">请选择来源</option>
<option value="1">个人</option>
<option value="2">学校</option>
<option value="3">平台</option>
</select>
下面的情况我们经常遇到:
从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了。
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.