React with Webpack
React
React是facebook团队打造的前端js框架,现在很多人都在使用React构造自己的前端项目,可见React已经非常成熟了,而且React的生态系统也非常完善。之前在segment fault和tui cool上拜读了很多关于React的文章,有幸能在新项目中来运用React。最开始就参照别人的demo写了一些简单的模块,懵懵懂懂也并没有理解React的优越性。在摸索了一段时间之后,发现React使用起来真是得心应手啊,自己也试着写一点对于React的总结:
React 使前端代码模块化
React认为一切皆模块,一段js代码是一个模块,一个css样式文件是一个模块。这样一个较复杂的项目就可以分成很多个完全独立的模块,每个同事并行开发,然后再将这些模块组装好。
React支持也推荐使用CommonJs规范编写代码,使用require加载一个模块,使用exports对外暴露接口。如果你写过nodejs应该对这种语法非常熟悉。
var lib = require('lib.js'); //加载对于lib模块的依赖
lib.method(); //然后就可以调用lib模块的方法
//_innerFun属于内部方法,对外是不可见的
function _innerFun(){}
function funA(){
_innerFun();
}
function funB(){}
//对外暴露的模块接口调用
module.exports = function() {
//仅暴露需要的方法,其他均视为私有属性
return {
methodA: funA,
methodB: funB
}
}
React 使我们改变传统的js编码思路
我们经常吐槽js代码不好维护,别说是别人写的代码了,就连我们自己写的代码在一段时间之后也感觉读不懂了(就算写了一堆注释)!一部分原因是代码组织不够清晰,另一方面由于我们平时都需要使用jQuery操作DOM,代码中大部分逻辑都是和DOM产生了复杂的关系。而且别人要是稍微改了一下DOM结构,我们的代码逻辑可能就需要重新组织一次了。
React推荐不要直接操作DOM,需要将DOM与数据操作逻辑解耦合。使用jsx语法(将js代码和html代码组织在一起,内部解析机制是,遇到<tagName>
就通过html语法解析,遇到{expression}
就通过javascript语法解析),和我们用过的模板引擎有些相似。需要注意的是:
关键字冲突
由于html和javascript写在一起,难免会有语法冲突。比如class、for等既是html属性又是javascript关键字。所以React将这些特殊html用特殊的关键字代替,class => className
,for => htmlFor
更多用法请参考React API
事件绑定
React事件绑定参考原始的在DOM上写事件监听的方法。比如<a href="#" onClick={this.clickFunc}>Click</a>
这里我们就在某个DOM上绑定了一个我们自定义的clickFunc方法。①React推荐事件代理的方式去实现事件监听,我们尽量把事件监听写在父级容器上再作分发。②在clickFunc中我们从默认的arguments中拿到的event是React包装后的事件对象,常用的事件属性都可以访问,但是如果你需要访问原始event对象可以通过event.nativeEvent得到。
语法规范
在jsx语法中,并不是像我们平时写html代码那么随意。①DOM属性有限制,比如我们需要在一个DOM上加上一个myAttr属性,我们想这样写<div myAttr="myVal"></div>
,然而通过React渲染出来的div却把myAttr丢掉了,因为React认为这不是一个合法的写法,我们可以这样写<div data-myAttr="myVal"></div>
,也就是说,React推荐使用data-作为自定义属性的规范。②标签嵌套要规范。我们平时写html代码时可能会忽略标签的嵌套规则,比如<p className="outerP">asdasdsa<p className="innerP">xxxxx</p></p>
,在P标签中嵌套了另一个P标签,这种写法被React解析出来会是怎么样的呢?<p class="outerP"><span>asdasdsa</span></p> <p class="innerP">xxxxx</p> <p></p>
,咦,asdasdsa被包裹了一层span。但是……真是哔了个狗了,原本的嵌套关系不存在了,而且还多出了一个空的P标签是什么鬼?使用div嵌套可破之~所以我们嵌套html标签时务必注意规则,避免产生不必要的bug。
React 神秘的虚拟DOM
为什么React这么火?因为React的虚拟DOM机制:通过state与DOM分离,并非数据一改变就会重新渲染整个DOM结构,React内部有一个复杂的diff算法,它会计算出真正需要改变的DOM元素,保留不变的DOM渲染真正改变的DOM,很大程度上提高了页面渲染效率,在PC上可能效果不够明显,这在移动端是非常有必要的!
React 和 Angular
很多人把React和Angular(1.x)对比(我承认我也曾努力寻找这方面的答案),其实他们的宗旨不一样,谈不上孰好孰坏,下面我们看一下两者的区别。
Angular追求的是一种大而全的概念;React则渴望成为小而精
Angular致力于MVVM(Model - View - ViewModel)的javascript框架,Angular提供了包括$scope、路由(Route)、控制器(Controller)、服务(Service)、指令(Directive)的明确MV*开发思路(如果你使用过后端开发代码,你会发现我们现在使用javascript代码分离简直烂的一比,和JAVA的Spring MVC相比,javascript一直处于一个垃圾堆一样的环境),Angular正是出于将垃圾分类的目的耀世而生。
其实和Angular相比React都算不上一个javascript框架,我想React仅仅是作为一种前端模块化的解决方案。要和Angular比较的话,React应该只属于Angualr的View层。React推崇模块化理念,在React中一切资源(js代码、css代码)皆模块。React支持AMD、CommonJs、ES6的模块化语法规范,相比AMD个人更偏爱CommonJS语法,因为它的语法更清晰明了。如果你写过JAVA代码,那么你对import一定不陌生,在JAVA代码中,class之间完全独立,如果要想引用其他class,就需要import其他class,这样代码的依赖关系一目了然。
题外话:前一段时间还在纠结该使用seajs还是requirejs对于前端代码的模块化,听说seajs理念先进,按需加载,可是打包和兼容老代码麻烦;requirejs先把模块全部加载完全,生态系统和兼容性好,但配置又太麻烦。现在使用Webpack + React或者Gulp + Browserify一切迎刃而解,简直不能太爽!
Angular的双向绑定 vs React的状态机
要说Angualr1.x和React各自的看家本领,那么Angualr就是data-bind对于数据的双向绑定,React则是state状态机映射的虚拟DOM。
Angualr的ViewModel对于Model和View是双向的,也就是说可以在Model层改变ViewModel达到更改View渲染的目的,也可以再View层改变ViewModel达到更改Model的目的。这样我们不需要关心数据的同步更新,我们只需要明确在Controller、Service、Directive所操作的是同一个ViewModel。然而这样的双向绑定在Angular内部通过脏检查实现,对性能开销特别大,而且一个ViewModel只要发生改变,用于渲染整个ViewModel的View层就需要完全刷新,这对于开发手机WEB应用而且数据量比较大时是致命的!听说在Angular2.x将采用完全区别于1.x版本的机制,而且这个性能问题也会得到完美解决,让我们拭目以待!
React内部有两个比较特别的属性:props和state。props是Render时传递进来的,props应该像常量一样是不可变的。state是用于模块内部渲染使用的状态机,一旦状态机发生改变,React就会计算出新的DOM结构并渲染出来,也可以理解为state对于view的单向绑定。虽然没有Angular的双向绑定,但是React的虚拟DOM足够强大,比如一个state存放了10000条的list,我们改变了其中一条,那么页面上仅会对这一条发生改变的数据绑定DOM作出刷新,其他9999则会保持不变。这也正是我们所需要的!
Angular想得更周到;React更灵活
Angualr内部提供了很多$service,而且采用依赖注入的方式调用。比如$http、$filter、$location、$compile等很方便的调用,而React除了React还是React,连ajax也没有实现。当然React会显得更灵活,模块之间的通信都要自己实现,想怎么写怎么写……一般我们采用订阅发布方式(PubSub)实现模块之间的通信。值得一提的是React的Mixins能够实现类似JAVA的面向切面编程。
React 之Hello world
首先我们需要在index.html中加入对React库的引用
<script type="text/javascript" src ="react.js"></script>
<script type="text/javascript" src ="JSXTransformer.js"></script>
我们在index.html中创建一个container
<div id="helloContainer"></div>
编写HelloWorld模块
//注意type是text/jsx |
调用React渲染 Done!
//通过标签形式渲染模块 | 选中页面中的某个DOM元素作为模块的加载容器React.render(<HelloWorld data={name: "Hooh"}></HelloWorld>, document.getElementById('#helloContainer'));
还是比Angular简单易上手,只是jsx语法可能一开始会有点不适应
Webpack
从上面的例子我们可以看到加载了JSXTransformer.js,这是用来解析jsx语法的,也就是我们所写的jsx代码并不能够直接被浏览器执行,需要先借助JSXTransformer解析成javascript代码供浏览器执行。一种更好的解决方案是在后端对jsx代码解析成普通的javascript代码,页面直接引用解析后的javascript代码,那么你需要借助webpack来实现了。当然,这仅仅是一个切入点。webpack的强大之处太多了,让我数数……总之开始webpack就对了。那么我们开始一个进阶的栗子:
树形结构
----app |
lib中存放了用到的库文件
src中存放开发环境的代码模块
build中存放打包输出的上线环境文件
index.html静态页面
webpack.config.js webpack配置文件
var webpack = require('webpack'); |
代码片段
index.html 入口页面
|
common.jsx 公共文件
//在weback配置文件中配置了别名,可直接引用 |
main.jsx 入口js文件
var React = require("react"); |
MsgList.jsx msg列表模块
require('../../css/msg.css'); |
MsgInput.jsx msg输入模块
require('../../css/msg.css'); |
webpack 使用技巧
webpack 执行一次开发模式的编译
webpack -p 执行一次发布环境(production)的编译,会压缩代码(minification)
webpack -d 生成SourceMaps
webpack —watch 无需任何配置,监听到文件改变时(真正require到的文件)会自动编译,而且速度非常快!