很幸运刚毕业到公司,领导就给我选择了前端开发这个行当,而接触的第一个框架就是Angular1,和大部分人一样,一本大漠老师的《使用AngularJS开发下一代WEB应用》带我入门,也照着写了一些代码,看书的时候也觉得大部分理解了,但是一到实际项目开发完全找不到北了,我想这也是很多人的写照。
就自己的体会,前端开发水平可以大概分为三个层次,每一个层次都是一个质的跨越,第一层
是入门级的(从Angular1来看就是ng-controller党,这样更形象一点),诚然这种模式已经比当年有了极大进步,但是同样的,如今的前端复杂程度大增,这种进步只能算不拖后腿,第二层
是进阶级的,组件化开发已经在心中打下烙印,这些人看到的不是页面,而是一颗组件树(说的好玄啊^ ^),对于一般的团队,我认为能达到这个水平就够用了,每个成员打好基本功,这个团队是很不错的了。第三层
,是我想象的,我认为在他们眼中都是数据,没有UI,数据就是UI,通过精妙的架构体系控制着数据的流动,同时掌控着数据在每一个环节的变化。这里的角度更多是按一个偏产品的前端开发团队来分析的,自己的视野有限欢迎大家讨论。我认为如果达到了第二层就算是脱离贫困奔小康甚至过上小资生活,对于生活(工作)遇到的困难都能比较轻松的应对。我认为一个框架的熟练度达到类似第二层是很重要的,否则盲目的追新是很难达到质的提升,所以希望大家共勉不要盲从,至少要在团队中技术能hold住,再去想技能树更新,这才会事半功倍的。
回到原题,由于项目演进的原因下半年会由Angular1转为React,因此自己是从一个Angular1的开发视角来学习react,有人会问为啥要用视角来学习,为啥要对比,我自己也不知道这是不是一个好的方式,但我个人觉得用一个更熟悉的视角,新知识会记得更深刻,也许文中的一些对比有误,请大家指正,go~
开始
Angular1作为一门09年的颠覆性技术,为前端开发带来了一场革命,但由于那时候没有成熟的模块化方案,没有ES6标准的推动,导致这场革命不是那么彻底。确实Angular1不够组件化,但Angular1带给其他框架的影响是巨大的,react,Vue的出现没有达到Angular1当年的颠覆,react和Vue的核心技术也都有着Angular1的影子(或者说变了形态的影子),因此这篇教程就是带大家在react上找寻Angular1熟悉的味道。
对比哪些
当然是angular.directive和angular.component,这也是Angular1最能体现组件化特征的地方。一般来说一个组件会对外暴露属性
、事件
和方法
。
属性
- angular
在angular1中属性有三种@
,<
,=
,@表示不可修改的属性,最常见的id都会用@来表示,如果@对应的值会变化时,要加上{{}},
<my-comp text={{::someText}}></my-comp>复制代码
修饰符<
的样子就体现了单向数据流的特征,它类似Vue中watch的形式,在$onChanges中可以watch到属性的变化,不用再自己一个一个添加$watch*之类的东东了,
=
这个样子也非常形象,父作用域和隔离作用域的属性都指向同一个地址空间,因此隔离作用域也可以修改父作用域的属性,这往往使得问题难以定位。
- react
react的属性很js,没有angular那么多修饰符或者继承、隔离这些东西,react很开放,它告诉你属性应该像@一样是不可变的,但是它就是一个js对象,你可以改变它,但是你要自己承担所可能带来的后果,那么想想@改怎么写,react的props就该怎么用,想象一下我们在写一个指令叫MyProps,它目前只有一个{id:'@', width: '@'}
angular版
angular.directive('myProps', function(){
return {
restrict: 'E',
scope: {
id:'@',
width: '@'
},
controller:function($scope){
scope.width = scope.width || 200
},
template: `<button id='{{::id}}' width='{{::width}}'></button>`
}
})复制代码
react版
class MyProps extends React.Component {
render() {
return (
<button id={this.props.id} style={{ width: this.props.width + 'px' }}>
idButton
</button>
)
}
}
MyProps.defaultProps = { width: 200 }复制代码
react很js很class吧,其实思路是不是很相似,this.props就和scope一样,只不过react通过jsx实现了表达式计算。
事件
- angular
在angular里事件一般用&
来修饰,如果你希望对响应参数做定制也可以使用=
。
angular版
angular.directive('myProps', function(){
return {
restrict: 'E',
scope: {
id:'@',
width: '@',
click: '&'
},
controller:function($scope){
scope.width = scope.width || 200
},
template: `<button id='{{::id}}' width='{{::width}}' ng-click='click()'></button>`
}
})复制代码
react版
class MyButton extends React.Component {
render() {
return (
<div>
<div id={this.props.id + '_btn'} style={{ width: this.props.w + 'px', height: this.props.h + 'px', backgroundColor: this.props.bgColor }} onClick={this.props.click}>clickMe</div>
</div>
)
}
}
class MyEvent extends React.Component {
constructor(props) {
super(props)
}
clickHandler() {
console.log('My Button Clicked')
}
render() {
return (
<MyButton id="event" click={this.clickHandler.bind(this)} w="200" h="40" bgColor="green" />
)
}
}
ReactDOM.render(
<MyEvent />, document.getElementById('root'))复制代码
你也可以对响应参数做定制,就像angular中把事件修饰&变为=那样,这里就不上angular的了
react版本
class MyButton extends React.Component {
constructor(props) {
super(props)
}
clickHandler() {
this.props.click(this.props.bgColor)
}
render() {
return (
<div>
<div id={this.props.id + '_btn'} style={{ width: this.props.w + 'px', height: this.props.h + 'px', backgroundColor: this.props.bgColor }} onClick={this.clickHandler.bind(this)}>clickMe</div>
</div>
)
}
}
class MyEvent extends React.Component {
constructor(props) {
super(props)
}
clickHandler(color) {
console.log('My Button color: ' + color)
}
render() {
return (
<MyButton id="event" click={this.clickHandler.bind(this)} w="200" h="40" bgColor="green" />
)
}
}
ReactDOM.render(
<MyEvent />, document.getElementById('root'))复制代码
方法
其实方法并不是angular所包含的一个概念,angular提倡数据状态影响UI的理念,除了angular提供的一些ng-内置指令,我们要写一大堆双向绑定属性(伴随着一大堆$watch),性能堪忧,更重要的是你会发现定位问题很难(脏数据校验)。因此在我们项目除了disable、display之类会使用<
,界面变更都采用方法,将指令的UI变更方法绑定到$scope.$parent上,这样父指令就可以通过这些方法来操作子指令的UI变更了。
angular版
angular.module('myApp',[]);
myApp.controller('listCtrl',function($scope){ var display = false
$scope.showOrHide=function(){
$scope.btn.setDisplay(display?'block':'none')
display =!display
};
});
myApp.directive('kid',function(){
return {
'restrict':'E',
scope:{id: '@'},
template:'<div><button>click</button></div>',
link: function(scope, elem) {
scope.$parent[scope.id] = {
setDisplay: function(display){
elem.css('display', display)
}
}
}
}
});复制代码
按照这个思路我们在react上也试试。
class MyButton extends React.Component {
constructor(props) {
super(props)
}
setDisplay(display) {
this.refs.myButton_ref.style.display = display
}
render() {
return (
<div ref="myButton_ref">
<div id={this.props.id + '_btn'} style={{ width: this.props.w + 'px', height: this.props.h + 'px', backgroundColor: this.props.bgColor }}>clickMe</div>
</div>
)
}
}
class MyEvent extends React.Component {
constructor(props) {
super(props)
this.display = false
}
showOrHide() {
this.child.setDisplay(this.display ? 'block' : 'none')
this.display = !this.display
}
render() {
return (
<div>
<MyButton id="event" w="200" h="40" bgColor="green" ref={(instance) => { this.child = instance }} />
<button onClick={this.showOrHide.bind(this)}>showOrHide</button>
</div>
)
}
}复制代码
代码中ref="myButton_ref"
非常别扭,因为直接对dom的操作让我们感觉回到了jquery时代,因此这里我们应该使用一直未提到的state
,那state像angular的什么呢,和指令中的controller很像,你可以在controller里对$scope里增加新的属性(这里也包含了父指令传进来的属性),因此angular里的$scope包含了react中的props和state,react把它分开会让使用者更加清楚,让使用者知道props是不能变的,state才是可变的。因此我们把代码中的MyButton
稍作修改。
class MyButton extends React.Component {
constructor(props) {
super(props)
this.state = { display: 'block' } // 初始值
}
setDisplay(display) {
this.setState({ display })
}
render() {
return (
<div style={{ display: this.state.display }}>
<div id={this.props.id + '_btn'} style={{ width: this.props.w + 'px', height: this.props.h + 'px', backgroundColor: this.props.bgColor }}>clickMe</div>
</div>
)
}
}复制代码
最后
这篇教程主要从自己这个Angular1使用者的的视角来看react,我觉得对于Angular开发人员来说是一个不错的学习react的思路,本人也是刚刚接触react,文中如有错误也请及时指正。下一篇将对Angular1指令的生命周期和react组件生命周期做以对比,谢谢大家。