撤销一个公共修改

Undo a “public” change

场景:你刚刚用 git push 将本地修改推送到了GitHub,这时你意识到在提交中有一个错误。

你想撤销这次提交。使用撤销命令:git revert

发生了什么:git revert 将根据给定SHA的相反值,创建一个新的提交。

如果旧提交是 matter,那么新的提交就是 anti-matter ——旧提交中所有已移除的东西将会被添加进到新提交中,
旧提交中增加的东西将在新提交中移除。

这是 Git 最安全、也是最简单的“撤销”场景,因为这样不会修改历史记录——你现在可以 git push下刚刚 revert 之后的提交来纠正错误了。

修改最近一次的提交信息

Fix the last commit message

场景:你只是在最后的提交信息中敲错了字,
比如你敲了 git commit -m “Fxies bug #42″
而在执行 git push 之前你已经意识到你应该敲 ”Fixes bug #42″

使用撤销命令:git commit –amendgit commit –amend -m “Fixes bug #42″ ;

发生了什么:git commit –amend 将使用一个包含了刚刚错误提交所有变更的新提交,来更新并替换这个错误提交。
由于没有 staged 的提交,所以实际上这个提交只是重写了先前的提交信息。

撤销本地更改

Undo “local” changes

场景:你正在编辑的文件被保存了但是你的编辑器恰在此时崩溃了。
此时你并没有提交过代码。你期望撤销这个文件中的所有修改——将这个文件回退到上次提交的状态。

使用撤销命令:git checkout —

发生了什么:git checkout 将工作目录 (working directory) 里的文件修改成先前 Git 已知的状态。
你可以提供一个期待回退分支的名字或者一个确切的SHA码Git 也会默认检出 HEAD——即:当前分支的上一次提交。

注意:用这种方法“撤销”的修改都将真正的消失。它们永远不会被提交。
因此Git不能恢复它们。此时,一定要明确自己在做什么!(或许可以用 git diff 来确定)

重置本地修改

Reset “local” changes

场景:你已经在本地做了一些提交(还没push),
但所有的东西都糟糕透了,你想撤销最近的三次提交——就像它们从没发生过一样。

使用撤销命令:git resetgit reset –hard

发生了什么:git reset 将你的仓库纪录一直回退到指定的最后一个SHA代表的提交,
那些提交就像从未发生过一样。

默认情况下,git reset 会保留工作目录(working directory)
这些提交虽然消失了,但是内容还在磁盘上。
这是最安全的做法,但通常情况是:你想使用一个命令来“撤销”所有提交和本地修改——那么请使用 –hard 参数吧。

撤销本地后重做

Redo after undo “local”

场景:你已经提交了一些内容,并使用 git reset –hard 撤销了这些更改(见上面),
突然意识到:你想还原这些修改!

使用撤销命令:git refloggit reset , 或者 git checkout

发生了什么:git reflog 是一个用来恢复项目历史记录的好办法。你可以通过 git reflog 恢复几乎任何已提交的内容。

你或许对 git log 命令比较熟悉,它能显示提交列表。
git reflog 与之类似,只不过 git reflog 显示的是HEAD变更次数的列表。
一些说明:

  1. 只有HEAD会改变。当你切换分支时,用 git commit 提交变更时,或是用 git reset 撤销提交时,HEAD都会改变。
    但当你用 git checkout –时, HEAD不会发生改变。(就像上文提到的情形,那些更改根本就没有提交,因此 reflog 就不能帮助我们进行恢复了)

  2. git reflog 不会永远存在。Git将会定期清理那些“不可达(unreachable)”的对象。不要期望能够在reflog里找到数月前的提交记录。

  3. reflog 只是你个人的。你不能用你的 reflog 来恢复其他开发者未 push 的提交。
    因此,怎样合理使用 reflog 来找回之前“未完成”的提交呢?这要看你究竟要做什么:

    • 如果你想恢复项目历史到某次提交,那请使用 git reset –hard

    • 如果你想在工作目录(working direcotry)中恢复某次提交中的一个或多个文件,并且不改变提交历史,那请使用 git checkout–

    • 如果你想确切的回滚到某次提交,那么请使用 git reset

crontab入门命令

cron是linux中的守护进程,用来定期执行一些任务。基本的命令包括以下几个:

  • crontab -e  编辑该用户的crontab,当指定crontab 不存在时新建。
  • crontab -l  列出该用户的crontab。
  • crontab -r  删除该用户的crontab。
  • crontab -u <用户名称>  指定要设定crontab的用户名称。
  • crontab –v 显示上一次编辑的时间(只在某些操作系统上可用)

作为一个新用户,首先需要的操作是,执行命令:

1
$ crontab -e

在linux系统中,不同的用户打开的crontab文件是不同的。当你打开你用户所属的crontab,第一次用这个命令,会让你选择文本编辑器。
我的电脑上显示4中编辑器,如下,我选择是vim.basic。

1
2
3
4
1. /bin/ed         
2. /bin/nano
3. /usr/bin/vim.basic
4. /usr/bin/vim.tiny

如果你编辑时,选择错了编辑器,或者是想更改,可以使用下面的命令进行重新选择:

1
$ select-editor

打开后的crontab内容如下:

1
2
# m h  dom mon dow   command      
*/2 * * * * date >> ~/time.log

第二行是我为了测试写的一个定期任务,它的意思是,每隔两分钟就执行 date >> ~/time.log 命令(记录当前时间到time.log文件)。
你可以把它加入你的crontab中,然后保存退出。

保存了crontab之后,我们还需要重启cron来应用这个计划任务。使用以下命令

1
$ sudo service cron restart

开启日志功能

修改配置文件:/etc/rsyslog.d/50-default.conf

1
vim /etc/rsyslog.d/50-default.conf

在配置文件中,把

1
#cron.*       /var/log/cron.log

前面的注释#号去掉就OK了。

重启rsyslog服务

1
service rsyslog restart

再次查看cron日志

1
ls /var/log/cron.log

备注:这里就有了 crontab 日志。

环境变量

crontab 有一个坏毛病,就是它总是不会缺省的从用户 profile 文件中读取环境变量参数,
经常导致在手工执行某个脚本时是成功的,但是到 crontab 中试图让它定期执行时就是会出错.
看了这个就知道怎么修改脚本了,脚本的头上用缺省的 #!/bin/sh 就可以,然后然后第一个部分先写这些:

1
2
3
#!/bin/sh
. /etc/profile
. ~/.bash_profile

奇怪

Phantomjs install

幻影幽灵 Js (名字好霸气呀)

Phantomjs官网

npm install

npm 安装是最简单快捷的方案

1
npm install -g phantomjs

Download

下载地址

phantomjs) 官网 下载 各个平台的包

OS 系统环境

在OSX和Linux上分别可以通过 Homebrewyum(或 apt-get )安装

1
2
3
4
5
6
//osx
brew phantomjs
// centos
yum install phantomjs
// ubuntu
apt-get install phantomjs

Windows上则可以 phantomjs) 官网 直接下载release包,
解压后放置到合适的目录下,然后环境变量中增加bin文件夹的目录即可

无论是OSX、Linux还是Windows,配置完成后执行phantomjs -v,
如果没有报错并打印出了对应的版本号,
就证明安装配置成功了,否则就需要检查一下是哪里出现了问题。

简介

Localtunnel可以方便快捷的实现你的本地web服务通过外网访问,
无需修改DNS和防火墙设置,其实原理与ngrok类似。
但Localtunnel是基于nodejs的,而ngrok是基于go语言。

使用官方提供的Localtunnel服务端

安装localtunnel客户端

localtunnel是基于node.js的一个模块,所以首先需要安装node.js和npm。

1
$  npm install -g localtunnel

使用

假设本地服务器在81端口,我们可以通过下面的命令把本地服务器暴露到公网中

1
2
$   lt --port 81
your url is: https://ouumalrvoi.localtunnel.me

通过上面的命令,我们不需要做其他设置就可以通过 https://ouumalrvoi.localtunnel.me 来访问我们本地服务器了。

如果想通过固定的域名访问,则可以通过以下命令进行设置,成功后可通过 https://zanjs.localtunnel.me 而访问到本地服务器。

1
2
$   lt --subdomain zanjs --port 81
$ lt -s zanjs -p 81

自建Localtunnel服务端

由于localtunnel.me是国外的服务器,访问速度有时候不太理想,这时候我们可以自己搭建localtunnel的服务端。

安装服务端

1
2
3
$   git clone https://github.com/localtunnel/server.git localtunnel-server
$ cd localtunnel-server
$ npm install

使用

以监听2000端口为例:

直接使用

1
2
$   bin/server --port 2000
localtunnel server listening on port: 2000 +0ms

配合pm2使用

1
2
3
4

$ npm install -g pm2

$ pm2 --name lt start bin/server -- --port 2000

启动服务端程序后,我们只要在使用客户端lt时加上–host参数,就可以指定服务端了。

1
2
3
#host后面不要加/
$ lt --host http://zanjs.me:2000 --port 81
your url is: http://zanjs.me:2000

这样,我们就可以通过自己的代理服务器来访问本地服务器了,不用经过第三方代理服务器,不必担心代理服务器的安全问题。

高级用法

反向代理

在Github 上面有一份Nginx的配置,我们可以直接使用,或者按照自己的需要做些修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
proxy_http_version 1.1;

# http://nginx.org/en/docs/http/websocket.html
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}

upstream lt-server {
server 127.0.0.1:2000;
}

server {
listen 80 default_server;
listen [::]:80 default_server ipv6only=on;

server_name .localtunnel.me;

location / {
proxy_pass http://lt-server/;

proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Proto http;
proxy_set_header X-NginX-Proxy true;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;

proxy_redirect off;
}
}

server {
listen 443 default_server ssl spdy;
listen [::]:443 default_server ipv6only=on;

server_name .localtunnel.me;

ssl on;

ssl_certificate /etc/nginx/ssl/STAR.localtunnel.me.crt;
ssl_certificate_key /etc/nginx/ssl/STAR.localtunnel.me.key;

ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers RC4:HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;

ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;

location / {
proxy_pass http://lt-server/;

proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header X-NginX-Proxy true;
proxy_set_header Connection $connection_upgrade;

proxy_redirect off;
}
}

指定子域名

有时候,用随机字符串作为子域名并不是一件好事,
我们可能需要固定的域名来访问本地服务器。这时,lt –subdomain就可以派上用场了。

1
2
3
#subdomain限制长度为4 ~ 63
$ lt --host http://tunnel.zanjs.me:2000 --port 81 --subdomain mysubdomain
your url is: http://mysubdomain.tunnel.zanjs.me:2000

看到了吗?通过–subdomain,我们就可以指定自己喜欢的子域名了。

然而,如果我们通过–host来指定子域名,会发生什么?

1
2
$   lt --host http://tunnel.zanjs.me:2000 --port 81
Error: localtunnel server returned an error, please try again

就算配置了 Nginx 的反向代理,你依然会得到这个错误。
可以查看 #21#31来看更多的细节。

要解决这个问题,最简单的就是不用–host来指定子域名,而用–subdomain来指定。

参考资料

http://www.google.com

https://github.com/localtunnel/localtunnel

https://github.com/localtunnel/server

https://scarletsky.github.io/2016/01/17/localtunnel-usage

基本内容

https://en.wikipedia.org/wiki/Regular_expression
了解一样东西,当然先从WIKI开始最好了。

1
2
3
4
5
6
7
// Regular Expression examples
I had a \S+ day today
[A-Za-z0-9\-_]{3,16}
\d\d\d\d-\d\d-\d\d
v(\d+)(\.\d+)*
TotalMessages="(.*?)"
<[^<>]>

教程

  1. 30分钟入门教程,网上流传甚广
  2. 55分钟教程【英文】
  3. 一本简单的书,每一节就是一块内容
  4. 正则匹配原理解析
  5. stackoverflow 正则标签,标签下有值得点击的链接,一些典型的问题
  6. 正则学习测试于一身
  7. MDN出品,JavaScript方面内容

验证与测试

  1. JavaScript, Python, PCRE 16-bit, generates explanation of pattern
  2. 正则验证测试,清晰明了
  3. 中文版正则验证测试
  4. 测试工具
  5. 也是测试工具,都可以试一试

在线实战

  1. 闯关模式练习正则表达式,完成一个个正则匹配的测验
  2. 通过实际练习掌握正则表达式
  3. 正则挑战,有不同难度,很丰富
  4. 正则挑战,完成正则匹配要求

其它

  1. MSDN 微软出品
  2. 常用正则表达式,如匹配网址、日期啊这种,这个谷歌一搜很多的
  3. 速查表地址

需求说明

H5 分享页 在 微信 WebView 下载按钮点击后,打开 AppStore下载页面;
如果是浏览器打开,则判断是否装了APP,如果装了则打开拼邮界面。

『经浏览器唤起APP』的最佳实现方案是怎样的?

下面 一一作出解决方案

遇到的主要问题

无法判断用户是否安装APP,来采用何种跳转逻辑:

  1. iOS 有原生的 Smart Banner,可以帮助判断是否安装 APP 并跳转,
    但也仅限于 safari 浏览器,并且不可定制样式和跳转URL。
    需要通过 schema 来唤起 APP,因为需要带上具体的页面参数。

  2. 在比较旧的iOS版本里,业界有一个比较通用的办法,是可以通过 iFrame尝试唤起 APP
    以一个时间差来判断用户手机里是否已安装,再来决定是否跳到 appStore
    然而最新的 iOS中,这一套方案已经失效,iFrame 不再有效果。

  3. 尝试过用 setTimeout 来首先直接唤起APP,然后再唤起 appStore
    这样的话,如果没有安装APP,会有一个难看的提示『该网址已经失效』,还需要手动X掉。
    尝试通过 setTimeout 来首先直接唤起 APP ,然后再跳转到另一个页面再唤起 appStore

这样的话新页面虽然可以将『该网址已经失效』给顶掉,
但是同时也会把正常唤起APP的提示『是否打开APP』也给顶掉。

  1. 如果用户是通过微信、qq等内置webview启动这一操作,那么提示用户启用 safari 打开并再走一次以上逻辑。
    有的APP可以直接通过微信qq判断并唤起,
    比如知乎?不知道此中是有友好协议,还是我并没有完全搞清楚原理,需要大家解答一下。

简单调查

简单调查了一下某些其他应用,发现大家并没有一套通用并完美的方案:

  1. 『知乎』及『网易云音乐』提供了两个入口,一个供跳转到APP,一个供跳转到 appStore
    未安装点击跳转到APP会有错误提示。

  2. 腾讯视频只有一个按钮,貌似是第一次点它会给你跳到 appStore ,此后再点它就会给你唤起APP。
    未安装点击会有错误提示。

  3. 简书则是直接唤起APP。未安装点击会有错误提示。

大体上就是这三类方案,可是始终没有一个满足最上面需求的完美方案。

实现方案

  1. 微信客户端与qq浏览器客户端

    1
    2
    3
    4
    5
    跳转到下载地址

    至于为什么?
    有些app会阉割掉深链接的功能,例如微信,QQ。
    这时候就需要引导用户通过打开外部浏览器来实现深链接的功能
  2. ios9 Safari

    1
    2
    直接打开 `Panli APP` 的 移动深链接, 如果安装了 直接弹出是否打开 `Panli APP`, 
    如果未安装,Safari 会弹出『该网址已经失效』,然后跳转到 `appStore`

demo

android

经过小规模的测试,得到以下结论

由于下载走的是 腾讯应用宝 , 在安卓的微信下面,需要借助 腾讯应用宝来打开App 的,
意思就是说,当安卓客户端未安装 应用宝的时候 ,中转页面会有一个 普通下载腾讯应用宝安全打开 2 个选项

  • 普通下载 ,不明显,且点击会有安全提示!

  • 腾讯应用宝安全打开 会先下载应用宝 (家族产品的霸道,有种你别用微信,人在屋檐下呀,囧!)

请注意 非官方已经安装过了 Panli App 点击 腾讯应用宝安全打开 ,会下载 Panli APP

测试机点击没反应 是因为是直接在 开发人员电脑上安装的。

由于 android 的特殊性 以及 Panli APP应用投放的应用商店,下载的地方除e了谷歌应用商店外,只有应用宝了 ,
又由于 国内的安卓设备 谷歌的应用商店几乎都不能用 ,所以以上请注意可以忽略

主要代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
if(isWeixinBrowser() || isQQBrowser()){
window.open(downUrl)
PL.open({
content: '正在为您跳转...',
time: 5
});
}else{
if(isAndroid){
//android
//唤醒app并阻止接下来的js执行
$('body').append("<iframe src='panliapp://openapp' style='display:none' target='' ></iframe>");

//没有安装应用会跳转下载地址
setTimeout(function(){window.location = Android},600);
}else{
//ios
//唤醒app
window.open('panliapp://openapp', "_self");

//没有安装应用会跳到 appStore
setTimeout(function(){window.location = 'itms-apps://itunes.apple.com/app/id590216292'},300);
}
}

通过判断浏览器的userAgent,用正则来判断是否是ios和Android客户端。代码如下:

1
2
3
4
5
var u = navigator.userAgent;
var isAndroid = u.indexOf('Android') > -1 || u.indexOf('Adr') > -1; //android终端
var isiOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); //ios终端
alert('是否是Android:'+isAndroid);
alert('是否是iOS:'+isiOS);

下面一个比较全面的浏览器检查函数,提供更多的检查内容,
你可以检查是否是移动端(Mobile)、ipad、iphone、微信、QQ等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//判断访问终端
var browser={
versions:function(){
var u = navigator.userAgent, app = navigator.appVersion;
return {
trident: u.indexOf('Trident') > -1, //IE内核
presto: u.indexOf('Presto') > -1, //opera内核
webKit: u.indexOf('AppleWebKit') > -1, //苹果、谷歌内核
gecko: u.indexOf('Gecko') > -1 && u.indexOf('KHTML') == -1,//火狐内核
mobile: !!u.match(/AppleWebKit.*Mobile.*/), //是否为移动终端
ios: !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/), //ios终端
android: u.indexOf('Android') > -1 || u.indexOf('Adr') > -1, //android终端
iPhone: u.indexOf('iPhone') > -1 , //是否为iPhone或者QQHD浏览器
iPad: u.indexOf('iPad') > -1, //是否iPad
webApp: u.indexOf('Safari') == -1, //是否web应该程序,没有头部与底部
weixin: u.indexOf('MicroMessenger') > -1, //是否微信 (2015-01-22新增)
qq: u.match(/\sQQ/i) == " qq" //是否QQ
};
}(),
language:(navigator.browserLanguage || navigator.language).toLowerCase()
}

使用方法:

1
2
3
4
5
6
//判断是否IE内核
if(browser.versions.trident){ alert("is IE"); }
//判断是否webKit内核
if(browser.versions.webKit){ alert("is webKit"); }
//判断是否移动端
if(browser.versions.mobile||browser.versions.android||browser.versions.ios){ alert("移动端"); }

检测浏览器语言

1
2
3
4
5
currentLang = navigator.language;   //判断除IE外其他浏览器使用语言
if(!currentLang){//判断IE浏览器使用语言
currentLang = navigator.browserLanguage;
}
alert(currentLang);

第二种:

1
2
3
4
5
6
7
8
9
if (/(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent)) {
//alert(navigator.userAgent);
window.location.href ="iPhone.html";
} else if (/(Android)/i.test(navigator.userAgent)) {
//alert(navigator.userAgent);
window.location.href ="Android.html";
} else {
window.location.href ="pc.html";
};

Webpack your bags
原文: Webpack your bags(by Maxime Fabre)

我的github会持续更新
2016年5月28日 杜梁 翻译。

如果您觉得我的翻译能给你带来点帮助,那就给我点个Star鼓励一下吧^_^,

Webpack your bags

之前你可能已经听说过这个很酷的叫webpack工具,如果你没仔细了解过这个工具,你可能会有些困惑,因为有人说他它是像Gulp之类构建工具,也有人说它是像Browserify之类的bundler,如果你仔细了解一下,你可能还是会不明白是怎么回事儿,因为官网上把webpack说成是这两者。

说实话,开始的时候对于**“webpack到底是什么“很模糊,感觉很受挫,然后我直接就把网页关了,毕竟我已经有一个构建系统了,而且我用得也非常哈皮,如果你紧赶javascript的时髦的话,像我这样,你可能在估计很快因为在各种流行的东西中频繁地跳来跳去而烧的灰飞烟灭了。
现在我有些经验了,我觉得我应该写这篇文章给那些还处在混沌中的小伙伴儿,更清楚解释一下webpack到底是什么,更重要的是它的什么地方那么棒以至于值得我们投入更多的精力。

Webpack是什么?

好的,下面我们来回答一下,介绍里面提到的那个问题,Webpack是一个构建系统还是一个模块bundler(捆绑器),恩-,两个都是 - 我不是说它两个事儿都干,我的意思是它把两者连接起来了,webpack不是先构建你的资源(assets),然后再bundle你的模块,它把你的资源本身当做模块。

更准确的说,它不是先编译你的所有的Sass文件,再优化所有的图片,再在一个地方include进来,然后bundle所有的模块,在另一个地方include到你的page上。假设你在是下面这样:

import stylesheet from 'styles/my-styles.scss';
import logo from 'img/my-logo.svg';
import someTemplate from 'html/some-template.html';

console.log(stylesheet); // "body{font-size:12px}"
console.log(logo); // "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5[...]"
console.log(someTemplate) // "<html><body><h1>Hello</h1></body></html>"

你的所有的资源本身被当成模块,这些模块可以被import,modify,manipulate(操作),最后被打包到你最后的bundle

为了达到这个目的,得在webpack configuration里注册loaders,loader就是当你遇到某种文件的时候,对它做相应的一种处理的一种插件,下面是一些loader的例子:

{
  // When you import a .ts file, parse it with Typescript
  test: /\.ts/,
  loader: 'typescript',
},
{
  // When you encounter images, compress them with image-webpack (wrapper around imagemin)
  // and then inline them as data64 URLs
  test: /\.(png|jpg|svg)/,
  loaders: ['url', 'image-webpack'],
},
{
  // When you encounter SCSS files, parse them with node-sass, then pass autoprefixer on them
  // then return the results as a string of CSS
  test: /\.scss/,
  loaders: ['css', 'autoprefixer', 'sass'],
}

最后在食物链的终点所有的loader返回的都是string,这样可以使webpack把资源最后包装成javascript模块。这个例子里你的Sass文件被loaders转换后,里面看起来差不多是这样。

export default 'body{font-size:12px}';

那你究竟为什么要用它呢?

一旦你明白webpack是做什么的,很有可能就会想到第二个问题:用它能有什么好处呢?images和CSS?还在我的JS里?玩啥呢哥们儿。好的,看下这个:很长时间我一直被告诉要把所有文件都放在一个文件里,这样来保证不浪费我们的request。

但这样导致一个最大的缺点,就是现在大多数人都把所有的资源(assets)bundle到一个单独的app.js文件里,然后把他include每一个页面,这就意味着渲染任意一个页面的时候大部分时间都浪费在加载一大堆根本用不上的资源上。如果你不这么做,那么你很有可能得手动把这些资源include到指定的页面,这就会引出一大团乱遭的依赖树去维护和跟踪如:哪些页面这个需要依赖?哪些页面style A 和Style B 起作用。

这两个方法都不对,也都不是错的。我们把webpack当做一个中间者-他不仅仅是一个构建(build)系统或者一个bundler,它是一个邪恶小精灵般的智能打包(packing)系统,正确地配置后,它甚至比你都了解你的系统栈(stack),而且它比你清楚怎么样最优优化你的系统。

我们来做一个小的app

为了让你更容易地理解webpack带来的好处,我们做一个小的app,然后用它来bundle 资源(assets),在本篇文章里我建议用Node 4 (或 5)和NPM 3因为扁平的依赖树在你用webpack的时候会避免很多让人头疼的麻烦,如果你还没有NPM3,你可以通过 npm install npm@3 -g 来安装。

$ node --version
v5.7.1
$ npm --version
3.6.0

同时我也建议你把 node_modules/.bin 加到你的PATH variable(环境变量里)以避免每次都手动打 node_modules/.bin/webpack ,后面的所有例子都不会显示我要执行的命令行的 node_modules/.bin 部分。

基本引导
我们开始,先创建个project,安装上webpack,我们也加上jquery好在后面证明一些东西。

$ npm init -y
$ npm install jquery --save
$ npm install webpack --save-dev

现在来我们创建一个app入口,用纯ES5 (in plain ES5)

src/index.js

var $ = require('jquery');

$('body').html('Hello');

在一个webpack.config.js文件里建webpack configuration,webpack configuration是javascript,需要export 一个object

webpack.config.js

module.exports = {
    entry:  './src',
    output: {
        path: 'builds',
        filename: 'bundle.js',
    },
};

这里,entry告诉webpack那个文件是你的app的入口点。这些是你的主文件,他们在依赖树的顶端,然后我们告诉它编译我们的bundle 到bundle.js文件放在builds目录下,再创建相应的index HTML

<!DOCTYPE html>
<html>
    <body>
        <h1>My title</h1>
        <a>Click me</a>

        <script src="builds/bundle.js"></script>
    </body>
</html>

跑一下webpack,如果一切正常,会看见一个信息告诉我们正确地编译了bundle.js

$ webpack
Hash: d41fc61f5b9d72c13744
Version: webpack 1.12.14
Time: 301ms
AssetSize  Chunks Chunk Names
bundle.js  268 kB   0  [emitted]  main
   [0] ./src/index.js 53 bytes {0} [built]
+ 1 hidden modules

这里webpack告诉你你的bundle.js包含了我们的入口点(index.js)和一个隐藏的模块,这个就是jquery,默认情况下webpack把不是你的模块会隐藏掉,如果想看见webpack编译的所有的模块 ,我们可以加--display-modules 参数

$ webpack --display-modules
bundle.js  268 kB   0  [emitted]  main
   [0] ./src/index.js 53 bytes {0} [built]
   [1] ./~/jquery/dist/jquery.js 259 kB {0} [built]

你也可以运行 webpack --watch 来自动监视文件的变化,如果有变化,自动重新编译。
搭建我们的第一个loader
现在还记得我们怎么讨论webpack可以导入CSS和HTML还有各种文件的吗?什么时候能排上用场呢?好的,如果你跟随着过去几年朝着web组件方向的大跃进(Angular 2, Vue, React, Polymer, X-Tag, etc.)估计你听过这样一个观点:你的app是如果用一套可重用,自包含的UI组件搭建的话相比较于一个单独的内聚的UI将会变得更容易维护,这个可重用的组件就是web 组件(在这里我说的比较简单),现在为了让组件变成真正的自包含,需要把组件需要的所有的东西封装到他们自己的内部,我们来考虑一个button,它里面肯定有一些HTML,而且还有一些JS来保证它的交互性,还得来一些style,这些东西如果需要的时候一起加载进来会非常棒,只有在我们导入button组件的时候,我们才会得到所有的这些资源文件(asset)。

来我们写一个button,首先我先假设你们已经熟悉了ES2015 ,我们先加入babel这个loader。想要在webpack里安装一个loader,有两步需要做:1.npm install {whatever}-loader,2.把它加到你的Webpack configuration里的module.loaders部分,我们开始,现在我们要加babel,所以:

$ npm install babel-loader --save-dev

我们也得安装 Babel ,因为现在我们这个case,loader不会安装它,我们需要装babel-core packagees2015 preset:

$ npm install babel-core babel-preset-es2015 --save-dev.

我们现在创建.babelrc文件,告诉bable用那个preset,这是一个json文件让你配置Babel在你的code上执行哪种变体,我们现在这个我们我告诉它用es2015preset

.babelrc { "presets": ["es2015"] }

现在babel配完了,我们可以更新我们的配置:我们想要什么?我们想babel在我们所有的.js结尾的文件上运行,但是由于webpack遍历所有的依赖,我们想避免babel运行在第三方代码上,如jquery,所以我们可以再稍微过滤一下,loaders既可以有include也可以由exclude,可以是string,regex(正则表达式),一个callback(回调),随便用哪个。因为我们想让babel值运行在我们的文件上,所以我们只include我们自己的source目录:

module.exports = {
    entry:  './src',
        output: {
            path: 'builds',
                filename: 'bundle.js',
            },
            module: {
            loaders: [
            {
                test:   /\.js/,
                loader: 'babel',
                include: __dirname + '/src',
            }
        ],
    }
};

我们可以用ES6重写一下我们的index.js这个小文件,由于我们引入了babel,从这里开始后面的所有例子都用ES6.

import $ from 'jquery';

$('body').html('Hello');

写个小组件
我们来写一个小的Button 组件,它有一些SCSS style,一个HTML template,和一些behavior,那么我们安装一些我们需要的东西。首先我们用一个非常轻量的模板包Mustache ,我们也需要给Sass和HTML用的loaders,因为结果会通过管道(pipe)从一个loader传到另一个,我们需要一个Sass loader,现在一旦我们有了css,会有多种方式处理它,当前阶段,我们用一个叫style-loader的loader,它会接收一段CSS,然后动态地把它插入到页面。

$ npm install mustache --save
$ npm install css-loader style-loader html-loader sass-loader node-sass --save-dev

为了让webpack用管道(pipe)把东西从一个loader传到另一个loader,我们简传入几个loader方向由右到左,用一个分开,或者你可以用一个数组通过loaders属性,不是loader

{
    test:/\.js/,
    loader:  'babel',
    include: __dirname + '/src',
},
{
test:   /\.scss/,
    loader: 'style!css!sass',
    // Or
    loaders: ['style', 'css', 'sass'],
},
{
    test:   /\.html/,
    loader: 'html',
}

现在我们把loaders准备好了,我们来写一个button:

src/Components/Button.scss

.button {
  background: tomato;
  color: white;
}

src/Components/Button.html

<a class="button" href="{{link}}">{{text}}</a>

src/Components/Button.js

import $ from 'jquery';
import template from './Button.html';
import Mustache from 'mustache';
import './Button.scss';

import $ from 'jquery';
import template from './Button.html';
import Mustache from 'mustache';
import './Button.scss';

export default class Button {
        constructor(link) {
        this.link = link;
    }

    onClick(event) {
        event.preventDefault();
        alert(this.link);
    }

    render(node) {
        const text = $(node).text();

        // Render our button
        $(node).html(Mustache.render(template, {text}));

        // Attach our listeners
        $('.button').click(this.onClick.bind(this));
    }
}

你的Button现在是百分之百自包含的,无论什么时候导入,无论运行在什么context,它有所有需要的东西,然后正确地render(渲染),现在我们只需要把我们的Button render到我们的网页上(page):

src/index.js

js import Button from ‘./Components/Button’;

const button = new Button(‘google.com’); button.render(‘a’); 

我们来运行一下webpack,然后刷新一下页面,你应该就能看见你的挺丑的button出来了。

现在你已经学了怎么配置loader和怎么给你的app的每一个部分定义dependency(依赖),现在可能看起来不太重要这些,但现在我们来把我们的例子再做一下改进:

code 分片(splitting)

这个例子还不错什么都有,但可能我们不总需要我们的Button,可能有些界面不会有 a(link)来让我们渲染Button用,在这中情况下,我们不需要导入所有的Button的style(式样),template(模板),Mustache 和一切相关的东西,对吧?“Monolithic bundle”(单片绑定) VS “Unmaintainable manual imports”(不好维护的手动导入). 这种idea是,你在你的代码里定义split point(分割点):你的code中可以轻松拆分开到单独文件中的部分,然后按需加载,语法非常简单:

import $ from 'jquery';

// This is a split point
require.ensure([], () => {
  // All the code in here, and everything that is imported
  // will be in a separate file
  const library = require('some-big-library');
  $('foo').click(() => library.doSomething());
});

require.ensure callback 里的任何东西都会被拆分成chunk(代码块)-一个webpack需要的时候会通过ajax单独加载的bundle,这意味着我们基本上有这些:

bundle.js
|- jquery.js
|- index.js // our main file
chunk1.js
|- some-big-libray.js
|- index-chunk.js // the code in the callback

你无需导入chunk1.js。webpack只有在需要它的时候才会加载它,这意味着你可以把你的code按照各种逻辑分成多块,我们将在下面的例子中这么做,我们只想在page里有link的的时候才需要Button

src/index.js

if (document.querySelectorAll('a').length) {
    require.ensure([], () => {
        const Button = require('./Components/Button').default;
            const button = new Button('google.com');

        button.render('a');
    });
}

注意,用require的时候,如果你想default export 你需要通过.default手动获取它,
因为require不会同时处理default 和 normal(正常) exports,所以你必须指定返回哪个,不过import有一个系统处理这个,所以它已经知道了(例如: import foo from 'bar')vs import {baz} from 'bar').

webpack的output现在应该会相应地不同了,我们来用--display-chunks flag 来运行一下,来看一下哪个模块在哪个chunk里

$ webpack --display-modules --display-chunks
Hash: 43b51e6cec5eb6572608
Version: webpack 1.12.14
Time: 1185ms
  Asset Size  Chunks Chunk Names
  bundle.js  3.82 kB   0  [emitted]  main
1.bundle.js   300 kB   1  [emitted]
chunk{0} bundle.js (main) 235 bytes [rendered]
[0] ./src/index.js 235 bytes {0} [built]
chunk{1} 1.bundle.js 290 kB {0} [rendered]
[1] ./src/Components/Button.js 1.94 kB {1} [built]
[2] ./~/jquery/dist/jquery.js 259 kB {1} [built]
[3] ./src/Components/Button.html 72 bytes {1} [built]
[4] ./~/mustache/mustache.js 19.4 kB {1} [built]
[5] ./src/Components/Button.scss 1.05 kB {1} [built]
[6] ./~/css-loader!./~/sass-loader!./src/Components/Button.scss 212 bytes {1} [built]
[7] ./~/css-loader/lib/css-base.js 1.51 kB {1} [built]
[8] ./~/style-loader/addStyles.js 7.21 kB {1} [built]

可以看到我们的入口(bundle.js)现在只有一些webpack的逻辑,其它的东西(jQuery, Mustache, Button) 都在1.bundle.js,只有page上有anchor (锚:超链接的意思)的时候才会加载,为了让webpack知道用ajax加载的时候在哪能找到chunks,我们必须在我们的配置中加几行:

path:   'builds',
filename:   'bundle.js',
publicPath: 'builds/',

output.publicPath选项告诉webpack相对于当前的page(我们的case是在 /builds/)在哪能找到built(构建后的)assets (资源),现在访问我们的page我们会看到一切都正常工作,但更重要的是,我们能看到,由于页面上有anchor (锚:超链接的意思),webpack实时地加载加载了chunk:

如果我们page上没有anchor,只有bundle.js会被加载 ,这点可以让你智能地把你的app里的大片逻辑拆分开,让每个页面值加载它真正需要的,我们也可以给我们的split point命名,不用1.bundle.js,我们可以用更有语义的名字,你可以通过传给require.ensure第三个参数来指定:

require.ensure([], () => {
    const Button = require('./Components/Button').default;
    const button = new Button('google.com');

    button.render('a');
}, 'button');

这样就会生成button.bundle.js而不是1.bundle.js.
加入第二个组件
现在啥都有了非常cool了已经,我们再来加一个组件来看看好不好使:

src/Components/Header.scss

.header {
  font-size: 3rem;
}

src/Components/Header.html

<header class="header">{{text}}</header>

src/Components/Header.js

import $ from 'jquery';
import Mustache from 'mustache';
import template from './Header.html';
import './Header.scss';

export default class Header {
    render(node) {
        const text = $(node).text();

        $(node).html(
            Mustache.render(template, {text})
        );
    }
}

我们在我们的application里把它render(渲染)一下:

// If we have an anchor, render the Button component on it
if (document.querySelectorAll('a').length) {
    require.ensure([], () => {
        const Button = require('./Components/Button').default;
        const button = new Button('google.com');

        button.render('a');
    });
}

// If we have a title, render the Header component on it
if (document.querySelectorAll('h1').length) {
        require.ensure([], () => {
        const Header = require('./Components/Header').default;

        new Header().render('h1');
    });
}

现在用--display-chunks --display-modules flags(参数标记)来看一下webpack的output:

$ webpack --display-modules --display-chunks
Hash: 178b46d1d1570ff8bceb
Version: webpack 1.12.14
Time: 1548ms
  Asset Size  Chunks Chunk Names
  bundle.js  4.16 kB   0  [emitted]  main
1.bundle.js   300 kB   1  [emitted]
2.bundle.js   299 kB   2  [emitted]
chunk{0} bundle.js (main) 550 bytes [rendered]
[0] ./src/index.js 550 bytes {0} [built]
chunk{1} 1.bundle.js 290 kB {0} [rendered]
[1] ./src/Components/Button.js 1.94 kB {1} [built]
[2] ./~/jquery/dist/jquery.js 259 kB {1} {2} [built]
[3] ./src/Components/Button.html 72 bytes {1} [built]
[4] ./~/mustache/mustache.js 19.4 kB {1} {2} [built]
[5] ./src/Components/Button.scss 1.05 kB {1} [built]
[6] ./~/css-loader!./~/sass-loader!./src/Components/Button.scss 212 bytes {1} [built]
[7] ./~/css-loader/lib/css-base.js 1.51 kB {1} {2} [built]
[8] ./~/style-loader/addStyles.js 7.21 kB {1} {2} [built]
chunk{2} 2.bundle.js 290 kB {0} [rendered]
[2] ./~/jquery/dist/jquery.js 259 kB {1} {2} [built]
[4] ./~/mustache/mustache.js 19.4 kB {1} {2} [built]
[7] ./~/css-loader/lib/css-base.js 1.51 kB {1} {2} [built]
[8] ./~/style-loader/addStyles.js 7.21 kB {1} {2} [built]
[9] ./src/Components/Header.js 1.62 kB {2} [built]
   [10] ./src/Components/Header.html 64 bytes {2} [built]
   [11] ./src/Components/Header.scss 1.05 kB {2} [built]
   [12] ./~/css-loader!./~/sass-loader!./src/Components/Header.scss 192 bytes {2} [built]

能看见这里有一个主要问题:我们的两个组件都会用到jQuery 和 Mustache,也就是说这两个依赖会在chunk里有重复,wepack默认做很少优化,但是他会以plugin的形式来给webpack提供强大的功能。

plugin跟laoder不同,它不是对指定的文件像pipe一样执行一些操作,他们对所有文件进行处理,做一些更高级的操作,但不一定非得是transformation(转换),webpack有一把plugin可以做各种优化,此时我们比较感兴趣的一个是CommonChunksPlugin:它分析你的chunk的递归依赖,然后把它们抽出来放在别的地方,可以使一个完全独立的文件(如vendor.js)或者也可以是你的主(main)文件。

我们现在的case,我们需要把公用的依赖移到我们的entry(入口) 文件,如果所有的文件需要jQuery 和 Mustache,我们也可以把它往上层移动,我们来更新一下我们的configuration:

var webpack = require('webpack');

module.exports = {
    entry:   './src',
    output:  {
      // ...
    },
    plugins: [
        new webpack.optimize.CommonsChunkPlugin({
            name:      'main', // Move dependencies to our main file
            children:  true, // Look for common dependencies in all children,
            minChunks: 2, // How many times a dependency must come up before being extracted
        }),
    ],
    module:  {
      // ...
    }
};

如果我们重新run一下 Webpack,我们可以看到现在比之前好多了,这里的main是默认chunk的名字

chunk    {0} bundle.js (main) 287 kB [rendered]
    [0] ./src/index.js 550 bytes {0} [built]
    [2] ./~/jquery/dist/jquery.js 259 kB {0} [built]
    [4] ./~/mustache/mustache.js 19.4 kB {0} [built]
    [7] ./~/css-loader/lib/css-base.js 1.51 kB {0} [built]
    [8] ./~/style-loader/addStyles.js 7.21 kB {0} [built]
chunk    {1} 1.bundle.js 3.28 kB {0} [rendered]
    [1] ./src/Components/Button.js 1.94 kB {1} [built]
    [3] ./src/Components/Button.html 72 bytes {1} [built]
    [5] ./src/Components/Button.scss 1.05 kB {1} [built]
    [6] ./~/css-loader!./~/sass-loader!./src/Components/Button.scss 212 bytes {1} [built]
chunk    {2} 2.bundle.js 2.92 kB {0} [rendered]
    [9] ./src/Components/Header.js 1.62 kB {2} [built]
   [10] ./src/Components/Header.html 64 bytes {2} [built]
   [11] ./src/Components/Header.scss 1.05 kB {2} [built]
   [12] ./~/css-loader!./~/sass-loader!./src/Components/Header.scss 192 bytes {2} [built]

如果我们把名字制定为如name: 'vendor':

new webpack.optimize.CommonsChunkPlugin({
    name:      'vendor',
    children:  true,
    minChunks: 2,
}),

由于vendor 这个 chunk还不存在,webpack会创建一个builds/vendor.js,我们手动把它导入到我们的HTML:

<script src="builds/vendor.js"></script>
<script src="builds/bundle.js"></script>

你也可以通过不提供一个公用的(common) chunk name或者是指定async: true来让公用的dependency被异步加载,webpack有非常多的只能优化。我不可能全列出来,但作为练习我们来给我们的app建一个product version

To production and beyond(超越)

好首先,我们加几个plugin到configuration,但我们想只有在NODE_ENV 等于production才加载这些plugin,所以让我们来给configuration加一些逻辑,因为只是一个js 文件,所以比较简单:

var webpack    = require('webpack');
var production = process.env.NODE_ENV === 'production';

var plugins = [
    new webpack.optimize.CommonsChunkPlugin({
        name:      'main', // Move dependencies to our main file
        children:  true, // Look for common dependencies in all children,
        minChunks: 2, // How many times a dependency must come up before being extracted
    }),
];

if (production) {
    plugins = plugins.concat([
       // Production plugins go here
    ]);
}

module.exports = {
    entry:   './src',
    output:  {
        path:       'builds',
        filename:   'bundle.js',
        publicPath: 'builds/',
    },
    plugins: plugins,
    // ...
};

再把几个webpack的设置在production上关掉:

module.exports = {
    debug:   !production,
    devtool: production ? false : 'eval',

第一个设置把转为非debug模式,非debug模式会去掉debug模式中产生的方便调试的code,第2个是控制sourcemap 生成的,webpack有几个方法可以render sourcemap,自带的里面eval是最好的,生产环境我们不太关心sourcemap的事,我们把它disable掉,下面加一下production plugins:

if (production) {
    plugins = plugins.concat([

        // This plugin looks for similar chunks and files
        // and merges them for better caching by the user
        new webpack.optimize.DedupePlugin(),

        // This plugins optimizes chunks and modules by
        // how much they are used in your app
        new webpack.optimize.OccurenceOrderPlugin(),

        // This plugin prevents Webpack from creating chunks
        // that would be too small to be worth loading separately
        new webpack.optimize.MinChunkSizePlugin({
            minChunkSize: 51200, // ~50kb
        }),

        // This plugin minifies all the Javascript code of the final bundle
        new webpack.optimize.UglifyJsPlugin({
            mangle:   true,
            compress: {
                warnings: false, // Suppress uglification warnings
            },
        }),

        // This plugins defines various variables that we can set to false
        // in production to avoid code related to them from being compiled
        // in our final bundle
        new webpack.DefinePlugin({
            __SERVER__:      !production,
            __DEVELOPMENT__: !production,
            __DEVTOOLS__:    !production,
            'process.env':   {
                BABEL_ENV: JSON.stringify(process.env.NODE_ENV),
            },
        }),

    ]);
}

上面这几个是我最长用的,不过webpack提供很多plugin用来优化处理你的modules和chunks,NPM上也有个人用户写的plugin,有各种功能的插件,自行按需选择。

另关于production assets的另一个方面是,我们有时想给asset加版本,还记得在上面我把output.filename设置为bundle.js吧,这有几个变量可以在option里用,其中一个是[hash]代表最后bundle文件的版本,同时用output.chunkFilename给chunk也加上版本:

output: {
    path:          'builds',
    filename:      production ? '[name]-[hash].js' : 'bundle.js',
    chunkFilename: '[name]-[chunkhash].js',
    publicPath:    'builds/',
},

由于没有什么特别好的办法能动态地获取这个简化版app编译后的bundle的名字(由于加了版本号),所以只是在production上给asset加版本,多次编译后目录里带版本的bundle会越来越多,还得加一个第三方插件来每次production build 之前清理一下build folder(output 里面的path):

$ npm install clean-webpack-plugin --save-dev

把它加到configuration里

var webpack     = require('webpack');
var CleanPlugin = require('clean-webpack-plugin');

// ...

if (production) {
    plugins = plugins.concat([

        // Cleanup the builds/ folder before
        // compiling our final assets
        new CleanPlugin('builds'),

欧了,做完了几个小优化,下面比较一下结果:

$ webpack
                bundle.js   314 kB       0  [emitted]  main
1-21660ec268fe9de7776c.js  4.46 kB       1  [emitted]
2-fcc95abf34773e79afda.js  4.15 kB       2  [emitted]

.

$ NODE_ENV=production webpack
main-937cc23ccbf192c9edd6.js  97.2 kB       0  [emitted]  main

to be done …………….(update 2016.6.5 )

去除空格

1
2
3
String.prototype.Trim = function(){ 
return this.replace(/\s+/g, "");
}

去除换行

1
2
3
4
5
function clearBr(key){ 
key = key.replace(/<\/?.+?>/g,"");
key = key.replace(/[\r\n]/g, "");
return key;
}

去除左侧空格

1
2
3
function LTrim(str) { 
return str.replace(/^\s*/g,"");
}

去右空格

1
2
3
function RTrim(str) { 
return str.replace(/\s*$/g,"");
}

去掉字符串两端的空格

1
2
3
function trim(str) { 
return str.replace(/(^\s*)|(\s*$)/g, "");
}

去除字符串中间空格

1
2
3
function CTim(str) { 
return str.replace(/\s/g,'');
}

是否为由数字组成的字符串

1
2
3
4
5
function is_digitals(str) { 
var reg=/^[0-9]*$/;## 匹配整数

return reg.test(str);
}

替换指定字符

1
2
3
4
5
function newStr(str)  

str.replace(/zan/, "Julian")

}

去掉空数组

移除了所有假值的数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function compact(array){
var newArr = array;

for(var i = 0 ;i<newArr.length;i++)
{
if(newArr[i] == "" || typeof(newArr[i]) == "undefined" || newArr[i] == NaN || newArr[i] == null || newArr[i] == false)
{
newArr.splice(i,1);
i= i-1;

}

}


}

真正学会 React 是一个漫长的过程。

漫长的过程

你会发现,它不是一个库,也不是一个框架,而是一个庞大的体系。
想要发挥它的威力,整个技术栈都要配合它改造。
你要学习一整套解决方案,从后端到前端,都是全新的做法。

reactjs

举例来说,React 不使用 HTML,而使用 JSX 。
它打算抛弃 DOM,要求开发者不要使用任何 DOM 方法。
它甚至还抛弃了 SQL ,自己发明了一套查询语言 GraphQL 。
当然,这些你都可以不用,React 照样运行,但是就发挥不出它的最大威力。

这样说吧,你只要用了 React,就会发现合理的选择就是,采用它的整个技术栈。

本文介绍 React 体系的一个重要部分:路由库 React-Router
它是官方维护的,事实上也是唯一可选的路由库。
它通过管理 URL,实现组件的切换和状态的变化,开发复杂的应用几乎肯定会用到。

另外,我没有准备示例库,因为官方的示例库非常棒,由浅入深,分成14步,每一步都有详细的代码解释。
我强烈建议你先跟着做一遍,然后再看下面的API讲解。

基本用法

React Router 安装命令如下。

1
npm install -S react-router

使用时,路由器 Router 就是 React 的一个组件

1
2
3
4
5
import { Router } from 'react-router';

//...

render(<Router/>, document.getElementById('app'));

Router 组件本身只是一个容器,真正的路由要通过 Route 组件定义

1
2
3
4
5
6
7
import { Router, Route, hashHistory } from 'react-router';

render((
<Router history={hashHistory}>
<Route path="/" component={App}/>
</Router>
), document.getElementById('app'));

上面代码中,用户访问根路由 / 比如 http://www.example.com/)

组件 APP 就会加载到 document.getElementById('app')

你可能还注意到,Router 组件有一个参数 history

它的值 hashHistory 表示,

路由的切换由 URLhash 变化决定,即 URL# 部分发生变化。

举例来说,用户访问 http://www.example.com/

实际会看到的是 http://www.example.com/#/

Route 组件定义了URL路径与组件的对应关系。你可以同时使用多个 Route组件。

更多原文