[vue3] vue3初始化渲染流程

来源:https://www.cnblogs.com/feixianxing/p/18343902/vue3-render-and-mount-app-component
-Advertisement-
Play Games

本文解析了 Vue3 組件初次渲染的流程,涵蓋應用程式初始化、核心渲染步驟,以及 vnode 的創建和渲染,探討了 Vue3 內部機制及其跨平臺實現的關鍵細節。 ...


組件初次渲染流程

組件是對DOM樹的抽象,組件的外觀由template定義,模板在編譯階段會被轉化為一個渲染函數,用於在運行時生成vnode。即組件在運行時的渲染步驟是:

graph LR A[創建vnode] --> B[渲染vnode] --> C[生成DOM]

vnode是一個用於描述視圖的結構和屬性的JavaScript對象。vnode是對真實DOM的一層抽象。

使用vnode的優點:

  • 相比於直接操作DOM在需要頻繁更新視圖的場景下,可以將多次操作應用在vnode上,再一次性地生成真實DOM,可以避免頻繁重排重繪導致的性能問題;
  • vnode是抽象的視圖層,具有平臺無關性,上層代碼可移植性強。

應用程式初始化

對於一個vue-app來說,整個組件樹由根組件開始渲染。為了找到根組件的渲染入口,從應用程式的初始化過程開始分析。

Vue2中,初始化應用的代碼:

import Vue from 'vue';
import App from './App';

const app = new Vue({
    render: h=>h(App)
});
app.$mount('#app');

Vue3中,初始化應用的代碼:

import { createApp } from 'vue';
import App from './App';

const app = createApp(App);
app.mount('#app');

對比二者的代碼可以看出,本質都是把App組件掛載到了#appDOM節點上。

本文主要關註Vue3

Vue3createApp的實現大致如下:

首先,createApp函數由createAppAPI根據對應的render對象構建得到。

export function createAppAPI<HostElement>(
  render: RootRenderFunction<HostElement>,
  hydrate?: RootHydrateFunction,
): CreateAppFunction<HostElement> {
  return function createApp(rootComponent, rootProps = null) {
      //...
  }
}

源碼位置:core/packages/runtime-core/src/apiCreateApp.ts at main · vuejs/core (github.com)

render對象由baseCreateRenderer函數創建,根據不同的環境創建不同的render對象(常見的是瀏覽器環境下用來渲染DOM)。

並由render對象來決定createApp函數的實現:

// baseCreateRenderer函數的返回值
return {
    render,
    hydrate,
    createApp: createAppAPI(render, hydrate),
}

這種根據不同環境構建不同render對象的操作是為了實現跨平臺

接下來回到createApp內部。

createApp應用工廠模式,在內部創建app對象,實現了mount方法,mount方法就是用來掛載組件的。

function createApp(rootComponent, rootProps = null){
    // ...
    const app: App = {
        // ...
        mount(
        	rootContainer: HostElement,
            isHydrate?: boolean,
            namespace?: boolean | ElementNamespace,
        ): any{
        	// mount的具體實現,這裡省略了很多代碼...
            // 1. 創建vnode
            const vnode = createVNode(rootComponent, rootProps)
            // 2. 渲染vnode
            render(vnode, rootContainer, namespace)
    	}
        // ...
    }
    return app;
}

在整個app對象創建過程中,Vue3通過閉包和函數柯里化等技巧實現了參數保留。

例如上面的mount方法內部實際上會使用render函數將vnode掛載到container上。而rendercreateAppAPI調用時傳入。這就是閉包的應用。

graph TB A["baseCreateRenderer"] --> B["craeteAppAPI [render]"] B --> C["createApp"] C --> D["mount"] D --> |"使用render"|B

上面提到的app對象中對mount的實現位於packages/runtime-core,也就是說是與平臺無關的,內部都是對抽象的vnoderootContainer進行操作,不一定是DOM節點。


Vue3將瀏覽器相關的DOM的實現移到了packages/runtime-dom中,在index.ts中可以看到ensureRenderer函數就調用了runtime-core中上述提到的createRenderer方法,傳入了DOM相關的配置,用於獲取一個專門用於瀏覽器環境的renderer

源碼位置:core/packages/runtime-dom/src/index.ts at main · vuejs/core (github.com)

runtime-domindex.ts中,我們從createApp函數入手,觀察到它調用了ensureRenderer來獲取一個適配瀏覽器環境的renderer,並調用其對應的createApp函數。

export const createApp = ((...args) => {
  const app = ensureRenderer().createApp(...args)
  // ......
  const { mount } = app
  // 重寫mount方法
  app.mount = (containerOrSelector: Element | ShadowRoot | string): any => {
    // 標準化容器:將字元串選擇器轉換為DOM對象
    const container = normalizeContainer(containerOrSelector)
    if (!container) return

    const component = app._component
    // 如果組件對象沒有定義render函數和template模板,則取容器的innerHTML作為模板內容
    if (!isFunction(component) && !component.render && !component.template) {
      // 使用innerHTML需要註意安全性問題
      component.template = container.innerHTML
      // ......
    }

    // 掛載前刪除容器的內容
    container.innerHTML = ''
    // 走runtime-core中實現的標準流程進行掛載
    const proxy = mount(container, false, resolveRootNamespace(container))
    // ......
    return proxy
  }

  return app
}) as CreateAppFunction<Element>

階段性總結

  • 重寫mount的原因:

    • runtime-core中的mount:實現標準化掛載流程;
    • runtime-dom中的mount:實現DOM節點相關的預處理,然後調用runtime-core中的mount進行掛載;
  • runtime-dommount的流程:

    1. 標準化容器:如果傳入字元串選擇器,那麼調用document.querySelector將其轉換為DOM對象;

    2. 檢查組件是否存在render函數和template對象,如果沒有則使用容器的innerHTML作為模板;

      使用innerHTML需要註意安全性問題。

    3. 刪除容器原先的innerHTML內容;

    4. 調用runtime-core中實現的mount方法走標準化流程掛載組件到DOM節點上。

app.mount方法調用後,才真正開始組件的渲染流程。

接下來,回到runtime-core中關註渲染流程。

核心渲染流程

這一流程中主要做了兩件事:創建vnode渲染vnode

vnode是用來描述DOMJavaScript對象,在Vue中既可以描述普通DOM節點,也可以描述組件節點,除此之外還有純文本vnode和註釋vnode

可以在runtime-corevnode.ts文件中找到vnode的類型定義:core/packages/runtime-core/src/vnode.ts at main · vuejs/core (github.com)

內容較多,這裡不做展示,比較核心的屬性有比如:

  • type:組件的標簽類型;
  • props:附加信息;
  • children:子節點,vnode數組;

除此之外,Vue3還為vnode打上了各種flag來做標記,在patch階段根據不同的類型執行相應的處理邏輯。

創建vnode

mount方法的實現中,通過調用createVNode函數創建根組件的vnode

const vnode = createVNode(rootComponent, rootProps);

vnode.ts中可以找到createVNode函數的實現:core/packages/runtime-core/src/vnode.ts at main · vuejs/core (github.com)

大致思路如下:

function _createVNode(
  type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,
  props: (Data & VNodeProps) | null = null,
  children: unknown = null,
  patchFlag: number = 0,
  dynamicProps: string[] | null = null,
  isBlockNode = false,
): VNode{
  // ...
  // 標準化class和style這些樣式屬性
  if(props){
    // ...
  }
    
  // 對vnode類型信息編碼(二進位)
  const shapeFlag = isString(type)
    ? ShapeFlags.ELEMENT
    : __FEATURE_SUSPENSE__ && isSuspense(type)
      ? ShapeFlags.SUSPENSE
      : isTeleport(type)
        ? ShapeFlags.TELEPORT
        : isObject(type)
          ? ShapeFlags.STATEFUL_COMPONENT
          : isFunction(type)
            ? ShapeFlags.FUNCTIONAL_COMPONENT
            : 0
  // 調用工廠函數構建vnode對象
  return createBaseVNode(
    type,
    props,
    children,
    patchFlag,
    dynamicProps,
    shapeFlag,
    isBlockNode,
    true,
  )
}

接下來看一下createBaseVNode的大致實現(這個函數也位於vnode.ts文件內):

function createBaseVNode(
	// vnode部分屬性的值
){
	const vnode = {
        type,
        props,
        // ...很多屬性
    } as VNode
    
    // 標準化children:討論數組或者文本類型
    if (needFullChildrenNormalization) {
    	normalizeChildren(vnode, children)
    }
    return vnode
}

渲染vnode

創建好vnode之後就是渲染的過程,在mount中使用render函數渲染創建好的vnode

render的標準化流程的實現位於runtime-corerenderer.ts中:

源碼位置:core/packages/runtime-core/src/renderer.ts at main · vuejs/core (github.com)

const render: RootRenderFunction = (vnode, container, namespace) => {
    if (vnode == null) {
        // 銷毀組件
        if (container._vnode) {
            unmount(container._vnode, null, null, true)
        }
    } else {
        // 創建或者更新組件
        patch(
            container._vnode || null,
            vnode,
            container,
            null,
            null,
            null,
            namespace,
        )
    }
    if (!isFlushing) {
        isFlushing = true
        flushPreFlushCbs()
        flushPostFlushCbs()
        isFlushing = false
    }
    // 緩存vnode節點,表示已經渲染
    container._vnode = vnode
}
  • 如果vnode不存在,則調用unmount銷毀組件;
  • 如果vnode存在,那麼調用patch創建或者更新組件;
  • vnode緩存到容器對象上,表示已渲染。

patch函數的前兩個參數分別是舊vnode和新vnode

  • 初次調用,則container._vnode屬性返回undefined,短路運算符傳入null,則patch內部走創建邏輯;調用過後會將創建的vnode緩存到container._vnode
  • 後續調用的container._vnode表示上一次創建的vnode,不為null,傳入patch後走更新邏輯。
patch的實現

patch本意是打補丁,這個函數有兩個功能:

  1. 根據vnode掛載DOM
  2. 比較新舊vnode更新DOM

這裡只討論初始化流程,故只記錄如何掛載DOM,更新流程這裡不做介紹。

源碼位置:core/packages/runtime-core/src/renderer.ts at main · vuejs/core (github.com)

const patch: PatchFn = (
    n1,
    n2,
    container,
    anchor = null,
    parentComponent = null,
    parentSuspense = null,
    namespace = undefined,
    slotScopeIds = null,
    optimized = __DEV__ && isHmrUpdating ? false : !!n2.dynamicChildren,
) => {
    // 二者相同,不需要更新
    if (n1 === n2) {
        return
    }

    // vnode類型不同,直接卸載舊節點
    if (n1 && !isSameVNodeType(n1, n2)) {
        anchor = getNextHostNode(n1)
        unmount(n1, parentComponent, parentSuspense, true)
        n1 = null
    }
	// ......

    const { type, ref, shapeFlag } = n2
    switch (type) {
        case Text:
            // 處理文位元組點
            break
        case Comment:
            // 處理註釋節點
            break
        case Static:
            // 靜態節點
            break
        case Fragment:
            // Fragment節點
            break
        default:
            if (shapeFlag & ShapeFlags.ELEMENT) {
                // 處理普通DOM元素
            } else if (shapeFlag & ShapeFlags.COMPONENT) {
                // 處理組件
            } else if (shapeFlag & ShapeFlags.TELEPORT) {
                // 處理teleport
            } else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
                // 處理suspense
            } else if (__DEV__) {
                // 報錯:vnode類型不在可識別範圍內
                warn('Invalid VNode type:', type, `(${typeof type})`)
            }
    }
}

這裡只關註前三個函數參數:

  • n1:舊vnode,為null則表示初次掛載;
  • n2:新vnode
  • container:掛載的目標容器。

patch在其內部調用了processXXX處理不同類型的vnode,這裡只關註組件類型和普通DOM節點類型。

對組件的處理

處理組件調用的是processComponent函數:

processComponent
const processComponent = (
    n1: VNode | null,
    n2: VNode,
    container: RendererElement,
    // ... 其它參數
) => {
    if (n1 == null) {
        // 掛載組件
        mountComponent(n2, container, /*...other args*/)
    } else {
        // 更新組件
        updateComponent(n1, n2, optimized)
    }
}
// 這裡還有很多其它參數省略了,函數體內還處理了`keep-alive`的情況,具體可以自己看源碼。
  • 掛載組件使用mountComponent函數;
  • 更新組件使用updateComponent函數。
mountComponent

源碼位置:core/packages/runtime-core/src/renderer.ts at main · vuejs/core (github.com)

這個函數處理了較多邊界情況,這裡只展示主要的步驟:

 const mountComponent: MountComponentFn = (
    initialVNode,
    container,
    anchor,
    parentComponent,
    parentSuspense,
    namespace: ElementNamespace,
    optimized,
 ) => {
     // 創建組件實例
     const instance: ComponentInternalInstance =
           (initialVNode.component = createComponentInstance(
               initialVNode,
               parentComponent,
               parentSuspense,
           ))
	
     // 設置組件實例
     setupComponent(instance, false, optimized)
	
     // 設置並運行帶副作用的渲染函數
     setupRenderEffect(
         instance,
         initialVNode,
         container,
         anchor,
         parentSuspense,
         namespace,
         optimized,
     )
 }
  • 創建組件實例:工廠模式創建組件實例對象;
  • 設置組件實例:instance記錄了許多組件相關的數據,setupComponent這一步主要是對propsslots等屬性進行初始化。

接下來重點看一下setupRenderEffect函數的實現。

setupRenderEffect

setupRenderEffect 函數的主要工作是設置一個響應式效果 (ReactiveEffect),並創建一個調度任務 (SchedulerJob) 來管理組件的渲染和更新。首次渲染和後續更新的邏輯都封裝在 componentUpdateFn 中。

簡化後的代碼

const setupRenderEffect: SetupRenderEffectFn = (
  instance,
  initialVNode,
  container,
  anchor,
  parentSuspense,
  namespace: ElementNamespace,
  optimized,
) => {
  // 組件更新函數
  const componentUpdateFn = () => {
    if (!instance.isMounted) {
      // 首次掛載邏輯
      instance.subTree = renderComponentRoot(instance)
      patch(null, instance.subTree, container, anchor, instance, parentSuspense, namespace)
      instance.isMounted = true
    } else {
      // 後續更新邏輯
      const nextTree = renderComponentRoot(instance)
      patch(instance.subTree, nextTree, container, anchor, instance, parentSuspense, namespace)
      instance.subTree = nextTree
    }
  }

  // 創建響應式效果
  const effect = (instance.effect = new ReactiveEffect(componentUpdateFn, NOOP))

  // 創建調度任務
  const update: SchedulerJob = (instance.update = () => {
    if (effect.dirty) {
      effect.run()
    }
  })

  // 立即執行更新函數
  update()
}

setupRenderEffect內部主要包含了3個函數:

  • componentUpdateFn 的主要作用是在組件首次掛載和後續更新時執行相應的渲染邏輯,確保組件的虛擬 DOM 樹與實際的 DOM 樹保持同步,並執行相關的生命周期鉤子函數
  • effect 封裝了組件的渲染邏輯,負責在響應式依賴變化時觸發重新渲染
  • update 是調度任務,負責在適當的時機檢查和觸發 effect,確保組件的渲染邏輯能夠正確執行。

也就是說它們依次為前者的進一步封裝。

componentUpdateFn中的初始掛載邏輯:

  • 渲染組件生成subTree;(遞歸調用patch
  • subTree通過patch掛載到container上。

這裡的patch就是一個遞歸過程。事實上patch對於組件只有渲染過程,沒有掛載的操作,因為組件是抽象的,並不能通過DOM API插入到頁面上。

也就是說patch只對DOM類型元素進行mount掛載,對於組件類型元素的處理只做遞歸操作。換個角度描述就是:組件樹的葉子節點一定都是DOM類型元素,只有這樣才能渲染並掛載到頁面上。

接下來開始研究patchDOM類型元素的處理過程。(可以返回上文看一下patch的實現)。

對DOM的處理
processElement

patch函數使用processElement 函數處理新舊DOM元素,當n1null時,走掛載流程;否則走更新流程。

源碼地址:core/packages/runtime-core/src/renderer.ts at main · vuejs/core (github.com)

const processElement = (
    n1: VNode | null,
    n2: VNode,
    container: RendererElement,
    // ...other args...
  ) => {
    if (n1 == null) {
      // 掛載
      mountElement(n2, container, /* ...other args... */)
    } else {
      // 更新
      patchElement(n1, n2, parentComponent, /* ...other args... */)
    }
  }
mountElement

源碼位置:core/packages/runtime-core/src/renderer.ts at main · vuejs/core (github.com)

這裡省略了很多代碼,只保留大致流程:

  1. 創建DOM元素;

  2. 掛載子節點;

    • 如果子節點只是文字,則設置DOM節點的textContent

    • 如果子節點是數組,則使用for迴圈 + 遞歸調用patch函數渲染子元素;

      這裡遞歸使用的是patch而不是mountElement是因為子元素可能不是DOM元素,而是其它類型的元素。因此還是要用到patch中的switch - case走類型判斷的邏輯。

  3. 設置DOM元素的屬性;

  4. 插入DOM元素。

const mountElement = (
  vnode: VNode,
  container: RendererElement,
  /* ...other args... */
) => {
  const { props, shapeFlag, transition, dirs } = vnode
  // 創建DOM元素
  const el = vnode.el = hostCreateElement(vnode.type as string, namespace, props && props.is, props)

  // 掛載子節點
  if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
    hostSetElementText(el, vnode.children as string)
  } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
    mountChildren(vnode.children as VNodeArrayChildren, el, null, parentComponent, parentSuspense, resolveChildrenNamespace(vnode, namespace), slotScopeIds, optimized)
  }
	
  // 設置屬性
  if (props) {
    for (const key in props) {
      if (key !== 'value' && !isReservedProp(key)) {
        hostPatchProp(el, key, null, props[key], namespace, parentComponent)
      }
    }
    // 特殊處理 value 屬性
    if ('value' in props) {
      hostPatchProp(el, 'value', null, props.value, namespace)
    }
  }

  // 插入元素
  hostInsert(el, container, anchor)
}

其中的hostCreateElementhostSetElementTexthostPatchProphostInsert函數都由runtime-dom中在創建renderer的時候傳入對應的實現。

runtime-dom模塊的nodeOps.tspatchProp.ts文件可以找到這些DOM相關操作的具體實現。

nodeOps.ts源碼位置:core/packages/runtime-dom/src/nodeOps.ts at e26fd7b1d15cb3335a4c2230cc49b1008daddca1 · vuejs/core (github.com)

patchProp.ts源碼位置:core/packages/runtime-dom/src/patchProp.ts at e26fd7b1d15cb3335a4c2230cc49b1008daddca1 · vuejs/core (github.com)

上述hostXXX對應的DOM方法分別是:

  • hostCreateElementdocument.createElement
  • hostSetElementTextel.textContent = ...
  • hostPatchProp:直接修改DOM對象上的鍵值,會對特殊的key做處理;
  • hostInsert:[Node.insertBefore](Node.insertBefore() - Web API | MDN (mozilla.org))

初次渲染流程總結

mount


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

-Advertisement-
Play Games
更多相關文章
  • 這次向大家分享Microsoft發表在SOSP'13的另一篇關於流處理系統論文Naiad,TimelyDataflow是它的開源實現。該論文促進了後續的流圖系統的設計與創新,從其調度框架設計中也可以看到TuGraph Analytics調度器的影子。 ...
  • EasyMR 作為袋鼠雲基於雲原生技術和 Hadoop、Hive、Spark、Flink、Hbase、Presto 等開源大數據組件構建的彈性計算引擎。此前,我們已就其展開了多方位、多角度的詳盡介紹。而此次,我們成功接入了大數據組件的升級和回滾功能,能夠藉助 EasyMR 來掌控大數據組件的升級與回 ...
  • 這次向大家分享發表在SOSP 2013上的另一篇經典圖計算框架論文X-Stream,構建了單機上基於外存的Scatter-Gather圖處理框架。 ...
  • 我們非常激動地宣佈,詞雲圖大師(WordCloudMaster)現已正式上線Web端!這一全新版本為用戶帶來了更多的便捷和功能,讓創建和分享詞雲變得更加輕鬆。無論是企業、教育機構還是個人用戶,都可以通過Web端實現快速生成和定製屬於自己的詞雲圖。 https://studio.wordcloudma ...
  • Kotlin中的布爾值是一種數據類型,僅能存儲`true`或`false`兩種狀態,適用於表示二選一的情況,如開關或真假判斷。布爾類型可通過`Boolean`關鍵字聲明,並直接賦值為`true`或`false`。此外,Kotlin支持使用比較運算符創建布爾表達式,用於條件判斷。條件語句包括`if`、... ...
  • ​一年一度的畢業季就要到了,畢業設計算是大學生畢業前的最後一個大作業,尤其是電腦相關專業的畢業設計,通常要通過編程開發一個軟體,比如開發一個圖書館管理系統,開發一個電商APP等等。 一個好的畢業設計可以給作者加分,可以評優,還能獲得編程開發的實戰經驗,所以很有必要認真去做畢業設計。那麼就電腦相關 ...
  • Kotlin中的字元串用於存儲文本,定義時使用雙引號包圍字元序列,如`var greeting = "Hello"`。Kotlin能自動推斷變數類型,但在未初始化時需顯式指定類型,如`var name: String`。可通過索引訪問字元串元素,如`txt[0]`獲取首字元。字元串作為對象,擁有屬性... ...
  • ## Kotlin 運算符 - **用途**: 對變數和值執行操作。 - **示例**: ```kotlin var x = 100 + 50 // 150 ``` - **分類**: - **算術**: `+`, `-`, `*`, `/`, `%`, `++`, `--`. ... ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...