برنامه نویسی

Fronted for Web3 Dapp: روشهای خوب

1. اتحادیه های تبعیض آمیز ایمنی نوع

با استفاده از اتحادیه های تبعیض آمیز در TypeScript kind یک رویکرد ایمن از نوع برای الگوبرداری از steets ، رویدادها و دستورات مختلف ایجاد می کند:

// State modeling with discriminated unions
type TxNormalStateIdle = { kind: 'idle' };
type TxNormalStateSending = { kind: 'sending' };
type TxNormalStateSuccess = { kind: 'success'; txHash: Hash };
type TxNormalStateError = { kind: 'error'; error: BaseError };

export type TxNormalState =
  | TxNormalStateIdle
  | TxNormalStateSending
  | TxNormalStateSuccess
  | TxNormalStateError;
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

2. الگوی فرمان-ایالتی

جداسازی اقدامات کاربر (دستورات) ، رویدادهای سیستم (رویدادها) و وضعیت برنامه (STAT) به شما امکان می دهد تا یک جریان داده یک طرفه واضح و روشن ایجاد کنید:

// Commands - user actions
type Submit = {
  kind: 'submit';
  data: { tx: () => WriteContractParameters };
};

// Events - system responses
type SuccessEvent = {
  kind: 'success';
  data: { hash: Hash };
};

// State - application state
type TxAllowanceStateApproveNeeded = {
  kind: 'approve-needed';
  amount: bigint;
};
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

3. دستگاه های حالت محدود

عملکرد مدل سازی از طریق دستگاه های حالت محدود (FSM) باعث می شود که دولتها از انتقال حالتها آشکار شده و از انتقال غیرقانونی آمار جلوگیری می کنند:

reducer: (state, event) => {
  switch (event.kind) {
    case 'check':
      return {
        ...state,
        kind: 'checking-allowance',
        amount: event.data.amount,
      };
    case 'enough-allowance':
      switch (state.kind) {
        case 'rechecking-allowance':
          return { ...state, kind: 'sending' };
        default:
          return {
            ...state,
            kind: 'has-allowance',
            amount: event.data.amount,
          };
      }
    // ...
  }
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

4. برنامه نویسی واکنشی с rxjs

استفاده از مشاهدات برای عملیات ناهمزمان بهترین ترکیب و امکان سانسی جریان را در صورت لزوم فراهم می کند (متأسفانه ، در زیر جعبه ، تمام API های Web3 هنوز در آنجا پیچیده اند):

return from(
  this.client.simulateContract(<SimulateContractParameters>cmd.data.tx())
).pipe(
  switchMap(response =>
    from(this.client.writeContract(response.request)).pipe(
      switchMap(txHash => {
        return from(
          this.client.waitForTransactionReceipt({
            hash: txHash,
            confirmations: 1,
          })
        ).pipe(
          map(() =>
            txAllowanceEvent({
              kind: 'success',
              data: { hash: txHash },
            })
          )
        );
      }),
      catchError(err => of(txAllowanceEvent({ kind: 'error', data: err })))
    )
  ),
  startWith(txAllowanceEvent({ kind: 'submitted' })),
  take(2)
);
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

5. خطا به عنوان داده ، نه استثنا

تمام خطاها به صورت داده های معمولی ارائه می شود ، نه exepshenes. این به شما امکان می دهد تا کد را تا حد ممکن واضح و قابل پیش بینی کنید ، زیرا آنها به لایه ای از منطق تجارت نشت نمی کنند:

// Error is just another type of state
type TxAllowanceStateError = {
  kind: 'error';
  amount: bigint;
  error: BaseError;
};

// Error is handled through the normal event flow
catchError(err => 
  of(txAllowanceEvent({ 
    kind: 'error', 
    data: err.cause ?? err 
  }))
)

// Inside business logic there is no exception handling, only data handling
tap(result => {
  if (result.kind === 'success') {
    // do something
  } else {
    // do something else, show alert for example
  }
})
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

6. معماری مبتنی بر افزونه

جداسازی عملکرد پیچیده به افزونه های ترکیبی و خودمختار:

@Injectable()
export class TxAllowanceStore extends FeatureStore<
  TxAllowanceCommand,
  TxAllowanceEvent,
  TxAllowanceState,
  TxAllowanceStateIdle
> {
  // Implementation
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

7. کلاسهای پایه انتزاعی قراردادهای رابط

استفاده از کلاسهای اساسی اساسی برای تعیین قراردادهای روشن:

@Injectable()
export abstract class WalletBase {
  public abstract requestConnect(): void;
  public abstract getCurrentAddress(): Observable<Hash | null>;
  public abstract getBalance(): Observable<string>;
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

8. تایپ سازی قوی در برنامه

استفاده از ژنرال ها برای ارائه ایمنی نوع:

export class TxNormalStore extends FeatureStore<
  TxNormalCommand,
  TxNormalEvent,
  TxNormalState,
  TxNormalStateIdle
> {
  // Implementation
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

9. تعریف صریح از حالتهای اولیه

نقطه شروع منطق تجارت برای هر افزونه:

initialValue: {
  kind: 'idle',
  amount: 0n,
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

10. کاهش دهنده های خالص انتقال دولت

استفاده از توابع خالص برای به روزرسانی حالت برای حفظ پیش بینی:

reducer: (state, event) => {
  switch (event.kind) {
    case 'reset':
      return { kind: 'idle', amount: 0n };
    case 'success':
      return { ...state, kind: 'success', txHash: event.data.hash };
    // ...
  }
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

مثال 1: جریان تأیید توکن با FSM

این مثال جریان کامل برای تأیید توکن را نشان می دهد ، که یک الگوی مشترک در برنامه های DEFI است. اجرای ظریف پردازش های پیچیده حالت را پردازش می کند:

// From tx-with-allowance.ts
function checkAllowance(
  client: PublicClient & WalletClient,
  data: Check['data']
) {
  return from(
    client.readContract({
      address: data.token,
      abi: erc20Abi,
      functionName: 'allowance',
      args: [data.userAddress, data.spender],
    })
  ).pipe(
    map(actualAllowance => {
      const isEnoughAllowance = actualAllowance >= data.amount;
      if (isEnoughAllowance) {
        return txAllowanceEvent({
          kind: 'enough-allowance',
          data: {
            spender: data.spender,
            token: data.token,
            amount: data.amount,
          },
        });
      } else {
        return txAllowanceEvent({
          kind: 'not-enough-allowance',
          data: {
            spender: data.spender,
            token: data.token,
            amount: data.amount,
          },
        });
      }
    }),
    catchError(() =>
      of(
        txAllowanceEvent({
          kind: 'not-enough-allowance',
          data: {
            spender: data.spender,
            token: data.token,
            amount: data.amount,
          },
        })
      )
    ),
    take(1)
  );
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

مثال 2: خط لوله اجرای معامله

این مثال نشان می دهد پیپلان برای معاملات با پردازش خطای بعدی:

// From tx-normal.ts
submit: cmd => {
  const tx = <SimulateContractParameters>cmd.data.tx();

  console.log('tx: ', tx);

  return from(this.client.simulateContract(tx)).pipe(
    switchMap(response =>
      from(this.client.writeContract(response.request)).pipe(
        switchMap(txHash => {
          return from(
            this.client.waitForTransactionReceipt({
              hash: txHash,
              confirmations: 1,
            })
          ).pipe(
            map(() =>
              txNormalEvent({ kind: 'success', data: { hash: txHash } })
            )
          );
        }),
        catchError(err => {
          return of(txNormalEvent({ kind: 'error', data: err }));
        })
      )
    ),
    catchError(err => {
      return of(
        txNormalEvent({
          kind: 'error',
          data: err.cause ?? err,
        })
      );
    }),
    startWith(txNormalEvent({ kind: 'submitted' })),
    take(2)
  );
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

مثال 3: کنترل کننده تأیید توکن

این مثال نحوه پردازش فرآیند تأیید توکن را با شبیه سازی + اجرا نشان می دهد:

// From tx-with-allowance.ts
approve: (cmd: Approve) => {
  return defer(() =>
    from(
      this.client.simulateContract({
        account: cmd.data.userAddress,
        address: cmd.data.token,
        abi: erc20Abi,
        functionName: 'approve',
        args: [cmd.data.spender, MAX_UINT],
        gas: 65000n,
      })
    )
  ).pipe(
    switchMap(response =>
      from(this.client.writeContract(response.request)).pipe(
        switchMap(value => {
          return from(
            this.client.waitForTransactionReceipt({
              hash: value,
              confirmations: 1,
            })
          ).pipe(switchMap(() => checkAllowance(this.client, cmd.data)));
        }),
        catchError(err => {
          return of(
            txAllowanceEvent({
              kind: 'approve-fail',
              data: err.cause ?? err,
            })
          );
        })
      )
    ),
    catchError(err =>
      of(
        txAllowanceEvent({
          kind: 'approve-fail',
          data: err.cause ?? err,
        })
      )
    ),
    startWith(
      txAllowanceEvent({
        kind: 'approve-sent',
        data: {
          spender: cmd.data.spender,
          token: cmd.data.token,
          amount: cmd.data.amount,
        },
      })
    ),
    take(2)
  );
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

مثال 4: انتقال دولت

// From tx-allowance.ts
reducer: (state, event) => {
  console.log('event: ', event);
  switch (event.kind) {
    case 'reset':
      return { kind: 'idle', amount: 0n };
    case 'success':
      return { ...state, kind: 'success', txHash: event.data.hash };
    case 'error':
      return { ...state, error: event.data, kind: 'error' };
    case 'check':
      return {
        ...state,
        kind: 'checking-allowance',
        amount: event.data.amount,
      };
    case 'enough-allowance':
      switch (state.kind) {
        case 'rechecking-allowance':
          return { ...state, kind: 'sending' };
        default:
          return {
            ...state,
            kind: 'has-allowance',
            amount: event.data.amount,
          };
      }
    // Additional cases omitted for brevity
  }
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

در اصل ، من از تمام این رویکردها برای هر برنامه کاربردی استفاده می کنم ، اما در Web3 این امر به ویژه مرتبط است ، زیرا وقتی کیف پول کاربر از بسیاری از ایالت ها عبور می کند (چه در زمان اتصال و چه در زمان معامله) ، پس از آن بدون الگوی عادی تطبیق ، اگر دیگر به دست آمده ، از انبوهی از انباشت بپاشید. رویکرد فوق به شما امکان می دهد تا همه چیز را بسازید تا کامپایلر تمام حالتهای ممکن و انتقال بین آنها را بررسی کند و بار را روی توسعه دهنده کاهش دهد.

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا