概念
框架与库
Angular是一个框架,而React是一个库。
React 本身不允许创建Web应用程序,因为它旨在创建视图(因此在MVC中为“ V”)。 React 可以用来构建可重复使用的 UI 组件,是个提供工具的 library。
Angular 是一个生态完整的框架(MV*),包含模板,数据双向绑定,路由,模块化,服务,过滤器,依赖注入等所有功能。
数据更新
Angular
数据更新检查:
- 在 Angular 中有一个
Zone.js
负责监听需要视图变化的事件触发 - 每一个组件都都它自己的检测器(detector),用于负责检查其自身模板上绑定的变量。
- 将旧值跟新值进行比较,不相等就说明检测到变化,更新对应视图
React
频繁操作 DOM 会导致大量的回流和重绘,会造成网页的卡顿。所以 React 采用虚拟 DOM 实现视图更新。
虚拟 DOM 如何运行?
- 创建阶段:首先依据 JSX 和基础数据创建出来虚拟 DOM,它反映了真实的 DOM 树的结构。然后由虚拟 DOM 树创建出真实 DOM 树,真实的 DOM 树生成完后,再触发渲染流水线往屏幕输出页面。
- 更新阶段:如果数据发生了改变,那么就需要根据新的数据创建一个新的虚拟 DOM 树;然后 React 比较两个树,找出变化的地方,并把变化的地方一次性更新到真实的 DOM 树上;最后渲染引擎更新渲染流水线,并生成新的页面。
Virtual Dom 优势在于直接频繁的操作 DOM 效率远低于操作 JavaScript 的效率,使用 JavaScript 的成本取代 DOM 执行成本。
数据绑定和数据流
单向数据绑定:Model 的更新会触发 View 的更新,而 View 的更新不会触发 Model 的更新,它们的作用是单向的。
双向数据绑定:Model 的更新会触发 View 的更新,View 的更新也会触发 Model 的更新,它们的作用是相互的。
React
React 采用单向数据绑定
当用户访问 View 时,通过触发 Events 进行交互,而在相应 Event Handlers中,会触发对应的 Actions,而 Actions 通过调用 setState
方法对 View 的 State 进行更新,State 更新后会触发 View 的重新渲染。
所以,在 React 中,View 层是不能直接修改 State,必须通过相应的 Actions 来进行操作。
Angular
Angular 支持单向数据绑定和双向数据绑定
单向数据绑定:使用[x]
属性绑定、(x)事件绑定或插值形式{{data}}
。
双向数据绑定:使用[(x)]
(香蕉船)语法,用户对 View 的更改会直接同步到 Model。
但是,React 和 Angular 都只是单向数据流。虽然 Angular 有双向数据绑定,但 Angualr 父子组件之间数据传递,仍然遵循单向数据流,即父组件可以向子组件传递 props
,但是子组件不能修改父组件传递来的 props
,子组件只能通过事件通知父组件进行数据更改。
组件通信
通信情况分类
- 父组件向子组件通信
- 子组件向父组件通信
- 非父子组件通信
-
跨级组件通信
-
没有嵌套关系组件之间的通信
-
父传子
React
父组件中通过给子组件设置属性,将数据传递给子组件,子组件通过 props
来接收,当父组件更改自己状态的时候,子组件接收到的属性就会发生改变。
const Child = ({ name }) => {
<div>{name}</div>
}
class Parent extends React.Component {
constructor(props) {
super(props)
this.state = {
name: 'jack'
}
}
render() {
return (
<Child name={this.state.name} />
)
}
}
Angular
通过 @Input
装饰器把数据从父组件传到子组件
// 父组件
@Component({
selector: 'app-hero-parent',
template: `
<app-hero-child *ngFor="let hero of heroes"
[hero]="hero"
</app-hero-child>
`
})
export class HeroParentComponent {
heroes = HEROES;
}
// 子组件
@Component({
selector: 'app-hero-child',
template: `
<h3>{{hero.name}} says:</h3>
`
})
export class HeroChildComponent {
@Input() hero: Hero;
}
子传父
React
子组件通过 回调函数 向父组件传递数据。父组件将自己的某个方法作为 props
传递给子组件,子组件通过 this.props
接收到父组件的方法后调用该回调函数,就可以向父组件进行通信了。
//子组件
class Child extends Component{
state={
name:"admin",
age:18
}
childClickHandle=()=>{
this.props.showInfo({address:"beijing"})
}
render(){
return (
<div>
//方式一:直接调用父组件的方法
<button onClick={this.props.showInfo.bind(this,this.state)}>按钮</button>
//方式二:先调用自身的方法,再调用父组件的方法
<button onClick={this.childClickHandle}>按钮</button>
</div>
)
}
}
//父组件
class Parent extends Component{
clickHandle(data){
//data为子组件中传递过来的数据
console.log(data);
}
render(){
return <Child showInfo={this.clickHandle.bind(this)}></Child>
}
}
ReactDOM.render(
<Parent/>,
document.getElementById('root')
);
Angular
方法一:@output + EventEmitter
@output
这种常见的通信,本质是给子组件传入一个函数,在子组件里执行完某些方法后,再执行传入的这个回调函数将值传给父组件。
// 子组件里定义一个 EventEmitter 类型的输出属性并发送(emit)
@Output() public childEventEmitter = new EventEmitter<any>();
ngOnInit(){
this.childEventEmitter.emit('向父组件发送数据');
}
// 父组件中绑定这个来自子组件的 EventEmitter 输出属性并作出回应,$event为子组件传递来的数据
<app-child (childEventEmitter)="childEventEmitter($event)"></app-child>
方法二:父组件调用 @viewChild
父组件不能使用数据绑定来读取子组件的属性或调用子组件的方法。但可以在父组件模板里,新建一个本地变量 #viewChild
来代表子组件,然后利用这个变量来读取子组件的属性和调用子组件的方法。
这个本地变量方法是个简单便利的方法。但是它也有局限性,因为父组件-子组件的连接必须全部在父组件的模板中进行。父组件本身的代码对子组件没有访问权。
// 父组件
<div>
<button class=" btn btn-primary" (click)="viewChild.myName(viewChildInputName.value)">局部变量传值</button>
<app-view-child-child #viewChild></app-view-child-child>
</div>
// 子组件
@Component({
selector: 'app-view-child-child',
templateUrl: './view-child-child.component.html',
styleUrls: ['./view-child-child.component.css']
})
export class ViewChildChildComponent implements OnInit {
constructor() { }
name: string;
myName(name: string) {
this.name = name ;
}
ngOnInit() { }
}
非父子组件通信
React
- Context
- Redux
- Mobx
Angular
- Service
- RxJS - Subject 订阅发布模式
参考
- www.infoq.cn/article/3zj…
- www.cuelogic.com/blog/what-a…
- 虚拟 DOM 和 实际 DOM 有何不同?