In den letzten Jahren hat der Faule vielleicht nicht über React Hooks geschrieben. Ich habe mich auch entschieden.
Ich erinnere mich an den ersten Eindruck - den WOW-Effekt. Sie müssen keine Klassen schreiben. Sie müssen den Zustandstyp nicht beschreiben, die Zustände im Konstruktor initialisieren, den gesamten Zustand in ein Objekt verschieben und sich daran erinnern, wie setState
der neue Zustand mit dem alten zusammengeführt wird. Sie müssen keine Methoden componentDidMount
und keine componentWillUnmount
verwirrende Logik für die Initialisierung und Freigabe von Ressourcen mehr erzwingen .
Hier ist eine einfache Komponente: ein steuerbares Textfeld und ein Zähler, der auf einem Timer um 1 erhöht und auf Knopfdruck um 10 verringert wird;
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);
}
}
wird noch einfacher:
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>;
}
Die Funktionskomponente ist nicht nur zweimal kürzer, sie ist auch klarer: Die Funktion passt in einen Bildschirm, alles ist vor Ihren Augen, die Konstruktionen sind lakonisch und klar. Schönheit.
In der realen Welt sind jedoch nicht alle Komponenten so einfach. Lassen Sie uns unser Komponentensignal die Möglichkeit eines Elternteils fügen Sie die Zahlen und Strings zu ändern, und die Elemente input
, und button
Austausch von Komponenten Input
und Button
das wird Wrap - Handler Haken Ereignisse erfordern 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);
}
}
Was zu tun ist? In schwierigen Fällen zu Klassenkomponenten zurückkehren? Nein, wir lieben die Möglichkeiten, die Haken bieten.
Ich schlage vor, die Handler, die den Code überladen, zusammen mit den Abhängigkeiten in das Klassenobjekt zu verschieben. Ist das nicht besser
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);
}
};
}
Der Komponentencode ist wieder einfach und elegant. Event-Handler und Abhängigkeiten drängen sich friedlich in der Klasse zusammen.
Hook useMembers
und Basisklasse sind trivial:
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;
}