Dalam beberapa tahun terakhir, mungkin pemalas belum menulis tentang React hooks. Saya juga mengambil keputusan.
Saya ingat kesan pertama - efek WOW. Anda tidak perlu menulis kelas. Anda tidak perlu mendeskripsikan tipe status, menginisialisasi status dalam konstruktor, mendorong seluruh status menjadi satu objek, ingat cara setState
menggabungkan status baru dengan yang lama. Anda tidak lagi harus memaksakan metode componentDidMount
dan componentWillUnmount
logika inisialisasi dan pelepasan resource yang membingungkan.
Berikut adalah komponen sederhana: kotak teks yang dapat dikontrol dan penghitung yang bertambah 1 pada timer dan berkurang 10 dengan mengklik tombol;
import * as React from "react";
interface IState {
numValue: number;
strValue: string;
}
export class SomeComponent extends React.PureComponent<{}, IState> {
private intervalHandle?: number;
constructor() {
super({});
this.state = { numValue: 0, strValue: "" };
}
render() {
const { numValue, strValue } = this.state;
return <div>
<span>{numValue}</span>
<input type="text" onChange={this.onTextChanged} value={strValue} />
<button onClick={this.onBtnClick}>-10</button>
</div>;
}
private onTextChanged = (e: React.ChangeEvent<HTMLInputElement>) =>
this.setState({ strValue: e.target.value });
private onBtnClick = () => this.setState(({ numValue }) => ({ numValue: numValue - 10 }));
componentDidMount() {
this.intervalHandle = setInterval(
() => this.setState(({ numValue }) => ({ numValue: numValue + 1 })),
1000
);
}
componentWillUnmount() {
clearInterval(this.intervalHandle);
}
}
menjadi lebih sederhana:
import * as React from "react";
export function SomeComponent() {
const [numValue, setNumValue] = React.useState(0);
const [strValue, setStrValue] = React.useState("");
React.useEffect(() => {
const intervalHandle = setInterval(() => setNumValue(v => v - 10), 1000);
return () => clearInterval(intervalHandle);
}, []);
const onBtnClick = () => setNumValue(v => v - 10);
const onTextChanged = (e: React.ChangeEvent<HTMLInputElement>) => setStrValue(e.target.value);
return <div>
<span>{numValue}</span>
<input type="text" onChange={onTextChanged} value={strValue} />
<button onClick={onBtnClick}>-10</button>
</div>;
}
Komponen fungsional tidak hanya dua kali lebih pendek, tetapi lebih jelas: fungsinya cocok dengan satu layar, semuanya ada di depan mata Anda, konstruksinya singkat dan jelas. Kecantikan.
Namun di dunia nyata, tidak semua komponen begitu sederhana. Mari tambahkan sinyal komponen kita kemungkinan dari orang tua untuk mengubah angka dan string, dan elemen input
, dan button
penggantian komponen Input
dan Button
itu akan membutuhkan event hook penangan bungkus useCallback
.
interface IProps {
numChanged?: (sum: number) => void;
stringChanged?: (concatRezult: string) => void;
}
export function SomeComponent(props: IProps) {
const { numChanged, stringChanged } = props;
const [numValue, setNumValue] = React.useState(0);
const [strValue, setStrValue] = React.useState("");
const setNumValueAndCall = React.useCallback((diff: number) => {
const newValue = numValue + diff;
setNumValue(newValue);
if (numChanged) {
numChanged(newValue);
}
}, [numValue, numChanged]);
React.useEffect(() => {
const intervalHandle = setInterval(() => setNumValueAndCall(1), 1000);
return () => clearInterval(intervalHandle);
}, [setNumValueAndCall]);
const onBtnClick = React.useCallback(
() => setNumValueAndCall(- 10),
[setNumValueAndCall]);
const onTextChanged = React.useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
setStrValue(e.target.value);
if (stringChanged) {
stringChanged(e.target.value);
}
}, [stringChanged]);
return <div>
<span>{numValue}</span>
<Input type="text" onChange={onTextChanged} value={strValue} />
<Button onClick={onBtnClick}>-10</Button>
</div>;
}
: useCallback
, . onBtnClick
useEffect
setNumValueAndCall
, useCallback
, (setNumValueAndCall
) . , - , onBtnClick
useEffect
setNumValueAndCall
.
. , .
.
export class SomeComponent extends React.PureComponent<IProps, IState> {
private intervalHandle?: number;
constructor() {
super({});
this.state = { numValue: 0, strValue: "" };
}
render() {
const { numValue, strValue } = this.state;
return <div>
<span>{numValue}</span>
<Input type="text" onChange={this.onTextChanged} value={strValue} />
<Button onClick={this.onBtnClick}>-10</Button>
</div>;
}
private onTextChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
this.setState({ strValue: e.target.value });
const { stringChanged } = this.props;
if (stringChanged) {
stringChanged(e.target.value);
}
}
private onBtnClick = () => this.setNumValueAndCall(- 10);
private setNumValueAndCall(diff: number) {
const newValue = this.state.numValue + diff;
this.setState({ numValue: newValue });
const { numChanged } = this.props;
if (numChanged) {
numChanged(newValue);
}
}
componentDidMount() {
this.intervalHandle = setInterval(
() => this.setNumValueAndCall(1),
1000
);
}
componentWillUnmount() {
clearInterval(this.intervalHandle);
}
}
Apa yang harus dilakukan? Dalam kasus yang sulit, kembali ke komponen kelas? Ya, tidak, kami hanya menyukai kemungkinan yang dibawa oleh pengait.
Saya mengusulkan untuk memindahkan penangan yang mengacaukan kode ke objek kelas bersama dengan dependensi. Bukankah itu lebih baik?
export function SomeComponent(props: IProps) {
const [numValue, setNumValue] = React.useState(0);
const [strValue, setStrValue] = React.useState("");
const { onTextChanged, onBtnClick, intervalEffect } =
useMembers(Members, { props, numValue, setNumValue, setStrValue });
React.useEffect(intervalEffect, []);
return <div>
<span>{numValue}</span>
<Input type="text" onChange={onTextChanged} value={strValue} />
<Button onClick={onBtnClick}>-10</Button>
</div>;
}
type SetState<T> = React.Dispatch<React.SetStateAction<T>>;
interface IDeps {
props: IProps;
numValue: number;
setNumValue: SetState<number>;
setStrValue: SetState<string>;
}
class Members extends MembersBase<IDeps> {
intervalEffect = () => {
const intervalHandle = setInterval(() => this.setNumValueAndCall(1), 1000);
return () => clearInterval(intervalHandle);
};
onBtnClick = () => this.setNumValueAndCall(- 10);
onTextChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
const { props: { stringChanged }, setStrValue } = this.deps;
setStrValue(e.target.value);
if (stringChanged) {
stringChanged(e.target.value);
}
};
private setNumValueAndCall(diff: number) {
const { props: { numChanged }, numValue, setNumValue } = this.deps;
const newValue = numValue + diff;
setNumValue(newValue);
if (numChanged) {
numChanged(newValue);
}
};
}
Kode komponen sekali lagi sederhana dan elegan. Penangan acara, bersama dengan dependensi, berkumpul dengan damai di kelas.
useMembers
Kelas hook dan dasar itu sepele:
export class MembersBase<T> {
protected deps: T;
setDeps(d: T) {
this.deps = d;
}
}
export function useMembers<D, T extends MembersBase<D>>(ctor: (new () => T), deps: (T extends MembersBase<infer D> ? D : never)): T {
const ref = useRef<T>();
if (!ref.current) {
ref.current = new ctor();
}
const rv = ref.current;
rv.setDeps(deps);
return rv;
}