近年、おそらく怠惰な人はReactフックについて書いていません。私も決心しました。
第一印象-WOW効果を覚えています。クラスを書く必要はありません。状態のタイプを記述したり、コンストラクターで状態を初期化したり、状態全体を1つのオブジェクトにプッシュsetState
したり、新しい状態を古い状態とマージする方法を覚えたりする必要はありません。リソースの初期化と解放のメソッドcomponentDidMount
やcomponentWillUnmount
混乱するロジックを強制する必要がなくなりました。
簡単なコンポーネントは次のとおりです。制御可能なテキストボックスと、タイマーで1ずつ増加し、ボタンをクリックすると10ずつ減少するカウンター。
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);
}
}
さらに簡単になります:
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>;
}
機能コンポーネントは2倍短いだけでなく、より明確です。機能は1つの画面に収まり、すべてが目の前にあり、構造は簡潔で明確です。美しさ。
しかし、現実の世界では、すべてのコンポーネントがそれほど単純なわけではありません。のは、数値と文字列、および要素を変更するには、親の可能性、当社のコンポーネント信号を追加してみましょうinput
、とbutton
部品の交換Input
とButton
それはラップハンドラがイベントをフックが必要になります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);
}
}
何をすべきか?難しい場合は、クラスのコンポーネントに戻りますか?いいえ、フックがもたらす可能性が大好きです。
コードを乱雑にするハンドラーを、依存関係とともにクラスオブジェクトに移動することを提案します。それは良くないですか?
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);
}
};
}
コンポーネントコードもシンプルでエレガントです。イベントハンドラーは、依存関係とともに、クラス内で平和的に集まります。
フックuseMembers
と基本クラスは簡単です。
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;
}