装飾をフックに反応する


デコレータは、タイプスクリプトと面白いデザインパターンの素晴らしい機能です.あまりに悪い、typescriptで最もクールな装飾者はむしろクラス指向であるので、あなたはそれがより多くの機能スタイル方法と反応して何かを飾ることを望むならば、どうしますか?
答えは高次関数です.このチュートリアルでは、より高い順序関数を使用して反応フックをどのように修飾することができ、いくつかのタイプスクリプトのマジックで変更された戻り値を持つことができます.
何がデコレータの可能なユースケースかもしれない?ログ、キャッシュ、あなたのタイプスクリプトカンフーなどを披露.
このチュートリアルの目的のために、私たちは、私たちには、オリジナルの名前で役に立たないフックがあると仮定しましょうuseStuff .
// ./hooks/use-stuff.hook.ts
import { useCallback, useState } from "react";

export const useStuff = (startValue: number) => {
  const [counter, setCount] = useState(startValue);

  const getStuffSync = useCallback((s: string) => "got some stuff sync", []);
  const getStuffAsync = useCallback(
    async (s: string, n: number) => Promise.resolve("got some stuff sync"),
    []
  );
  const failtToGetSomeStuffSync: () => string = useCallback(() => {
    throw new Error("no you dont");
  }, []);

  const failtToGetSomeStuffAsync: () => Promise<string> = useCallback(
    () => Promise.reject("no async for you"),
    []
  );

  return {
    getStuffSync,
    getStuffAsync,
    failtToGetSomeStuffSync,
    failtToGetSomeStuffAsync,
    setCount,
    counter,
  };
};
それで、理由のないカウンタを持っています、同期機能のカップルと非同期のカップルとそれらのいくつかは、常に失敗する運命です.現実世界では、それらは潜在的に失敗する可能性があるAPI要求、または投げることができた計算で使用される若干の方法でありえました.
すべてのエラーに対処するのに飽きてしまったと想像し、エラーが発生した場合は、すべてをキャッチし、NULLを返すだけの良い考えであると判断しました.ではエラーとは何ですか?簡単にするためにユーザコンソールにダンプしましょう.
しかし、ここで4つの方法があり、それぞれを包んで追加するtry/catch ブロックは、それらのすべての退屈と繰り返しに見える.また、それぞれのメソッドの戻り値の型を変更するにはnull エラーの場合.したがって、4つの場所の戻り値のタイプを変更する.また、このフックが単位テストでよくカバーされており、戻り値の型の変更もテストファイルを変更する必要があります.良い音はありません.
しかし、我々は我々が必要とするすべての新しい機能を加えるために、この非常にフックを飾ることができますtry/catch それぞれのメソッドと変更メソッドに対して、型をNULLにします.
まず第一に、我々が必要とするインターフェースについて考えましょう.
最も基本的なものは任意の関数に適合するインターフェイスです.フックやフックメソッドが拡張します.
// ./models/function-with-arguments.model.ts
export interface FunctionWithArguments {
  (...args: any): any;
}
それから私たちはOptional 私たちが変更しようとするフックメソッドが返すことができるので、ジェネリックnull エラーが発生した場合:
// ./models/optional.model.ts
export type Optional<T> = T | null;
これらの2つの基本的な型に基づいて、戻り関数、同期または非同期を取ることができる型を作成することができます.
// ./models/function-with-optional-return.model.ts
import { FunctionWithArguments } from "./function-with-arguments.model";
import { Optional } from "./optional.model";

export type FunctionWithOptionalReturn<F extends FunctionWithArguments> = (
  ...args: Parameters<F>
) => ReturnType<F> extends Promise<infer P>
  ? Promise<Optional<P>>
  : Optional<ReturnType<F>>;
関数を変更するための汎用性を持っているので、フックの戻り値を処理するためにジェネリックを作成することができます.
// ./models/hook-methods-optionazed-returns.model.ts
import { FunctionWithArguments } from "./function-with-arguments.model";
import { FunctionWithOptionalReturn } from "./function-with-optional-return.model";

export type HookMethodsOptionalizedReturns<T extends FunctionWithArguments> = {
  [k in keyof ReturnType<T>]: ReturnType<T>[k] extends FunctionWithArguments
    ? FunctionWithOptionalReturn<ReturnType<T>[k]>
    : ReturnType<T>[k];
};
すべての必要なモデルは準備ができており、我々は我々のデコレータを作成することができます.これは引数としてフックを受け入れ、渡されたフックの変更されたバージョンを生成しますtry/catch ブロック可能null エラー時の返り値として:
// ./hooks/use-error-devourer.hook.ts
import { FunctionWithArguments } from "../models/function-with-arguments.model";
import { HookMethodsOptionalizedReturns } from "../models/hook-methods-optionazed-returns.model";

export const devourErrorsDecorator = <F extends FunctionWithArguments>(
  fn: F
) => {
  return (...args: Parameters<F>): HookMethodsOptionalizedReturns<F> => {
    const { ...result } = fn(...args);
    Object.entries<FunctionWithArguments>(result)
      // we've assumed only functions for typing purposes, so filter to safeguard
      .filter(([k, v]) => typeof v === "function")
      .forEach(([k, fn]) => {
        result[k] =
          fn.constructor.name === "AsyncFunction"
            ? async (...args: Parameters<typeof fn>) => {
                console.log("AsyncFunction called with ", ...args);
                try {
                  return await fn(...args);
                } catch (e) {
                  console.log("ASYNC failed");
                  return null;
                }
              }
            : (...args: Parameters<typeof fn>) => {
                console.log("Sync function called with ", ...args);
                try {
                  return fn(...args);
                } catch (e) {
                  console.log("SYNC failed");
                  return null;
                }
              };
      });
    return result;
  };
};
ご覧の通り、元のフックと収益を呼び出してメソッドを変更します.
今、我々は新しいバージョンを生成することができますuseStuff フック、修正をキャッチするエラーで強化されます.
// ./hooks/no-error-use-stuff.hook.ts
import { devourErrorsDecorator } from "./use-error-devourer.hook";
import { useStuff as errorProneUseStuff } from "./use-stuff.hook";

export const useStuff = devourErrorsDecorator(errorProneUseStuff);
かなり涼しいですね.我々は、フックの装飾バージョンを作成し、すべてのメソッドを変更し、返される値を維持し、強くすべてを入力します.
コードとのrepoを見つけることができますhere .