前言
无论是Vue、React还是Angular,双向绑定一定是个重要且必须掌握的知识点。双向绑定,即
视图(UI)⇌数据模型state(同步)。当后台数据变化时,UI会自动更新,当用户在UI中输入内容时,后台数据也会自动更新。下面我将讲一下Vue、React、Angular框架是怎么实现数据的双向绑定的。
一、双向绑定
Vue的双向绑定(v-model)
Vue的双向绑定是最直接简单的,代码如下:
<input v-model="username">
// v-model是一个语法糖,等价于下面的写法
<input :value="msg" @input="msg = $event.target.value">
data(){
return{
username:'',
msg:''
}
}
Vue使用Object.defineProperty(Vue2)或Proxy(Vue3)实现对数据的拦截,并通过监听DOM事件来更新数据。它的使用很简单利用v-model就可以实现双向绑定。
React的双向绑定(value+onChange)
相比于Vue,由于React为单向数据流,所以默认只有单向绑定,需要开发者手动实现双向同步:
import React, { useState } from 'react';
function UsernameInput() {
// 创建一个状态变量 username,用来存储输入框的值
const [username, setUsername] = useState('');
// 监听输入变化,更新状态
const handleChange = (event) => {
setUsername(event.target.value);
};
return (
<div>
<input
type="text"
value={username} // 把状态绑定到输入框
onChange={handleChange} // 用户输入时更新状态
placeholder="请输入用户名"
/>
<p>你输入的用户名是:{username}</p>
</div>
);
}
export default UsernameInput;
可以看出来,React是显示控制的,开发者必须手动监听输入并更新数据。这种更偏向函数式编程的方式,会比Vue麻烦一点,不过数据流会更清晰,且便于调试。
Angular双向绑定([(NgModel)])
Angular是支持双向绑定的,主要是通过NgModel
和[()]
语法实现。下面这个例子实现了在页面上动态更新属性firstName
:
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
@Component({
imports: [FormsModule],
template: `
<main>
<h2>Hello {{ firstName }}!</h2>
<input type="text" [(ngModel)]="firstName" />
</main>
`
})
export class AppComponent {
firstName:string = 'Ada';
}
[(ngModel)]是[value]和(input)的语法糖。在使用的时候记得先导入FormsModule。
除了在表单上可以双向绑定,在父子组件之间也可以进行双向绑定,不过需要进行更多的配置:
// ./app.component.ts 父组件
import { Component } from '@angular/core';
import { CounterComponent } from './counter/counter.component';
@Component({
selector: 'app-root',
imports: [CounterComponent],
template: `
<main>
<h1>Counter: {{ initialCount }}</h1>
<app-counter [(count)]="initialCount"></app-counter>
</main>
`,
})
export class AppComponent {
initialCount = 18;
}
// './counter/counter.component.ts'; 子组件
import { Component, model } from '@angular/core';
@Component({
selector: 'app-counter',
template: `
<button (click)="updateCount(-1)">-</button>
<span>{{ count() }}</span>
<button (click)="updateCount(+1)">+</button>
`,
})
export class CounterComponent {
count = model<number>(0);
updateCount(amount: number): void {
this.count.update(currentCount => currentCount + amount);
}
}
同样的,首先在父组件内,通过[(count)]="initialCount"
将子组件上绑定count;然后子组件需要额外注意的是:子组件必须包含一个model
属性,即count = model<number>(0);
然后后续子组件内更新可以使用update方法实现。
二、条件渲染
同样是三个框架同时对比!
Vue的条件渲染(v-if/v-show)
vue主要通过v-if来实现:
<template>
<div>
<p v-if="loggedIn">欢迎回来!</p>
<p v-else>请先登录</p>
</div>
</template>
<script>
export default {
data() {
return {
loggedIn: false
}
}
}
</script>
实际上,v-show也是条件渲染,它是通过display:none控制,不会销毁DOM元素。
React的条件渲染(函数式)
React的写法更加灵活,也是我最喜欢的,他有多种方法可以实现:
//写法一:三元表达式
function App() {
const [loggedIn, setLoggedIn] = useState(false);
return (
<div>
{loggedIn ? <p>欢迎回来!</p> : <p>请先登录</p>}
</div>
);
}
//写法二:逻辑与(适合只渲染一个分支)
{loggedIn && <p>欢迎回来!</p>}
//写法三:函数里面写if/return,这个可以适合多个分支
function App() {
const [loggedIn, setLoggedIn] = useState(false);
if (!loggedIn) {
return <p>请先登录</p>;
}
return <p>欢迎回来!</p>;
}
React的条件渲染本质上是JS的条件判断+JSX的渲染表达式,没有语法糖,但更加灵活。
Angular的条件渲染(ngIf和@if)
angular里面的条件渲染主要介绍两点,他们以Angular17为分界线。
在Angular17之前,我们通过ngIf来实现条件渲染,需要与ng-template配合使用
<div *ngIf="condition; else elseBlock">条件为真时的内容</div>
<ng-template #elseBlock>条件为假时的内容</ng-template>
else 里面写上锚点,然后在ng-template里面插入这个锚点,进行匹配。
而Angular17+就更加直观,使用类似编程语言的块语法,并支持直接嵌套和多条件分支:
@if (condition) {
<div>条件为真时的内容</div>
} @else if (anotherCondition) {
<div>另一个条件为真时的内容</div>
} @else {
<div>所有条件都为假时的内容</div>
}
如果是新项目的话,推荐无脑选@if,语法更清晰,且性能更好;如果要考虑代码兼容性问题的话,那么可以使用过去的ngIf。
补充:实际上,官方文档里面也提供了另一种控制块,即@switch。实际上就和if以及switch的场景类似啦,这里贴一下:
@switch (userPermissions) {
@case ('admin') {
<app-admin-dashboard />
}
@case ('reviewer') {
<app-reviewer-dashboard />
}
@case ('editor') {
<app-editor-dashboard />
}
@default {
<app-viewer-dashboard />
}
}
三、循环渲染
我们同样将三个框架的循环渲染都放在这,对比着学习与记忆!
Vue的循环渲染(v-for)
vue主要通过v-for实现循环渲染,示例如下:
<template>
<ul>
<li v-for="(item, index) in items" :key="item.id">
{{ index }} - {{ item.name }}
</li>
</ul>
</template>
<script>
export default {
data() {
return {
users: [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }];
}
}
}
</script>
React的循环渲染(JSX+.map())
function UserList() {
const users = [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }];
return (
<ul>
{users.map((user, index) => (
<li key={user.id}>
{index} - {user.name}
</li>
))}
</ul>
);
}
在React中,可以使用原生的map方法,返回一个由标签元素组成的数组,JSX会自动进行处理并渲染。
Angular的循环渲染(*ngFor或@For)
同样以Angular17为分界线。旧语法使用*ngFor:
@Component({
selector: 'app-example',
template: `
<!-- 基本用法 -->
<div *ngFor="let item of items">{{item.name}}</div>
<!-- 带索引和其他本地变量 -->
<div *ngFor="let item of items; index as i; first as isFirst">
{{i}}: {{item.name}} {{isFirst ? '(第一个)' : ''}}
</div>
<!-- 使用trackBy提高性能 -->
<div *ngFor="let item of items; trackBy: trackById">{{item.name}}</div>
`
})
export class ExampleComponent {
items = [
{ id: 1, name: '项目1' },
{ id: 2, name: '项目2' },
{ id: 3, name: '项目3' }
];
trackById(index: number, item: any): number {
return item.id;
}
}
注意:在旧版的*ngFor中,trackBy
后面需要跟一个函数,函数内返回唯一标识符作为key
而Angular17+之后使用@For
@Component({
selector: 'app-example',
template: `
<!-- 基本用法 -->
@for (item of items; track item.id) {
<div>{{item.name}}</div>
}
<!-- 带索引和其他本地变量 -->
@for (item of items; track item.id; let i = $index; let isFirst = $first) {
<div>{{i}}: {{item.name}} {{isFirst ? '(第一个)' : ''}}</div>
}
<!-- 处理空列表 -->
@for (item of items; track item.id) {
<div>{{item.name}}</div>
} @empty {
<div>列表为空</div>
}
`
})
export class ExampleComponent {
items = [
{ id: 1, name: '项目1' },
{ id: 2, name: '项目2' },
{ id: 3, name: '项目3' }
];
}
注意:相比于*ngFor,新版的@for就更加简单,track
后面可以直接跟需要追踪的key!
同时补充一下@for控制块中的隐式变量:
同时,当遍历的对象为空时,可以包含@empty来做一个保险
@for (item of items; track item.name) {
<li> {{ item.name }}</li>
} @empty {
<li aria-hidden="true"> There are no items. </li>
}
这么看起来,ngFor和Vue的比较像,@For和React的比较像哈哈,我个人还是更喜欢新的模式,大家也可以根据个人喜好以及项目适配度来选择开发方式。
今天先分享到这~