ReactフックとTypeScriptの操作
公開: 2022-03-11コードの可読性を向上させるために、2019年2月にフックがReactに導入されました。 以前の記事でReactフックについてはすでに説明しましたが、今回はフックがTypeScriptでどのように機能するかを調べています。
フックの前は、Reactコンポーネントには2つのフレーバーがありました。
- 状態を処理するクラス
- 小道具によって完全に定義されている関数
これらの自然な使用法は、クラスを備えた複雑なコンテナコンポーネントと、純粋関数を備えた単純なプレゼンテーションコンポーネントを構築することでした。
Reactフックとは何ですか?
コンテナコンポーネントは、状態管理とサーバーへのリクエストを処理します。これらは、この記事の副作用で呼び出されます。 状態は、小道具を介してコンテナの子に伝播されます。
しかし、コードが大きくなるにつれて、機能コンポーネントはコンテナコンポーネントとして変換される傾向があります。
機能コンポーネントをよりスマートなコンポーネントにアップグレードすることはそれほど苦痛ではありませんが、時間のかかる不快な作業です。 さらに、プレゼンターとコンテナーを厳密に区別することは、もはや歓迎されません。
フックは両方を実行できるため、結果のコードはより均一になり、ほとんどすべての利点があります。 これは、引用署名を処理している小さなコンポーネントにローカル状態を追加する例です。
// put signature in local state and toggle signature when signed changes function QuotationSignature({quotation}) { const [signed, setSigned] = useState(quotation.signed); useEffect(() => { fetchPost(`quotation/${quotation.number}/sign`) }, [signed]); // effect will be fired when signed changes return <> <input type="checkbox" checked={signed} onChange={() => {setSigned(!signed)}}/> Signature </> }
これには大きなボーナスがあります。TypeScriptを使用したコーディングはAngularでは優れていましたが、Reactでは肥大化しました。 ただし、 TypeScriptを使用してReactフックをコーディングするのは楽しい経験です。
OldReactを使用したTypeScript
TypeScriptはMicrosoftによって設計され、ReactがFlowを開発したときにAngularの道をたどりましたが、現在は勢いを失っています。 多くのキーが同じであっても、React開発者はprops
とstate
両方を入力する必要があったため、単純なTypeScriptを使用してReactクラスを作成するのは非常に面倒でした。
これが単純なドメインオブジェクトです。 Quotation
タイプの見積もりアプリを作成し、状態と小道具を使用していくつかのクラッドコンポーネントで管理します。 Quotation
を作成し、それに関連するステータスを署名済みまたは未署名に変更できます。
interface Quotation{ id: number title:string; lines:QuotationLine[] price: number } interface QuotationState{ readonly quotation:Quotation; signed: boolean } interface QuotationProps{ quotation:Quotation; } class QuotationPage extends Component<QuotationProps, QuotationState> { // ... }
しかし、QuotationPageがサーバーにIDを要求することを想像してみてください。たとえば、会社が作成した678番目の見積もりです。 つまり、QuotationPropsはその重要な数値を認識していません。つまり、Quotationを正確にラップしているわけではありません。 QuotationPropsインターフェースでさらに多くのコードを宣言する必要があります。
interface QuotationProps{ // ... all the attributes of Quotation but id title:string; lines:QuotationLine[] price: number }
ID以外のすべての属性を新しいタイプでコピーします。 うーん。 それは私にたくさんのDTOを書くことを含んだ古いJavaを思い起こさせます。 それを克服するために、TypeScriptの知識を増やして苦痛を回避します。
フック付きTypeScriptの利点
フックを使用することで、以前のQuotationStateインターフェースを取り除くことができます。 これを行うには、QuotationStateを状態の2つの異なる部分に分割します。
interface QuotationProps{ quotation:Quotation; } function QuotationPage({quotation}:QuotationProps){ const [quotation, setQuotation] = useState(quotation); const [signed, setSigned] = useState(false); }
状態を分割することで、新しいインターフェースを作成する必要がなくなります。 ローカル状態タイプは、多くの場合、デフォルトの状態値によって推測されます。
フック付きのコンポーネントはすべて関数です。 したがって、Reactライブラリで定義されたFC<P>
タイプを返す同じコンポーネントを作成できます。 この関数は、propsタイプに沿って設定し、returnタイプを明示的に宣言します。
const QuotationPage : FC<QuotationProps> = ({quotation}) => { const [quotation, setQuotation] = useState(quotation); const [signed, setSigned] = useState(false); }
明らかに、ReactフックでTypeScriptを使用する方が、Reactクラスで使用するよりも簡単です。 また、強い型付けはコードの安全性にとって貴重なセキュリティであるため、新しいプロジェクトでフックを使用する場合は、TypeScriptの使用を検討する必要があります。 TypeScriptが必要な場合は、必ずフックを使用する必要があります。
Reactを使用するかどうかにかかわらず、TypeScriptを回避できる理由はたくさんあります。 しかし、それを使用することを選択した場合は、必ずフックも使用する必要があります。
フックに適したTypeScriptの特定の機能
前のReactフックTypeScriptの例では、QuotationPropsにnumber属性がまだありますが、そのnumberが実際に何であるかはまだわかりません。
TypeScriptはユーティリティタイプの長いリストを提供し、そのうちの3つは、多くのインターフェイス記述のノイズを減らすことでReactを支援します。
-
Partial<T>
:Tの任意のサブキー Omit<T, 'x'>
:キーx
を除くTのすべてのキーPick<T, 'x', 'y', 'z'>
:T
からの正確x, y, z
キー
この場合、 Omit<Quotation, 'id'>
で引用のidを省略します。 type
キーワードを使用して、その場で新しいタイプを作成できます。
Partial<T>
およびOmit<T>
は、Javaなどのほとんどの型付き言語には存在しませんが、フロントエンド開発でのフォームの例に大いに役立ちます。 タイピングの負担を軽減します。
type QuotationProps= Omit<Quotation, id>; function QuotationPage({quotation}:QuotationProps){ const [quotation, setQuotation] = useState(quotation); const [signed, setSigned] = useState(false); // ... }
これで、IDのない見積もりができました。 したがって、 Quotation
を設計し、 PersistedQuotation
がQuotation
をextends
する可能性があります。 また、再発if
またはundefined
の問題を簡単に解決します。 完全なオブジェクトではありませんが、それでも変数引用符を呼び出す必要がありますか? これはこの記事の範囲を超えていますが、とにかく後で説明します。

ただし、これで、 number
が付いていると思ったオブジェクトを広めることはないと確信しています。 Partial<T>
を使用しても、これらすべての保証が得られるわけではないため、慎重に使用してください。
Pick<T, 'x'|'y'>
は、新しいインターフェイスを宣言する必要なしに、その場で型を宣言するもう1つの方法です。 コンポーネントの場合は、見積もりのタイトルを編集するだけです。
type QuoteEditFormProps= Pick<Quotation, 'id'|'title'>
あるいは単に:
function QuotationNameEditor({id, title}:Pick<Quotation, 'id'|'title'>){ ...}
私を判断しないでください、私はドメイン駆動設計の大ファンです。 私は、新しいインターフェイスのためにさらに2行を書きたくないという点で怠惰ではありません。 私はドメイン名を正確に記述するためにインターフェースを使用し、これらのユーティリティ関数はローカルコードを正確にし、ノイズを回避します。 読者は、 Quotation
が標準的なインターフェースであることを知っているでしょう。
Reactフックの他の利点
Reactチームは常に、Reactを機能的なフレームワークと見なして扱いました。 彼らは、コンポーネントがそれ自体の状態を処理できるようにクラスを使用し、関数がコンポーネントの状態を追跡できるようにする手法としてフックするようになりました。
interface Place{ city:string, country:string } const initialState:Place = { city: 'Rosebud', country: 'USA' }; function reducer(state:Place, action):Partial<Place> { switch (action.type) { case 'city': return { city: action.payload }; case 'country': return { country: action.payload }; } } function PlaceForm() { const [state, dispatch] = useReducer(reducer, initialState); return ( <form> <input type="text" name="city" onChange={(event) => { dispatch({ type: 'city',payload: event.target.value}) }} value={state.city} /> <input type="text" name="country" onChange={(event) => { dispatch({type: 'country', payload: event.target.value }) }} value={state.country} /> </form> ); }
これは、 Partial
を使用することが安全で適切な場合です。
関数は何度も実行できますが、関連するuseReducer
フックは1回だけ作成されます。
コンポーネントからレデューサー関数を自然に抽出することにより、コードをクラス内の複数の関数ではなく、複数の独立した関数に分割できます。これらはすべて、クラス内の状態にリンクされています。
これは、テスト容易性にとって明らかに優れています。一部の関数はJSXを処理し、他の関数は動作を処理し、他の関数はビジネスロジックを処理します。
あなたは(ほとんど)高階コンポーネントをもう必要としません。 Render propsパターンは、関数を使用して作成する方が簡単です。
したがって、コードを読む方が簡単です。 コードはクラス/関数/パターンのフローではなく、関数のフローです。 ただし、関数はオブジェクトにアタッチされていないため、これらすべての関数に名前を付けるのは難しい場合があります。
TypeScriptはまだJavaScriptです
JavaScriptは、コードを任意の方向に引き裂くことができるので楽しいです。 TypeScriptを使用すると、 keyof
を使用してオブジェクトのキーを操作できます。 型共用体を使用して、読み取り不可能で保守不可能なものを作成できます。いいえ、私はそれらが好きではありません。 タイプエイリアスを使用して、文字列がUUIDであるかのように見せかけることができます。
しかし、あなたはヌルの安全でそれを行うことができます。 tsconfig.json
に"strict":true
オプションがあることを確認してください。 プロジェクトの開始前に確認してください。そうしないと、ほぼすべての行をリファクタリングする必要があります。
コードに入力するタイピングのレベルについては議論があります。 すべてを入力することも、コンパイラに型を推測させることもできます。 リンターの構成とチームの選択によって異なります。
また、実行時エラーが発生する可能性もあります。 TypeScriptはJavaよりも単純であり、ジェネリックスとの共変性/反変性の問題を回避します。
この動物/猫の例では、猫リストと同じ動物リストがあります。 残念ながら、これは最初の行の契約であり、2番目の行ではありません。 次に、アヒルを動物リストに追加するので、猫のリストは偽です。
interface Animal {} interface Cat extends Animal { meow: () => string; } const duck = {age: 7}; const felix = { age: 12, meow: () => "Meow" }; const listOfAnimals: Animal[] = [duck]; const listOfCats: Cat[] = [felix]; function MyApp() { const [cats , setCats] = useState<Cat[]>(listOfCats); // Here the thing: listOfCats is declared as a Animal[] const [animals , setAnimals] = useState<Animal[]>(listOfCats) const [animal , setAnimal] = useState(duck) return <div onClick={()=>{ animals.unshift(animal) // we set as first cat a duck ! setAnimals([...animals]) // dirty forceUpdate } }> The first cat says {cats[0].meow()}</div>; }
TypeScriptには、ジェネリックスに対して単純でJavaScript開発者の採用を支援する2変量アプローチしかありません。 変数に正しく名前を付ければ、 listOfCats
にduck
を追加することはめったにありません。
また、共変性と反変性の契約を追加および追加する提案があります。
結論
私は最近、優れた型付き言語であるKotlinに戻りましたが、複雑なジェネリックを正しく入力するのは複雑でした。 ダックタイピングと二変量アプローチの単純さのために、TypeScriptでその一般的な複雑さはありませんが、他の問題があります。 おそらく、TypeScriptを使用して設計されたAngularで多くの問題が発生することはありませんでしたが、Reactクラスで問題が発生しました。
TypeScriptはおそらく2019年の大勝者でした。Reactを獲得しましたが、Node.jsと非常に単純な宣言ファイルで古いライブラリを入力する機能でバックエンドの世界を征服しています。 フローを埋めていますが、ReasonMLを使用するものもあります。
今、私はhooks+TypeScriptがAngularよりも快適で生産的だと感じています。 半年前は思っていなかったでしょう。