<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          React,優(yōu)雅的捕獲異常進(jìn)階篇, 含Hooks方案

          共 28780字,需瀏覽 58分鐘

           ·

          2021-08-06 11:36

          點(diǎn)擊上方 全棧前端精選,關(guān)注公眾號

          回復(fù)【1】,加入前端群


          在React項目中,因為事件處理程序總是需要寫 try/catch,不勝其煩。

          雖然可以丟給window.onerror或者 window.addEventListener("error")去處理,但是對錯誤細(xì)節(jié)的捕獲以及錯誤的補(bǔ)償是極其不友好的。

          于是基于ES標(biāo)準(zhǔn)的裝飾器,出了一個事件處理程序的捕獲方案,詳情參見前篇 React,優(yōu)雅的捕獲異常 。

          評論區(qū)有掘友吐槽,都啥年代,還寫Class?, Hooks 666啊。

          掘友說的對,我要跟上時代的步伐, 要支持Hooks, getter等等。

          補(bǔ)充一下

          最初僅僅是為了捕獲和處理事件程序的異常,實際上是可以用于任何Class的方法上的

          問題回顧

          React,優(yōu)雅的捕獲異常 方案存在的問題:

          1. 抽象不夠
            獲取選項, 錯誤處理函數(shù)完全可以分離,變成通用方法。
          2. 同步方法經(jīng)過轉(zhuǎn)換后會變?yōu)楫惒椒椒ā?br>所以理論上,要區(qū)分同步和異步方案。
          3. 錯誤處理函數(shù)再異常怎么辦
          4. 功能局限

          我們來一一解決。

          一覽風(fēng)姿

          我們捕獲的范圍:

          1. Class的靜態(tài)同步方法
          2. Class的靜態(tài)異步方法
          3. Class的同步方法
          4. Class的異步方法
          5. Class的同步屬性賦值方法
          6. Class的異步屬性賦值方法
          7. Class的getter方法
          8. Hooks方法

          getter這里是不是很類似 vue的 計算值,所以以后別說我大React沒有計算屬性,哈哈。

          來來來,一覽其風(fēng)采:

          先看看Class組件的


          interface State {
              price: number;
              count: number;
          }

          export default class ClassT extends BaseComponent<{}, State> {
              constructor(props) {
                  super(props);
                  this.state = {
                      price: 100,
                      count: 1
                  }
                  this.onIncrease = this.onIncrease.bind(this);
                  this.onDecrease = this.onDecrease.bind(this);
              }

              componentDidMount() {
                  ClassT.printSomething();
                  ClassT.asyncPrintSomething();

                  this.doSomethings();
                  this.asyncDoSomethings();
              }

              @catchMethod({ message: "printSomething error", toast: true })
              static printSomething() {
                  throw new CatchError("printSomething error: 主動拋出");
                  console.log("printSomething:"Date.now());
              }

              @catchMethod({ message: "asyncPrintSomething error", toast: true })
              static async asyncPrintSomething() {
                  const { run } = delay(1000);
                  await run();
                  throw new CatchError("asyncPrintSomething error: 主動拋出");
                  console.log("asyncPrintSomething:"Date.now());
              }

              @catchGetter({ message: "計算價格失敗", toast: true })
              get totalPrice() {
                  const { price, count } = this.state;
                  // throw new Error("A");
                  return price * count;
              }

              @catchMethod("增加數(shù)量失敗")
              async onIncrease() {

                  const { run } = delay(1000);
                  await run();

                  this.setState({
                      count: this.state.count + 1
                  })
              }

              @catchMethod("減少數(shù)量失敗")
              onDecrease() {
                  this.setState({
                      count: this.state.count - 1
                  })
              }

              @catchInitializer({ message: "catchInitializer error", toast: true })
              doSomethings = () => {
                  console.log("do some things");
                  throw new CatchError("catchInitializer error: 主動拋出");
              }

              @catchInitializer({ message: "catchInitializer async error", toast: true })
              asyncDoSomethings = async () => {
                  const { run } = delay(1000);
                  await run();
                  throw new CatchError("catchInitializer async error: 主動拋出");
              }

              render() {
                  const { onIncrease, onDecrease } = this;
                  const totalPrice = this.totalPrice;

                  return <div style={{
                      padding: "150px",
                      lineHeight: "30px",
                      fontSize: "20px"
                  }}>
                      <div>價格:{this.state.price}</div>
                      <div>數(shù)量:1</
          div>
                      <div>
                          <button onClick={onIncrease}>增加數(shù)量</button>
                          <button onClick={onDecrease}>減少數(shù)量</
          button>
                      </div>
                      <div>{totalPrice}</
          div>
                  </div>
              }

          }
          復(fù)制代碼

          再看看函數(shù)式組件,就是大家關(guān)注的Hooks,包裝出useCatch,底層是基于useMemo

          const HooksTestView: React.FC<Props> = function (props{

              const [count, setCount] = useState(0);

              
              const doSomething  = useCatch(async function(){
                  console.log("doSomething: begin");
                  throw new CatchError("doSomething error")
                  console.log("doSomething: end");
              }, [], {
                  toast: true
              })

              const onClick = useCatch(async (ev) => {
                  console.log(ev.target);
                  setCount(count + 1);

                  doSomething();


                  const d = delay(3000() => {
                      setCount(count => count + 1);
                      console.log()
                  });
                  console.log("delay begin:"Date.now())
                  await d.run();
                  console.log("delay end:"Date.now())
                  console.log("TestView"this);
                  (d as any).xxx.xxx.x.x.x.x.x.x.x.x.x.x.x
                  // throw new CatchError("自定義的異常,你知道不")
              },
                  [count],
                  {
                      message: "I am so sorry",
                      toast: true
                  });

              return <div>
                  <div><button onClick={onClick}>點(diǎn)我</button></div>
                  <div>{count}</div>
              </
          div>
          }

          export default React.memo(HooksTestView);
          復(fù)制代碼

          我們一覽風(fēng)采之后,先看看我們做了哪些優(yōu)化,為什么要說優(yōu)化呢。因為優(yōu)化之前的代碼之后,代碼的可讀性,復(fù)用性,可擴(kuò)展性大幅增強(qiáng)。

          優(yōu)化

          封裝getOptions方法

          // options類型白名單
          const W_TYPES = ["string""object"];

          export function getOptions(options: string | CatchOptions{
              const type = typeof options;
              let opt: CatchOptions;
              
              if (options == null || !W_TYPES.includes(type)) { // null 或者 不是字符串或者對象
                  opt = DEFAULT_ERRPR_CATCH_OPTIONS;
              } else if (typeof options === "string") {  // 字符串
                  opt = {
                      ...DEFAULT_ERRPR_CATCH_OPTIONS,
                      message: options || DEFAULT_ERRPR_CATCH_OPTIONS.message,
                  }
              } else { // 有效的對象
                  opt = { ...DEFAULT_ERRPR_CATCH_OPTIONS, ...options }
              }

              return opt;
          }
          復(fù)制代碼

          定義默認(rèn)處理函數(shù)

          /**
           * 
           * @param err 默認(rèn)的錯誤處理函數(shù)
           * @param options 
           */

          function defaultErrorHanlder(err: any, options: CatchOptions{
              const message = err.message || options.message;
              console.error("defaultErrorHanlder:", message, err);
          }

          復(fù)制代碼

          區(qū)分同步方法和異步方法

          export function observerHandler(fn: AnyFunction, context: any, callback: ErrorHandler{
              return async function (...args: any[]{
                  try {
                      const r = await fn.call(context || this, ...args);
                      return r;
                  } catch (err) {
                      callback(err);
                  }
              };
          }

          export function observerSyncHandler(fn: AnyFunction, context: any, callback: ErrorHandler{
              return function (...args: any[]{
                  try {
                      const r = fn.call(context || this, ...args);
                      return r;
                  } catch (err) {
                      callback(err);
                  }
              };
          }
          復(fù)制代碼

          具備多級選項定義能力

          export default function createErrorCatch(handler: ErrorHandlerWithOptions, 
          baseOptions: CatchOptions = DEFAULT_ERRPR_CATCH_OPTIONS
          {

              return {
                  catchMethod(options: CatchOptions | string = DEFAULT_ERRPR_CATCH_OPTIONS) {
                      return catchMethod({ ...baseOptions, ...getOptions(options) }, handler)
                  }   
              }
          }
          復(fù)制代碼

          自定義錯誤處理函數(shù)

          export function commonErrorHandler(error: any, options: CatchOptions{    
              try{
                  let message: string;
                  if (error.__type__ == "__CATCH_ERROR__") {
                      error = error as CatchError;
                      const mOpt = { ...options, ...(error.options || {}) };

                      message = error.message || mOpt.message ;
                      if (mOpt.log) {
                          console.error("asyncMethodCatch:", message , error);
                      }

                      if (mOpt.report) {
                          // TODO::
                      }

                      if (mOpt.toast) {
                          Toast.error(message);
                      }

                  } else {

                      message = options.message ||  error.message;
                      console.error("asyncMethodCatch:", message, error);

                      if (options.toast) {
                          Toast.error(message);
                      }
                  }
              }catch(err){
                  console.error("commonErrorHandler error:", err);
              }
          }


          const errorCatchInstance = createErrorCatch(commonErrorHandler);

          export const catchMethod = errorCatchInstance.catchMethod; 
          復(fù)制代碼

          增強(qiáng)

          支持getter

          先看一下catchGetter的使用

          class Test {

              constructor(props) {
                  super(props);
                  this.state = {
                      price: 100,
                      count: 1
                  }

                  this.onClick = this.onClick.bind(this);
              }

              @catchGetter({ message: "計算價格失敗", toast: true })
              get totalPrice() {
                  const { price, count } = this.state;
                  throw new Error("A");
                  return price * count;
              }
              
                render() {   
                  const totalPrice = this.totalPrice;

                  return <div>
                      <div>價格:{this.state.price}</div>
                      <div>數(shù)量:1</
          div>
                      <div>{totalPrice}</div>
                  </
          div>
              }
              
          }
          復(fù)制代碼

          實現(xiàn)

          /**
           * class {  get method(){} }
           * @param options 
           * @param hanlder 
           * @returns 
           */

          export function catchGetter(options: string | CatchOptions = DEFAULT_ERRPR_CATCH_OPTIONS, 
          hanlder: ErrorHandlerWithOptions = defaultErrorHanlder
          {

              let opt: CatchOptions = getOptions(options);

              return function (_target: any, _name: string, descriptor: PropertyDescriptor{
                  const { constructor } = _target;
                  const { get: oldFn } = descriptor;

                  defineProperty(descriptor, "get", {
                      value: function () {
                          // Class.prototype.key lookup
                          // Someone accesses the property directly on the prototype on which it is
                          // actually defined on, i.e. Class.prototype.hasOwnProperty(key)
                          if (this === _target) {
                              return oldFn();
                          }
                          // Class.prototype.key lookup
                          // Someone accesses the property directly on a prototype but it was found
                          // up the chain, not defined directly on it
                          // i.e. Class.prototype.hasOwnProperty(key) == false && key in Class.prototype
                          if (
                              this.constructor !== constructor &&
                              getPrototypeOf(this).constructor === constructor
                          ) {
                              return oldFn();
                          }
                          const boundFn = observerSyncHandler(oldFn, thisfunction (error: Error) {
                              hanlder(error, opt)
                          }
          );
                          (boundFn as any)._bound = true;
                      
                          return boundFn();
                      }
                  });

                  return descriptor;
              }

          }
          復(fù)制代碼

          支持屬性定義和賦值

          這個需要babel的支持,詳情可以參見babel-plugin-proposal-class-properties

          demo可以參見class-error-catch

          class Test{
              @catchInitializer("nono")
              doSomethings = ()=> {
                  console.log("do some things");
              }
          }
          復(fù)制代碼

          實現(xiàn)

          export function catchInitializer(options: string | CatchOptions = DEFAULT_ERRPR_CATCH_OPTIONS, hanlder: ErrorHandlerWithOptions = defaultErrorHanlder){

              const opt: CatchOptions = getOptions(options);

               return function (_target: any, _name: string, descriptor: any{

                  console.log("debug....");
                  const initValue = descriptor.initializer();
                  if (typeof initValue !== "function") {
                      return descriptor;
                  }

                  descriptor.initializer = function() {
                      initValue.bound = true;
                      return observerSyncHandler(initValue, thisfunction (error: Error{
                          hanlder(error, opt)
                      });
                  };
                  return descriptor;
              }
          }
          復(fù)制代碼

          支持Hooks

          使用


          const TestView: React.FC<Props> = function (props{

              const [count, setCount] = useState(0);

              
              const doSomething  = useCatch(async function(){
                  console.log("doSomething: begin");
                  throw new CatchError("doSomething error")
                  console.log("doSomething: end");
              }, [], {
                  toast: true
              })

              const onClick = useCatch(async (ev) => {
                  console.log(ev.target);
                  setCount(count + 1);

                  doSomething();

                  const d = delay(3000() => {
                      setCount(count => count + 1);
                      console.log()
                  });
                  console.log("delay begin:"Date.now())

                  await d.run();
                  
                  console.log("delay end:"Date.now())
                  console.log("TestView"this)
                  throw new CatchError("自定義的異常,你知道不")
              },
                  [count],
                  {
                      message: "I am so sorry",
                      toast: true
                  });

              return <div>
                  <div><button onClick={onClick}>點(diǎn)我</button></div>
                  <div>{count}</div>
              </
          div>
          }

          export default React.memo(TestView);

          復(fù)制代碼

          實現(xiàn): 其基本原理就是利用 useMemo和之前封裝的observerHandler,寥寥幾行代碼就實現(xiàn)了。

          export function useCatch<T extends (...args: any[]) => any>(callback: T, deps: DependencyList, options: CatchOptions =DEFAULT_ERRPR_CATCH_OPTIONS): T {    

              const opt =  useMemo( ()=> getOptions(options), [options]);
              
              const fn = useMemo((..._args: any[]) => {
                  const proxy = observerHandler(callback, undefinedfunction (error: Error{
                      commonErrorHandler(error, opt)
                  });
                  return proxy;

              }, [callback, deps, opt]) as T;

              return fn;
          }
          復(fù)制代碼

          這里你可能會問啥,你只是實現(xiàn)了方法的異常捕獲啊,我的useEffect, useCallbak, useLayout等等,你就不管呢?

          其實到這里,基本兩條思路

          1. 基于useCatch分離定義的方法
          2. 針對每一個Hook再寫一個useXXX

          到這里,我想,已經(jīng)難不倒各位了。

          我這里只是提供了一種思路,一種看起來不復(fù)雜,可行的思路。

          關(guān)于源碼

          因為目前代碼是直接跑在我們的實際項目上的,還沒時間去獨(dú)立的把代碼分離到一個獨(dú)立的項目。想要全部源碼的同學(xué)可以聯(lián)系我。

          之后會把全部源碼,示例獨(dú)立出來。

          后續(xù)

          我想肯定有人會問,你用Object.defineProperty,out了,你看vue都用Proxy來實現(xiàn)了。

          是的,Proxy固然強(qiáng)大,但是要具體情況具體對待,這里我想到有兩點(diǎn)Proxy還真不如 Object.defineProperty 和 裝飾器。

          1.兼容性
          2.靈活度

          后續(xù):

          1. 支持直接捕獲整個Class
          2. 通過實用修復(fù)相關(guān)的問題
          3. 獨(dú)立代碼和示例,封裝為庫
          4. 嘗試使用Proxy實現(xiàn)

          具備類似功能的庫

          • catch-decorator

          僅僅捕獲方法,處理比較初級

          • catch-decorator-ts

          同上

          • catch-error-decorator

          通過 AsyncFunction判斷,提供失敗后的默認(rèn)返回值。

          • auto-inject-async-catch-loader

          主要捕獲異步方法,原理是webpack loader, 遍歷AST. 其他async-catch-loader,babel-plugin-promise-catcher等原理類似。

          寫在最后

          寫作不易,如果覺得還不錯, 一贊一評,就是我最大的動力。

          babel-plugin-proposal-class-properties)
          setpublicclassfields

          關(guān)于本文

          來源:云的世界

          https://juejin.cn/post/6976414994107727909

          瀏覽 31
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  免费一级无码婬片A片久久老年性 | 亚洲精品免费AV | 一级A片播放 | 东京热国产传媒 | 亚洲一区欧美日韩国产 云播 |