本文适用于熟悉Angular 1.x并希望了解更多关于React的开发人员。 我们将研究他们构建富Web应用程序所采用的不同方法,重叠的功能以及React不会尝试填补的空白。
阅读后,您将了解React打算解决的问题,以及如何使用已经掌握的知识开始在自己的项目中使用React。
框架与库
Angular是一个框架 ,而React是仅专注于视图层的库 。 使用框架和一组松散耦合的库都存在成本和收益。
框架试图提供一个完整的解决方案,如果您是一个大型团队的一员,它们可能会帮助您通过模式和约定来组织代码。 但是,拥有较大的API会在编写时增加认知负担,并且您将花费更多时间阅读文档和记住模式-特别是在您仍在学习的早期。
使用带有小型API的松散耦合的库的集合更容易学习和掌握,但这意味着当您遇到问题时,需要使用更多的代码来解决它们或根据需要引入外部库。 这通常导致您必须编写自己的框架来减少样板。
推荐课程
盒子外面
Angular为您提供了用于构建Web应用程序的丰富功能集。 其功能包括:
- 在双curly中具有动态表达式的HTML模板
{{ }}
- 内置指令(例如
ng-model
,ng-repeat
和ng-class
用于扩展HTML的功能 - 用于将逻辑分组并将数据传递到视图的控制器
- 双向绑定是使视图和控制器保持同步的一种简单方法
- 大量的模块,例如
$http
与服务器通信和ngRoute
用于路由 - 用于创建自己的HTML语法的自定义指令
- 依赖注入,用于限制对象对应用程序特定部分的暴露
- 共享业务逻辑服务
- 用于查看格式帮助程序的过滤器。
另一方面,React为您提供:
- 单个curl中具有JavaScript表达式的模板的JSX语法
{ }
- 组件,最类似于Angular的element指令。
当涉及到其余应用程序结构时,React不受限制,它鼓励在框架抽象上使用标准JavaScript API。 您可以使用fetch()代替提供类似$ http的包装来进行服务器通信。 您可以自由使用服务和过滤器之类的构造,但是React不会为其提供抽象。 您可以将它们放在JavaScript模块中,并根据需要在组件中使用它们。
因此,尽管Angular为您提供了更多用于常见任务的抽象,但是React故意避免这样做,以使您更频繁地编写标准JavaScript并在其他所有内容上使用外部依赖项。
自举
初始化Angular应用程序需要一个模块,一个依赖项列表和一个根元素。
let app = angular.module('app', [])
let root = document.querySelector('#root');
angular.element(root).ready(function() {
angular.bootstrap(root, ['app']);
});
React的入口点是将组件渲染到根节点中。 也可能有多个根组件:
let root = document.querySelector('#root');
ReactDOM.render(<App />, root)
范本
角度视图的解剖结构很复杂,并且有很多责任。 HTML模板包含指令和表达式的混合,这些指令和表达式将视图和关联的控制器绑定在一起。 数据通过$scope
遍历多个上下文。
在React中,它的组件一直向下 ,数据从组件树的顶部一直到叶节点沿一个方向流动。 JSX是用于编写组件,将熟悉的XML结构转换为JavaScript的最常用语法。 虽然这确实类似于模板语法,但它会编译为嵌套的函数调用。
const App = React.createClass({
render: function() {
return (
<Component>
<div>{ 2 + 1 }</div>
<Component prop="value" />
<Component time={ new Date().getTime() }>
<Component />
</Component>
</Component>
)
}
})
以下已编译的代码应有助于阐明上面的JSX表达式如何映射到createElement(component, props, children)
函数调用:
var App = React.createClass({
render: function render() {
return React.createElement(
Component,
null,
React.createElement("div", null, 2 + 1),
React.createElement(Component, { prop: "value" }),
React.createElement(
Component,
{ time: new Date().getTime() },
React.createElement(Component, null)
)
);
}
});
模板指令
让我们看一下如何在React组件中编写一些Angular最常用的模板指令。 现在,React没有模板,因此这些示例是位于组件的render
函数中的JSX代码。 例如:
class MyComponent extends React.Component {
render() {
return (
// JSX lives here
)
}
}
ng-repeat
<ul>
<li ng-repeat="word in words">{ word }</li>
</ul>
我们可以使用标准的JavaScript循环机制(例如map
来获取JSX中的元素数组。
<ul>
{ words.map((word)=> <li>{ word }</li> )}
</ul>
ng级
<form ng-class="{ active: active, error: error }">
</form>
在React中,我们留给自己的设备来为className
属性创建以空格分隔的类列表。 为此,通常使用现有功能(例如Jed Watson的classNames) 。
<form className={ classNames({active: active, error: error}) }>
</form>
在JSX中考虑这些属性的方式就像是直接在这些节点上设置属性一样。 这就是为什么它是className
而不是class
属性名称的原因。
formNode.className = "active error";
ng-if
<div>
<p ng-if="enabled">Yep</p>
</div>
if … else
语句在JSX内部不起作用,因为JSX只是函数调用和对象构造的语法糖。 通常,为此使用三元运算符,或者将条件逻辑移到JSX之外的render方法的顶部。
// ternary
<div>
{ enabled ? <p>Enabled</p> : null }
</div>
// if/else outside of JSX
let node = null;
if (enabled) {
node = <p>Enabled</p>;
}
<div>{ node }</div>
ng-show / ng-hide
<p ng-show="alive">Living</p>
<p ng-hide="alive">Ghost</p>
在React中,您可以直接设置样式属性或将实用程序类(例如.hidden { display: none }
)添加到CSS中,以隐藏元素(Angular处理它的方式)。
<p style={ display: alive ? 'block' : 'none' }>Living</p>
<p style={ display: alive ? 'none' : 'block' }>Ghost</p>
<p className={ classNames({ hidden: !alive }) }>Living</p>
<p className={ classNames({ hidden: alive }) }>Ghost</p>
您现在掌握了它。 除了使用特殊的模板语法和属性之外,您还需要使用JavaScript来实现所需的功能。
一个示例组件
React的Components最像Angular的Directives 。 它们主要用于将复杂的DOM结构和行为抽象为可重用的部分。 下面是一个幻灯片演示组件的示例,该组件接受一组幻灯片,使用导航元素呈现图像列表,并跟踪其自身的activeIndex
状态以突出显示活动幻灯片。
<div ng-controller="SlideShowController">
<slide-show slides="slides"></slide-show>
</div>
app.controller("SlideShowController", function($scope) {
$scope.slides = [{
imageUrl: "allan-beaver.jpg",
caption: "Allan Allan Al Al Allan"
}, {
imageUrl: "steve-beaver.jpg",
caption: "Steve Steve Steve"
}];
});
app.directive("slideShow", function() {
return {
restrict: 'E',
scope: {
slides: '='
},
template: `
<div class="slideshow">
<ul class="slideshow-slides">
<li ng-repeat="slide in slides" ng-class="{ active: $index == activeIndex }">
<figure>
<img ng-src="{{ slide.imageUrl }}" />
<figcaption ng-show="slide.caption">{{ slide.caption }}</figcaption>
</figure>
</li>
</ul>
<ul class="slideshow-dots">
<li ng-repeat="slide in slides" ng-class="{ active: $index == activeIndex }">
<a ng-click="jumpToSlide($index)">{{ $index + 1 }}</a>
</li>
</ul>
</div>
`,
link: function($scope, element, attrs) {
$scope.activeIndex = 0;
$scope.jumpToSlide = function(index) {
$scope.activeIndex = index;
};
}
};
});
Angular中的幻灯片组件
请参阅CodePen上的SitePoint ( @SitePoint )的笔角度幻灯片 。
React中的这个组件将呈现在另一个组件内部,并通过props传递幻灯片数据。
let _slides = [{
imageUrl: "allan-beaver.jpg",
caption: "Allan Allan Al Al Allan"
}, {
imageUrl: "steve-beaver.jpg",
caption: "Steve Steve Steve"
}];
class App extends React.Component {
render() {
return <SlideShow slides={ _slides } />
}
}
React组件在this.state
具有局部作用域,您可以通过调用this.setState({ key: value })
。 状态的任何更改都会导致组件重新呈现自身。
class SlideShow extends React.Component {
constructor() {
super()
this.state = { activeIndex: 0 };
}
jumpToSlide(index) {
this.setState({ activeIndex: index });
}
render() {
return (
<div className="slideshow">
<ul className="slideshow-slides">
{
this.props.slides.map((slide, index) => (
<li className={ classNames({ active: index == this.state.activeIndex }) }>
<figure>
<img src={ slide.imageUrl } />
{ slide.caption ? <figcaption>{ slide.caption }</figcaption> : null }
</figure>
</li>
))
}
</ul>
<ul className="slideshow-dots">
{
this.props.slides.map((slide, index) => (
<li className={ (index == this.state.activeIndex) ? 'active': '' }>
<a onClick={ (event)=> this.jumpToSlide(index) }>{ index + 1 }</a>
</li>
))
}
</ul>
</div>
);
}
}
React中的事件看起来像老式的内联事件处理程序,例如onClick
。 不过,请不要感到难过:在后台执行正确的操作并创建高性能的委托事件侦听器。
React中的幻灯片组件
请参阅CodePen上的SitePoint ( @SitePoint )的Pen React SlideShow 。
双向绑定
Angular值得信赖的ng-model
和$scope
构成了一个链接,数据在表单元素和控制器中JavaScript对象上的属性之间来回流动。
app.controller("TwoWayController", function($scope) {
$scope.person = {
name: 'Bruce'
};
});
<div ng-controller="TwoWayController">
<input ng-model="person.name" />
<p>Hello {{ person.name }}!</p>
</div>
React避开了这种模式,转而采用单向数据流。 虽然可以使用两种模式构建相同类型的视图。
class OneWayComponent extends React.Component {
constructor() {
super()
this.state = { name: 'Bruce' }
}
change(event) {
this.setState({ name: event.target.value });
}
render() {
return (
<div>
<input value={ this.state.name } onChange={ (event)=> this.change(event) } />
<p>Hello { this.state.name }!</p>
</div>
);
}
}
此处的<input>
称为“受控输入”。 这意味着只有在调用“ render”功能时(在上例中的每个按键上),它的值才会更改。 组件本身称为“有状态”,因为它管理自己的数据。 不建议将其用于大多数组件。 理想的做法是使组件保持“无状态”,并通过props
将数据传递给它们。
见笔单向数据流在阵营由SitePoint( @SitePoint上) CodePen 。
通常,有状态的容器组件或控制器视图位于树的顶部,下面有许多无状态的子组件。 有关此的更多信息,请阅读哪些组件应该具有状态? 来自文档。
打电话给你的父母
尽管数据沿一个方向向下流动,但可以通过回调在父对象上调用方法。 通常这是响应某些用户输入而完成的。 在将组件重构为最简单的表示形式时,这种灵活性为您提供了很多控制权。 如果重构的组件根本没有任何状态,则可以将它们编写为纯函数。
// A presentational component written as a pure function
const OneWayComponent = (props)=> (
<div>
<input value={ props.name } onChange={ (event)=> props.onChange(event.target.value) } />
<p>Hello { props.name }!</p>
</div>
);
class ParentComponent extends React.Component {
constructor() {
super()
this.state = { name: 'Bruce' };
}
change(value) {
this.setState({name: value});
}
render() {
return (
<div>
<OneWayComponent name={ this.state.name } onChange={ this.change.bind(this) } />
<p>Hello { this.state.name }!</p>
</div>
)
}
}
如果您熟悉双向数据绑定,那么乍一看似乎是一种绕行模式。 有很多的,只是接受数据作为小表象“哑巴”成分的好处props
,并将其呈现的是,他们在默认情况下简单,简单的部件少很多错误。 这也可以防止UI处于不一致状态,如果数据位于多个位置并且需要单独维护,则经常会发生这种情况。
依赖注入,服务,过滤器
JavaScript模块是处理依赖关系的更好方法。 您现在可以将它们与Webpack , SystemJS或Browserify之类的工具一起使用 。
// An Angular directive with dependencies
app.directive('myComponent', ['Notifier', '$filter', function(Notifier, $filter) {
const formatName = $filter('formatName');
// use Notifier / formatName
}]
// ES6 Modules used by a React component
import Notifier from "services/notifier";
import { formatName } from "filters";
class MyComponent extends React.Component {
// use Notifier / formatName
}
听起来不错。 我可以同时使用!!
是! 可以在现有的Angular应用程序内部渲染React组件。 本·纳德尔(Ben Nadel)在截屏中撰写了一篇很好的文章,内容涉及如何在Angular指令中渲染React组件 。 还有ngReact ,它提供了react-component
指令来充当React和Angular之间的粘合剂 。
如果您在Angular应用程序的某些部分遇到渲染性能问题,则可以通过将某些渲染委托给React来获得性能提升。 话虽这么说,但包括两个解决许多相同问题的大型JavaScript库并不理想。 即使React只是视图层,它的大小也与Angular大致相同,因此根据您的用例,重量可能会过高。
尽管React和Angular解决了一些相同的问题,但是他们以非常不同的方式来解决它。 React支持一种功能性的声明式方法,其中组件是没有副作用的纯函数。 这种编程的功能风格导致更少的错误,并且更易于推理。
Angular 2呢?
Angular 2中的组件在很多方面类似于React组件。 文档中的示例组件具有非常接近的类和模板。 事件看起来很相似。 它说明了如何使用Component Hierarchy构建组件视图,就像在React中构建视图一样,它包含用于依赖项注入的ES6模块。
// Angular 2
@Component({
selector: 'hello-component',
template: `
<h4>Give me some keys!</h4>
<input (keyup)="onKeyUp($event)" />
<div>{{ values }}</div>
`
})
class HelloComponent {
values='';
onKeyUp(event) {
this.values += event.target.value + ' | ';
}
}
// React
class HelloComponent extends React.Component {
constructor(props) {
super()
this.state = { values: '' };
}
onKeyUp(event) {
const values = `${this.state.values + event.target.value} | `;
this.setState({ values: values });
}
render() {
return (
<div>
<h4>Give me some keys!</h4>
<div><input onKeyUp={ this.onKeyUp.bind(this) } /></div>
<div>{ this.state.values }</div>
</div>
);
}
}
Angular 2上的许多工作一直在使其更加高效地执行DOM更新。 以前的模板语法和范围的复杂性导致大型应用程序出现许多性能问题。
完整的申请
在本文中,我专注于模板,指令和表单,但是,如果您要构建完整的应用程序,则将需要其他条件来帮助您最小化管理数据模型,服务器通信和路由。 当我第一次学习Angular和React时,我创建了一个示例Gmail应用程序,以了解它们的工作原理,并在我开始在实际应用程序中使用它们之前了解开发人员的体验。
您可能会发现浏览这些示例应用程序以比较React和Angular的差异很有趣。 React的例子是用CJSX用CoffeeScript 编写的 ,尽管React社区从那时起就通过Babel和Webpack聚集在ES6上 ,因此,如果您从今天开始,那是我建议采用的工具。
您还可以看一下TodoMVC应用程序进行比较:
学习资源
学习React非常有趣,它教给我更多有关函数式编程的知识,并拥有一个充满活力的社区,围绕它,他们为React生态系统贡献了自己有趣的作品。 Andrew Ray在React和Flux上写了一些很棒的介绍性帖子,官方的React教程是入门的入门之地。 请享用!
- 对愚蠢的人做出反应– Andrew Ray
- 愚蠢人的助焊剂–安德鲁·雷(Andrew Ray)
- React教程– Facebook
- 反应路由器– Ryan Florence
- Redux – Dan Abramov的视频系列
本文由Craig Bilner进行了同行评审。 感谢所有SitePoint的同行评审人员使SitePoint内容达到最佳状态!
From: https://www.sitepoint.com/react-for-angular-developers/