es7你都懂了嗎?今天帶你瞭解es7的神器decorator

来源:https://www.cnblogs.com/zhuanzhuanfe/archive/2018/05/28/9101922.html
-Advertisement-
Play Games

es7帶來了很多更強大的方法,比如async/await,decorator等,相信大家對於async/await已經用的很熟練了,下麵我們來講一下decorator。 何為decorator? 官方說法,修飾器(Decorator)函數,用來修改類的行為。這樣講對於初學者來說不是很好理解,通俗點講 ...


es7帶來了很多更強大的方法,比如async/await,decorator等,相信大家對於async/await已經用的很熟練了,下麵我們來講一下decorator。

何為decorator?

官方說法,修飾器(Decorator)函數,用來修改類的行為。這樣講對於初學者來說不是很好理解,通俗點講就是我們可以用修飾器來修改類的屬性和方法,比如我們可以在函數執行之前改變它的行為。因為decorator是在編譯時執行的,使得讓我們能夠在設計時對類、屬性等進行標註和修改成為了可能。decorator不僅僅可以在類上面使用,還可以在對象上面使用,但是decorator不能修飾函數,因為函數存在變數提升。decorator相當於給對象內的函數包裝一層行為。decorator本身就是一個函數,他有三個參數target(所要修飾的目標類), name(所要修飾的屬性名), descriptor(該屬性的描述對象)。後面我們會讓大家體會到decorator的強大魅力。

大型框架都在使用decorator?
  • Angular2中的TypeScript Annotate就是標註裝潢器的另一類實現。

  • React中redux2也開始利用ES7的Decorators進行了大量重構。

  • Vue如果你在使用typescript,你會發現vue組件也開始用Decorator了,就連vuex也全部用Decorators重構。

接下來讓我們舉一個簡單的readonly的例子:

這是一個Dog類

  1. class Dog {

  2.  bark () {

  3.    return '汪汪汪!!'

  4.  }

  5. }

讓我們給他加上@readonly修飾器後

  1. import { readOnly } from "./decorators";

  2.  

  3. class Dog {

  4.  @readonly

  5.  bark () {

  6.    return '汪汪汪!!'

  7.  }

  8. }

  9.  

  10. let dog = new Dog()

  11. dog.bark = 'wangwang!!';

  12. // Cannot assign to read only property 'bark' of [object Object]

  13. // 這裡readonly修飾器把Dog類的bark方法修改為只讀狀態

讓我們看下readonly是怎麼實現的,代碼很簡單

  1. /**

  2. * @param target 目標類Dog

  3. * @param name 所要修飾的屬性名 bark

  4. * @param descriptor 該屬性的描述對象 bark方法

  5. */

  6. function readonly(target, name, descriptor) {

  7.  // descriptor對象原來的值如下

  8.  // {

  9.  //   value: specifiedFunction,

  10.  //   enumerable: false,

  11.  //   configurable: true,

  12.  //   writable: true

  13.  // };

  14.  descriptor.writable = false;

  15.  return descriptor

  16. }

readonly有三個參數,第一個target是目標類Dog,第二個是所要修飾的屬性名bark,是一個字元串,第三個是該屬性的描述對象,bark方法。這裡我們用readonly方法將bark方法修飾為只讀。所以當你修改bark方法的時候就是報錯了。

decorator 實用的decorator庫 core-decorators.js

npm install core-decorators --save

  1. // 將某個屬性或方法標記為不可寫。

  2. @readonly  

  3. // 標記一個屬性或方法,以便它不能被刪除; 也阻止了它通過Object.defineProperty被重新配置

  4. @nonconfigurable  

  5. // 立即將提供的函數和參數應用於該方法,允許您使用lodash提供的任意助手來包裝方法。 第一個參數是要應用的函數,所有其他參數將傳遞給該裝飾函數。

  6. @decorate  

  7. // 如果你沒有像Babel 6那樣的裝飾器語言支持,或者甚至沒有編譯器的vanilla ES5代碼,那麼可以使用applyDecorators()助手。

  8. @extendDescriptor

  9. // 將屬性標記為不可枚舉。

  10. @nonenumerable

  11. // 防止屬性初始值設定項運行,直到實際查找修飾的屬性。

  12. @lazyInitialize

  13. // 強制調用此函數始終將此引用到類實例,即使該函數被傳遞或將失去其上下文。

  14. @autobind

  15. // 使用棄用消息調用console.warn()。 提供自定義消息以覆蓋預設消息。

  16. @deprecate

  17. // 在調用裝飾函數時禁止任何JavaScript console.warn()調用。

  18. @suppressWarnings

  19. // 將屬性標記為可枚舉。

  20. @enumerable

  21. // 檢查標記的方法是否確實覆蓋了原型鏈上相同簽名的函數。

  22. @override  

  23. // 使用console.time和console.timeEnd為函數計時提供唯一標簽,其預設首碼為ClassName.method。

  24. @time

  25. // 使用console.profile和console.profileEnd提供函數分析,並使用預設首碼為ClassName.method的唯一標簽。

  26. @profile

還有很多這裡就不過多介紹,瞭解更多 https://github.com/jayphelps/core-decorators

下麵給大家介紹一些我們團隊寫的一些很實用的decorator方法庫

作者:吳鵬和 羅學

  • noConcurrent 避免併發調用,在上一次操作結果返回之前,不響應重覆操作

  1. import {noConcurrent} from './decorators';

  2. methods: {

  3.  @noConcurrent     //避免併發,點擊提交後,在介面返回之前無視後續點擊

  4.  async onSubmit(){

  5.    let submitRes = await this.$http({...});

  6.    //...

  7.    return;

  8.  }

  9. }

  • makeMutex 多函數互斥,具有相同互斥標識的函數不會併發執行

  1. import {makeMutex} from './decorators';

  2. let globalStore = {};

  3. class Navigator {

  4.  @makeMutex({namespace:globalStore, mutexId:'navigate'}) //避免跳轉相關函數併發執行

  5.  static async navigateTo(route){...}

  6.  

  7.  @makeMutex({namespace:globalStore, mutexId:'navigate'}) //避免跳轉相關函數併發執行

  8.  static async redirectTo(route){...}

  9. }

  • withErrToast 捕獲async函數中的異常,併進行錯誤提示

  1. methods: {

  2.  @withErrToast({defaultMsg: '網路錯誤', duration: 2000})

  3.  async pullData(){

  4.    let submitRes = await this.$http({...});

  5.    //...

  6.    return '其他原因'; // toast提示 其他原因

  7.    // return 'ok';   // 正常無提示

  8.  }

  9. }

  • mixinList 用於分頁載入,上拉載入時返回拼接數據及是否還有數據提示

  1. methods: {

  2.  @mixinList({needToast: false})

  3.  async loadGoods(params = {}){

  4.    let goodsRes = await this.$http(params);

  5.    return goodsRes.respData.infos;

  6.  },

  7.  async hasMore() {

  8.    let result = await this.loadgoods(params);

  9.    if(result.state === 'nomore') this.tipText = '沒有更多了';

  10.    this.goods = result.list;

  11.  }

  12. }

  13. // 上拉載入調用hasMore函數,goods數組就會得到所有拼接數據

  14. // loadGoods可傳三個參數 params函數需要參數 ,startNum開始的頁碼,clearlist清空數組

  15. // mixinList可傳一個參數 needToast 沒有數據是否需要toast提示

typeCheck 檢測函數參數類型

  1. methods: {

  2.  @typeCheck('number')

  3.  btnClick(index){ ... },

  4. }

  5. // btnClick函數的參數index不為number類型 則報錯

Buried 埋點處理方案,統計頁面展現量和所有methods方法點擊量,如果某方法不想設置埋點 可以 return 'noBuried' 即可

  1. @Buried

  2. methods: {

  3.  btn1Click() {

  4.    // 埋點為 btn1Click

  5.  },

  6.  btn2Click() {

  7.    return 'noBuried'; // 無埋點

  8.  },

  9. },

  10. created() {

  11.  // 埋點為 view

  12. }

  13. // 統計頁面展現量和所有methods方法點擊量

decorators.js

  1. /**

  2. * 避免併發調用,在上一次操作結果返回之前,不響應重覆操作

  3. * 如:用戶連續多次點擊同一個提交按鈕,希望只響應一次,而不是同時提交多份表單

  4. * 說明:

  5. *    同步函數由於js的單線程特性沒有併發問題,無需使用此decorator

  6. *    非同步時序,為便於區分操作結束時機,此decorator只支持修飾async函數

  7. */

  8. export const noConcurrent = _noConcurrentTplt.bind(null,{mutexStore:'_noConCurrentLocks'});

  9.  

  10. /**

  11. * 避免併發調用修飾器模板

  12. * @param {Object} namespace 互斥函數間共用的一個全局變數,用於存儲併發信息,多函數互斥時需提供;單函數自身免併發無需提供,以本地私有變數實現

  13. * @param {string} mutexStore 在namespace中占據一個變數名用於狀態存儲

  14. * @param {string} mutexId   互斥標識,具有相同標識的函數不會併發執行,預設值:函數名

  15. * @param target

  16. * @param funcName

  17. * @param descriptor

  18. * @private

  19. */

  20. function _noConcurrentTplt({namespace={}, mutexStore='_noConCurrentLocks', mutexId}, target, funcName, descriptor) {

  21.  namespace[mutexStore] = namespace[mutexStore] || {};

  22.  mutexId = mutexId || funcName;

  23.  

  24.  let oriFunc = descriptor.value;

  25.  descriptor.value = function () {

  26.    if (namespace[mutexStore][mutexId]) //上一次操作尚未結束,則無視本次調用

  27.      return;

  28.  

  29.    namespace[mutexStore][mutexId] = true; //操作開始

  30.    let res = oriFunc.apply(this, arguments);

  31.  

  32.    if (res instanceof Promise)

  33.      res.then(()=> {

  34.        namespace[mutexStore][mutexId] = false;

  35.      }).catch((e)=> {

  36.        namespace[mutexStore][mutexId] = false;

  37.        console.error(funcName, e);

  38.      }); //操作結束

  39.    else {

  40.      console.error('noConcurrent decorator shall be used with async function, yet got sync usage:', funcName);

  41.      namespace[mutexStore][mutexId] = false;

  42.    }

  43.  

  44.    return res;

  45.  }

  46. }

  47.  

  48. /**

  49. * 多函數互斥,具有相同互斥標識的函數不會併發執行

  50. * @param namespace 互斥函數間共用的一個全局變數,用於存儲併發信息

  51. * @param mutexId   互斥標識,具有相同標識的函數不會併發執行

  52. * @return {*}

  53. */

  54. export function makeMutex({namespace, mutexId}) {

  55.  if (typeof namespace !== "object") {

  56.    console.error('[makeNoConcurrent] bad parameters, namespace shall be a global object shared by all mutex funcs, got:', namespace);

  57.    return function () {}

  58.  }

  59.  

  60.  return _noConcurrentTplt.bind(null, {namespace, mutexStore:'_noConCurrentLocksNS', mutexId})

  61. }

  62.  

  63. /**

  64. * 捕獲async函數中的異常,併進行錯誤提示

  65. * 函數正常結束時應 return 'ok',return其它文案時將toast指定文案,無返回值或產生異常時將toast預設文案

  66. * @param {string} defaultMsg  預設文案

  67. * @param {number, optional} duration 可選,toast持續時長

  68. */

  69. export function withErrToast({defaultMsg, duration=2000}) {

  70.  return function (target, funcName, descriptor) {

  71.    let oriFunc = descriptor.value;

  72.    descriptor.value = async function () {

  73.      let errMsg = '';

  74.      let res = '';

  75.      try {

  76.        res = await oriFunc.apply(this, arguments);

  77.        if (res != 'ok')

  78.          errMsg = typeof res === 'string' ? res : defaultMsg;

  79.      } catch (e) {

  80.        errMsg = defaultMsg;

  81.        console.error('caught err with func:',funcName, e);

  82.      }

  83.  

  84.      if (errMsg) {

  85.        this.$toast({

  86.          title: errMsg,

  87.          type: 'fail',

  88.          duration: duration,

  89.        });

  90.      }

  91.      return res;

  92.    }

  93.  }

  94. }

  95.  

  96. /**

  97. * 分頁載入

  98. * @param {[Boolean]} [是否載入為空顯示toast]

  99. * @return {[Function]} [decrotor]

  100. */

  101. export function mixinList ({needToast = false}) {

  102.  let oldList = [],

  103.      pageNum = 1,

  104.  /**

  105.  * state [string]

  106.  *   hasmore  [還有更多]

  107.  *   nomore   [沒有更多了]

  108.  */

  109.  state = 'hasmore',

  110.  current = [];

  111.  return function (target,name,descriptor) {

  112.    const oldFunc  = descriptor.value,

  113.          symbol   = Symbol('freeze');

  114.    target[symbol] = false;

  115.    /**

  116.     * [description]

  117.     * @param  {[Object]}   params={}       [請求參數]

  118.     * @param  {[Number]}   startNum=null   [手動重置載入頁數]

  119.     * @param  {[Boolean]}  clearlist=false [是否清空數組]

  120.     * @return {[Object]}   [{所有載入頁數組集合,載入完成狀態}]

  121.     */

  122.    descriptor.value = async function(params={},startNum=null,clearlist=false) {

  123.      try {

  124.        if (target[symbol]) return;

  125.        // 函數執行前賦值操作

  126.        target[symbol] = true;

  127.        params.data.pageNum = pageNum;

  128.        if (startNum !== null && typeof startNum === 'number') {

  129.          params.data.pageNum = startNum;

  130.          pageNum = startNum;

  131.        }

  132.        if (clearlist) oldList = [];

  133.        // 釋放函數,取回list

  134.        let before = current;

  135.        current = await oldFunc.call(this,params);

  136.        // 函數執行結束賦值操作

  137.        (state === 'hasmore' || clearlist) && oldList.push(...current);

  138.        if ((current.length === 0) || (params.data.pageSize > current.length)) {

  139.          needToast && this.$toast({title: '沒有更多了',type: 'fail'});

  140.          state = 'nomore';

  141.        } else {

  142.          state = 'hasmore';

  143.          pageNum++;

  144.        }

  145.        target[symbol] = false;

  146.        this.$apply();

  147.        return { list : oldList,state };

  148.      } catch(e) {

  149.        console.error('fail code at: ' + e)

  150.      }

  151.    }

  152.  }

  153. }

  154.  

  155. /**

  156. * 檢測工具

  157. */

  158. const _toString = Object.prototype.toString;

  159. // 檢測是否為純粹的對象

  160. const _isPlainObject = function  (obj) {

  161.  return _toString.call(obj) === '[object Object]'

  162. }

  163. // 檢測是否為正則

  164. const _isRegExp = function  (v) {

  165.  return _toString.call(v) === '[object RegExp]'

  166. }

  167. /**

  168. * @description 檢測函數

  169. *  用於檢測類型action

  170. * @param {Array} checked 被檢測數組

  171. * @param {Array} checker 檢測數組

  172. * @return {Boolean} 是否通過檢測

  173. */

  174. const _check = function (checked,checker) {

  175.  check:

  176.  for(let i = 0; i < checked.length; i++) {

  177.    if(/(any)/ig.test(checker[i]))

  178.      continue check;

  179.    if(_isPlainObject(checked[i]) && /(object)/ig.test(checker[i]))

  180.      continue check;

  181.    if(_isRegExp(checked[i]) && /(regexp)/ig.test(checker[i]))

  182.      continue check;

  183.    if(Array.isArray(checked[i]) && /(array)/ig.test(checker[i]))

  184.      continue check;

  185.    let type = typeof checked[i];

  186.    let checkReg = new RegExp(type,'ig')

  187.    if(!checkReg.test(checker[i])) {

  188.      console.error(checked[i] + 'is not a ' + checker[i]);

  189.      return false;

  190.    }

  191.  }

  192.  return true;

  193. }

  194. /**

  195. * @description 檢測類型

  196. *   1.用於校檢函數參數的類型,如果類型錯誤,會列印錯誤並不再執行該函數;

  197. *   2.類型檢測忽略大小寫,如string和String都可以識別為字元串類型;

  198. *   3.增加any類型,表示任何類型均可檢測通過;

  199. *   4.可檢測多個類型,如 "number array",兩者均可檢測通過。正則檢測忽略連接符 ;

  200. */

  201. export function typeCheck() {

  202.  const checker =  Array.prototype.slice.apply(arguments);

  203.  return function (target, funcName, descriptor) {

  204.    let oriFunc = descriptor.value;

  205.    descriptor.value =  function () {

  206.      let checked =  Array.prototype.slice.apply(arguments);

  207.      let result = undefined;

  208.      if(_check(checked,checker) ){

  209.        result = oriFunc.call(this,...arguments);

  210.      }

  211.      return result;

  212.    }

  213.  }

  214. };

  215.  

  216. const errorLog = (text) => {

  217.  console.error(text);

  218.  return true;

  219. }

  220. /**

  221. * @description 全埋點

  222. *  1.在所有methods方法中埋點為函數名

  223. *  2.在鉤子函數中'beforeCreate','created','beforeMount','mounted','beforeUpdate','activated','deactivated'依次尋找這些鉤子

  224. *    如果存在就會增加埋點 VIEW

  225. *

  226. * 用法:

  227. *   @Buried

  228. *   在單文件導出對象一級子對象下;

  229. *   如果某方法不想設置埋點 可以 return 'noBuried' 即可

  230. */

  231. export function Buried(target, funcName, descriptor) {

  232.  let oriMethods = Object.assign({},target.methods),

  233.      oriTarget = Object.assign({},target);

  234.  // methods方法中

  235.  if(target.methods) {

  236.    for(let name in target.methods) {

  237.      target.methods[name] = function () {

  238.        let result = oriMethods[name].call(this,...arguments);

  239.        // 如果方法中返回 noBuried 則不添加埋點

  240.        if(typeof result === 'string' && result.includes('noBuried')) {

  241.          console.log(name + '方法設置不添加埋點');

  242.        } else if(result instanceof Promise) {

  243.          result.then(res => {

  244.            if(typeof res === 'string' && res.includes('noBuried')) { console.log(name + '方法設置不添加埋點'); return; };

  245.            console.log('添加埋點在methods方法中:' , name.toUpperCase ());

  246.            this.$log(name);

  247.          });

  248.        }else{

  249.          console.log('添加埋點在methods方法中:' , name.toUpperCase ());

  250.          this.$log(name);

  251.        };

  252.        return result;

  253.      }

  254.    }

  255.  }

  256.  // 鉤子函數中

  257.  const hookFun = (hookName) => {

  258.    target[hookName] = function() {

  259.      let result =  oriTarget[hookName].call(this,...arguments);

  260.      console.log('添加埋點,在鉤子函數' + hookName + '中:', 'VIEW');

  261.      this.$log('VIEW');

  262.      return result;

  263.    }

  264.  }

  265.  

  266.  const LIFECYCLE_HOOKS = [

  267.    'beforeCreate',

  268.    'created',

  269.    'beforeMount',

  270.    'mounted',

  271.    'beforeUpdate',

  272.    'activated',

  273.    'deactivated',

  274.  ];

  275.  

  276.  for(let item of LIFECYCLE_HOOKS) {

  277.    if (target[item]) return hookFun(item);

  278.  }

  279. }

 

 
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • HTML 5.2草案加入了新的dialog元素。但是是一種實驗技術。 以前,如果我們想要構建任何形式的模式對話框或對話框,我們需要有一個背景,一個關閉按鈕,將事件綁定在對話框中的方式安排我們的標記,找到一種將消息傳遞出去的方式對話......這真的很複雜。對話框元素解決了上述所有問題。 ...
  • [1]安裝 [2]初始化 [3]安裝插件 [4]git鉤子 [5]配置任務 [6]郵件提醒 [7]測試 ...
  • 現在各種小程式風靡,這邊H5的需求還沒有搞定,產品又要求做小程式版本,做可以,關鍵是618前上線,我…… whatever,618要做推廣,日期訂了,剩下的就只能是排期,定方案,儘可能完成。 最後和產品商量之後的決定是:小程式中特有的營銷推廣的頁面,用小程式編寫,剩下的黃金流程,內嵌H5解決。 聽起 ...
  • 恢復內容開始 在Vue學習的過程中可能出現這個情況,未捕獲ReferenceError:Vue沒有定義,為什麼沒有定義及找到這個Vue呢,目前說的最多的是“過早關閉腳本標簽”,例如這樣引用 <script> src="https://cdnjs.cloudflare.com/ajax/libs/vu ...
  • json 概念:json是一種輕量級數據交換格式。 如果我們要在不同的編程語言之間傳遞對象,就必須把對象序列化為標準格式,比如XML,但更好的方法是序列化為JSON,因為JSON表示出來就是一個字元串,可以被所有語言讀取,也可以方便地存儲到磁碟或者通過網路傳輸。JSON不僅是標準格式,並且比XML更 ...
  • ...
  • 自定義主體樣式參數 在皮膚里先挑一個現成的模板開始我們的定製之旅,這裡我選了SimpleMemory,我還是喜歡這種朴素的風格。 選擇SimpleMemory模板 上圖成品的修改源碼如下: 能明白上述樣式原理,那麼就可以繼續定製h1,h2,h3元素和表格 定製h1,h2,h3 定製表格元素 其他參數 ...
  • 原文出處: 🚀 Nuxt 2 is coming! Oh yeah! – Nuxt.js – Medium [https://medium.com/nuxt/nuxt-2-is-coming-oh-yeah-212c1a9e1a67] 1.4.0發佈25天後,Nuxt2就即將來臨。超過330次提交 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...