響應式表單
angular的設計原則和react最大的差別是,後者基本是state就是一切,但前者會把各種功能包裝好,signal雖然很像state,但實踐中似乎很少直接操作signal。官方的設計原則就是把最佳實踐封裝好,大部分情況下只用官方封裝好的工具就夠了。而不是react那樣給你最小的內容,自己去組裝。signal反而是受了react的影響才後來推出,但總體上來說,angular還是大而全的,以封裝好的工具為主。
不過用類比來做知識遷移還是很合適的。
FormControl 從這個開始。這個有點類似於react的useState,主要的作用是定義單個狀態值,在react裡需要手動更新,但這邊都給封裝好了。
//react写法,需要自己更新set
const [username, setUsername] = useState('');
<input value={username} onChange={e => setUsername(e.target.value)} />
//angular写法,自动更新
usernameControl = new FormControl('');
<input [formControl]="usernameControl" />
FormControl物件接受的元素,除了第一個是值外,還同時接受單個或者多個驗證器物件
//单个直接写
username: new FormControl('', Validators.required)
//多个就用list
email: new FormControl('', [Validators.required, Validators.email]),
這算是form的最小物件了,有下面這些屬性
| 屬性 | 含義 | 何時為 true |
|---|---|---|
pristine | 原始狀態 | 使用者未修改過 |
dirty | 髒資料 | 使用者已修改過 |
untouched | 未觸碰 | 使用者未訪問過(未 focus) |
touched | 已觸碰 | 使用者已訪問過(blur 後) |
valid | 驗證通過 | 無驗證錯誤 |
invalid | 驗證失敗 | 有驗證錯誤 |
disabled | 已停用 | 控件被停用 |
enabled | 已啟用 | 控件可用 |
FormGroup 這個就是一堆 FormControl 的封裝,就是字面意思的group
//每一个都要显示声明
loginForm = new FormGroup({
username: new FormControl(''),
email: new FormControl(''),
password: new FormControl('')
});
// 取值
this.loginForm.value // { username: '', email: '', password: '' }
this.loginForm.get('username').value // ''
//html里面用属性取值,然后用formControlName取对应的key
<form [formGroup]="loginForm">
<input formControlName="username" />
<input formControlName="email" />
<input formControlName="password" />
</form>
FormBuilder 上面也能看出來,直接用FormGroup非常麻煩,所以官方提供了一個封裝好的工廠類,直接一步到位。
// 不用 FormBuilder
// 写法比较繁琐
loginForm = new FormGroup({
username: new FormControl('', Validators.required),
email: new FormControl('', [Validators.required, Validators.email]),
});
// 用 FormBuilder
// 一次性搞定,这也是为什么定义值的时候用的是list
// 这里的逻辑其实和操作 FormControl 时没差别,值和验证器(单个或list)
constructor(private fb: FormBuilder) {}
loginForm = this.fb.group({
username: ['', Validators.required],
email: ['', [Validators.required, Validators.email]],
});
Validators 這個是驗證器。原生的react裡沒有封裝好的驗證其,得用額外的庫,沒有就只能自己寫hook。但angular這邊官方就做了一個出來,但同時也可以自己實現。
// 自定义验证器,接受一个control对象,返回错误对象或null
function forbiddenName(control: AbstractControl) {
return control.value === 'admin' ? { forbiddenName: true } : null;
}
// 和官方定义好的一样,将验证器函数放到验证器的位置
username = new FormControl('', [Validators.required, forbiddenName]);
valueChanges 類似react裡的副作用useEffect函式,這個函式會在指定資料變動時觸發,也就是字面意義上的副作用。這裡兩者的功能是類似的。
// react的副作用写法
useEffect(() => {
console.log('username changed:', username);
// 下面是绑定的数据对象
}, [username]);
// 下面是valueChanges的做法,任何Control对象都有这个方法
// 而其返回结果是一个Observable,所以需要订阅
// 这里是监听单个数据对象
this.usernameControl.valueChanges.subscribe(value => {
console.log('username changed:', value);
});
// 这里是监听整个表单数据对象
this.loginForm.valueChanges.subscribe(formValue => {
console.log('form changed:', formValue);
});
FormArray 這個相當於react裡提前定義好的陣列狀態,但可以直接用類似array的方法來操作,而不必要每次都繁瑣的用...複製再更新整個狀態。
// 这里就很麻烦,原生react的写法相当于每次都要把更新整个列表的逻辑写进去
const [phones, setPhones] = useState(['']);
const addPhone = () => setPhones([...phones, '']);
const removePhone = (i) => setPhones(phones.filter((_, idx) => idx !== i));
//定义方法类似,但这里内部要求一个FormControl对象
phoneNumbers = new FormArray([
new FormControl('')
]);
addPhone() {
//这个调用确实更方便
this.phoneNumbers.push(new FormControl(''));
}
removePhone(i: number) {
this.phoneNumbers.removeAt(i);
}