Angular 和 React 从概念到使用对比

2,786 阅读5分钟

概念

框架与库

Angular是一个框架,而React是一个库。

React 本身不允许创建Web应用程序,因为它旨在创建视图(因此在MVC中为“ V”)。 React 可以用来构建可重复使用的 UI 组件,是个提供工具的 library。

Angular 是一个生态完整的框架(MV*),包含模板,数据双向绑定,路由,模块化,服务,过滤器,依赖注入等所有功能。

数据更新

Angular

数据更新检查:

  1. 在 Angular 中有一个 Zone.js 负责监听需要视图变化的事件触发
  2. 每一个组件都都它自己的检测器(detector),用于负责检查其自身模板上绑定的变量。
  3. 将旧值跟新值进行比较,不相等就说明检测到变化,更新对应视图

React

频繁操作 DOM 会导致大量的回流和重绘,会造成网页的卡顿。所以 React 采用虚拟 DOM 实现视图更新。

虚拟 DOM 如何运行?

  1. 创建阶段:首先依据 JSX 和基础数据创建出来虚拟 DOM,它反映了真实的 DOM 树的结构。然后由虚拟 DOM 树创建出真实 DOM 树,真实的 DOM 树生成完后,再触发渲染流水线往屏幕输出页面。
  2. 更新阶段:如果数据发生了改变,那么就需要根据新的数据创建一个新的虚拟 DOM 树;然后 React 比较两个树,找出变化的地方,并把变化的地方一次性更新到真实的 DOM 树上;最后渲染引擎更新渲染流水线,并生成新的页面。

image.png Virtual Dom 优势在于直接频繁的操作 DOM 效率远低于操作 JavaScript 的效率,使用 JavaScript 的成本取代 DOM 执行成本。

数据绑定和数据流

单向数据绑定:Model 的更新会触发 View 的更新,而 View 的更新不会触发 Model 的更新,它们的作用是单向的。

双向数据绑定:Model 的更新会触发 View 的更新,View 的更新也会触发 Model 的更新,它们的作用是相互的。

image.png

React

React 采用单向数据绑定

image.png

当用户访问 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,子组件只能通过事件通知父组件进行数据更改。

image.png

组件通信

image.png

通信情况分类

  • 父组件向子组件通信
  • 子组件向父组件通信
  • 非父子组件通信
    • 跨级组件通信

    • 没有嵌套关系组件之间的通信

父传子

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 订阅发布模式

参考