Vue3設計思想及響應式源碼剖析

来源:https://www.cnblogs.com/Jcloud/archive/2023/11/23/17850848.html
-Advertisement-
Play Games

一、Vue3結構分析 1、Vue2與Vue3的對比 對TypeScript支持不友好(所有屬性都放在了this對象上,難以推倒組件的數據類型) 大量的API掛載在Vue對象的原型上,難以實現TreeShaking。 架構層面對跨平臺dom渲染開發支持不友好,vue3允許自定義渲染器,擴展能力強。 C ...


一、Vue3結構分析

1、Vue2與Vue3的對比

  • 對TypeScript支持不友好(所有屬性都放在了this對象上,難以推倒組件的數據類型)
  • 大量的API掛載在Vue對象的原型上,難以實現TreeShaking。
  • 架構層面對跨平臺dom渲染開發支持不友好,vue3允許自定義渲染器,擴展能力強。
  • CompositionAPI。受ReactHook啟發
  • 對虛擬DOM進行了重寫、對模板的編譯進行了優化操作...

2、Vue3設計思想

  • Vue3.0更註重模塊上的拆分,在2.0中無法單獨使用部分模塊。需要引入完整的Vuejs(例如只想使用使用響應式部分,但是需要引入完整的Vuejs), Vue3中的模塊之間耦合度低,模塊可以獨立使用。拆分模塊
  • Vue2中很多方法掛載到了實例中導致沒有使用也會被打包(還有很多組件也是一樣)。通過構建工具Tree-shaking機制實現按需引入,減少用戶打包後體積。重寫API
  • Vue3允許自定義渲染器,擴展能力強。不會發生以前的事情,改寫Vue源碼改造渲染方式。擴展更方便

依然保留了Vue2的特點:

依舊是聲明式框架,底層渲染邏輯不關心(命令式比較關註過程,可以控制怎麼寫最優?編寫過程不同),如for和reduce

採用虛擬DOM

區分編譯時和運行時

內部區分了編譯時(模板?編程成js代碼,一般在構建工具中使用)和運行時

簡單來說,Vue3 框架更小,擴展更加方便

3、monorepo管理項目

Monorepo 是管理項目代碼的一個方式,指在一個項目倉庫(repo)中管理多個模塊/包(package)。也就是說是一種將多個package放在一個repo中的代碼管理模式。Vue3內部實現了一個模塊的拆分, Vue3源碼採用 Monorepo 方式進行管理,將模塊拆分到package目錄中。

  • 一個倉庫可維護多個模塊,不用到處找倉庫
  • 方便版本管理和依賴管理,模塊之間的引用,調用都非常方便
  • 每個包可以獨立發佈

早期使用yarn workspace + lerna來管理項目,後面是pnpm

pnpm介紹

快速,節省磁碟空間的包管理器,主要採用符號鏈接的方式管理模塊

  1. 快速

  2. 高效利用磁碟空間

pnpm 內部使用基於內容定址的文件系統來存儲磁碟上所有的文件,這個文件系統出色的地方在於:

  • 不會重覆安裝同一個包。用 npm/yarn 的時候,如果 100 個項目都依賴 lodash,那麼 lodash 很可能就被安裝了 100 次,磁碟中就有 100 個地方寫入了這部分代碼。但在使用 pnpm 只會安裝一次,磁碟中只有一個地方寫入,後面再次使用都會直接使用hardlink(硬鏈接)
  • 即使一個包的不同版本,pnpm 也會極大程度地復用之前版本的代碼。比如 lodash 有 100 個文件,更新版本之後多了一個文件,那麼磁碟當中並不會重新寫入 101 個文件,而是保留原來的 100 個文件的hardlink,僅僅寫入那一個新增的文件
  1. 支持Monorepo

pnpm 與 npm/yarn 一個很大的不同就是支持了 monorepo

  1. 安全性高

之前在使用 npm/yarn 的時候,由於 node_module 的扁平結構,如果 A 依賴 B, B 依賴 C,那麼 A 當中是可以直接使用 C 的,但問題是 A 當中並沒有聲明 C 這個依賴。因此會出現這種非法訪問的情況。但 pnpm自創了一套依賴管理方式,很好地解決了這個問題,保證了安全性

預設情況下,pnpm 則是通過使用符號鏈接的方式僅將項目的直接依賴項添加到node_modules的根目錄下。

安裝和初始化

  • 全局安裝(node版本>16)
npm install pnpm -g


  • 初始化
pnpm init


配置workspace

根目錄創建pnpm-workspace.yaml

packages:
  - 'packages/*'


將packages下所有的目錄都作為包進行管理。這樣我們的Monorepo就搭建好了。確實比 lerna + yarn workspace 更快捷

4、項目結構

packages

  • reactivity:響應式系統
  • runtime-core:與平臺無關的運行時核心 (可以創建針對特定平臺的運行時 - 自定義渲染器)
  • runtime-dom: 針對瀏覽器的運行時。包括DOM API,屬性,事件處理等
  • runtime-test:用於測試
  • server-renderer:用於伺服器端渲染
  • compiler-core:與平臺無關的編譯器核心
  • compiler-dom: 針對瀏覽器的編譯模塊
  • compiler-ssr: 針對服務端渲染的編譯模塊
  • template-explorer:用於調試編譯器輸出的開發工具
  • shared:多個包之間共用的內容
  • vue:完整版本,包括運行時和編譯器
                                    +---------------------+
                                    |                     |
                                    |  @vue/compiler-sfc  |
                                    |                     |
                                    +-----+--------+------+
                                          |        |
                                          v        v
                      +---------------------+    +----------------------+
                      |                     |    |                      |
        +------------>|  @vue/compiler-dom  +--->|  @vue/compiler-core  |
        |             |                     |    |                      |
   +----+----+        +---------------------+    +----------------------+
   |         |
   |   vue   |
   |         |
   +----+----+        +---------------------+    +----------------------+    +-------------------+
        |             |                     |    |                      |    |                   |
        +------------>|  @vue/runtime-dom   +--->|  @vue/runtime-core   +--->|  @vue/reactivity  |
                      |                     |    |                      |    |                   |
                      +---------------------+    +----------------------+    +-------------------+


scripts

Vue3在開發環境使用esbuild打包,生產環境採用rollup打包

包的相互依賴

安裝

把packages/shared安裝到packages/reactivity

pnpm install @vue/shared@workspace --filter @vue/reactivity

使用

在reactivity/src/computed.ts中引入shared中相關方法

import { isFunction, NOOP } from '@vue/shared' // ts引入會報錯

const onlyGetter = isFunction(getterOrOptions)
  if (onlyGetter) {
    ...
  } else {
    ...
  }
...


tips:@vue/shared引入會報錯,需要在tsconfig.json中配置

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@vue/compat": ["packages/vue-compat/src"],
      "@vue/*": ["packages/*/src"],
      "vue": ["packages/vue/src"]
    }
  },
}


5、打包

所有包的入口均為src/index.ts這樣可以實現統一打包.

• reactivity/package.json

{
  "name": "@vue/reactivity",
  "version": "3.2.45",
  "main": "index.js",
  "module":"dist/reactivity.esm-bundler.js",
  "unpkg": "dist/reactivity.global.js",
  "buildOptions": {
    "name": "VueReactivity",
    "formats": [
      "esm-bundler",
      "cjs",
      "global"
    ]
  }
}


• shared/package.json

{
    "name": "@vue/shared",
    "version": "3.2.45",
    "main": "index.js",
    "module": "dist/shared.esm-bundler.js",
    "buildOptions": {
        "formats": [
            "esm-bundler",
            "cjs"
        ]
    }
}


formats 為自定義的打包格式,有 esm-bundler 在構建工具中使用的格式、 esm-browser 在瀏覽器中使用的格式、 cjs 在node中使用的格式、 global 立即執行函數的格式

開發環境esbuild打包

開發時 執行腳本, 參數為要打包的模塊

"scripts": {
    "dev": "node scripts/dev.js reactivity -f global"
}


// Using esbuild for faster dev builds.
// We are still using Rollup for production builds because it generates
// smaller files w/ better tree-shaking.

// @ts-check
const { build } = require('esbuild')
const nodePolyfills = require('@esbuild-plugins/node-modules-polyfill')
const { resolve, relative } = require('path')
const args = require('minimist')(process.argv.slice(2))

const target = args._[0] || 'vue'
const format = args.f || 'global'
const inlineDeps = args.i || args.inline
const pkg = require(resolve(__dirname, `../packages/${target}/package.json`))

// resolve output
const outputFormat = format.startsWith('global')
  ? 'iife'
  : format === 'cjs'
  ? 'cjs'
  : 'esm'

const postfix = format.endsWith('-runtime')
  ? `runtime.${format.replace(/-runtime$/, '')}`
  : format

const outfile = resolve(
  __dirname,
  `../packages/${target}/dist/${
    target === 'vue-compat' ? `vue` : target
  }.${postfix}.js`
)
const relativeOutfile = relative(process.cwd(), outfile)

// resolve externals
// TODO this logic is largely duplicated from rollup.config.js
let external = []
if (!inlineDeps) {
  // cjs & esm-bundler: external all deps
  if (format === 'cjs' || format.includes('esm-bundler')) {
    external = [
      ...external,
      ...Object.keys(pkg.dependencies || {}),
      ...Object.keys(pkg.peerDependencies || {}),
      // for @vue/compiler-sfc / server-renderer
      'path',
      'url',
      'stream'
    ]
  }

  if (target === 'compiler-sfc') {
    const consolidateDeps = require.resolve('@vue/consolidate/package.json', {
      paths: [resolve(__dirname, `../packages/${target}/`)]
    })
    external = [
      ...external,
      ...Object.keys(require(consolidateDeps).devDependencies),
      'fs',
      'vm',
      'crypto',
      'react-dom/server',
      'teacup/lib/express',
      'arc-templates/dist/es5',
      'then-pug',
      'then-jade'
    ]
  }
}

build({
  entryPoints: [resolve(__dirname, `../packages/${target}/src/index.ts`)],
  outfile,
  bundle: true,
  external,
  sourcemap: true,
  format: outputFormat,
  globalName: pkg.buildOptions?.name,
  platform: format === 'cjs' ? 'node' : 'browser',
  plugins:
    format === 'cjs' || pkg.buildOptions?.enableNonBrowserBranches
      ? [nodePolyfills.default()]
      : undefined,
  define: {
    __COMMIT__: `"dev"`,
    __VERSION__: `"${pkg.version}"`,
    __DEV__: `true`,
    __TEST__: `false`,
    __BROWSER__: String(
      format !== 'cjs' && !pkg.buildOptions?.enableNonBrowserBranches
    ),
    __GLOBAL__: String(format === 'global'),
    __ESM_BUNDLER__: String(format.includes('esm-bundler')),
    __ESM_BROWSER__: String(format.includes('esm-browser')),
    __NODE_JS__: String(format === 'cjs'),
    __SSR__: String(format === 'cjs' || format.includes('esm-bundler')),
    __COMPAT__: String(target === 'vue-compat'),
    __FEATURE_SUSPENSE__: `true`,
    __FEATURE_OPTIONS_API__: `true`,
    __FEATURE_PROD_DEVTOOLS__: `false`
  },
  watch: {
    onRebuild(error) {
      if (!error) console.log(`rebuilt: ${relativeOutfile}`)
    }
  }
}).then(() => {
  console.log(`watching: ${relativeOutfile}`)
})



生產環境rollup打包

具體代碼參考rollup.config.mjs

build.js

二、Vue3中Reactivity模塊

1、vue3對比vue2的響應式變化

  • 在Vue2的時候使用defineProperty來進行數據的劫持, 需要對屬性進行重寫添加gettersetter性能差
  • 當新增屬性和刪除屬性時無法監控變化。需要通過$set$delete實現
  • 數組不採用defineProperty來進行劫持 (浪費性能,對所有索引進行劫持會造成性能浪費)需要對數組單獨進行處理

Vue3中使用Proxy來實現響應式數據變化。從而解決了上述問題

2、CompositionAPI

  • 在Vue2中採用的是OptionsAPI, 用戶提供的data,props,methods,computed,watch等屬性 (用戶編寫複雜業務邏輯會出現反覆橫跳問題)
  • Vue2中所有的屬性都是通過this訪問,this存在指向明確問題
  • Vue2中很多未使用方法或屬性依舊會被打包,並且所有全局API都在Vue對象上公開。Composition API對 tree-shaking 更加友好,代碼也更容易壓縮。
  • 組件邏輯共用問題, Vue2 採用mixins 實現組件之間的邏輯共用; 但是會有數據來源不明確,命名衝突等問題。 Vue3採用CompositionAPI 提取公共邏輯非常方便

簡單的組件仍然可以採用OptionsAPI進行編寫,compositionAPI在複雜的邏輯中有著明顯的優勢~。 reactivity 模塊中就包含了很多我們經常使用到的 API 例如:computed、reactive、ref、effect等

3、基本使用

const { effect, reactive } = VueReactivity
// console.log(effect, reactive);
const state = reactive({name: 'qpp', age:18, address: {city: '南京'}})
console.log(state.address);
effect(()=>{
    console.log(state.name)
})


4、reactive實現

import { mutableHandlers } from'./baseHandlers'; 
// 代理相關邏輯import{ isObject }from'./util';// 工具方法
export function reactive(target: object) {
  // if trying to observe a readonly proxy, return the readonly version.
  if (isReadonly(target)) {
    return target
  }
  return createReactiveObject(
    target,
    false,
    mutableHandlers,
    mutableCollectionHandlers,
    reactiveMap
  )
}
function createReactiveObject(target, baseHandler){
    if(!isObject(target)){
        return target;
    }
    ...
    const observed =new Proxy(target, baseHandler);
    return observed
}


baseHandlers

import { isObject, hasOwn, hasChanged } from"@vue/shared";
import { reactive } from"./reactive";
const get = createGetter();
const set = createSetter();
function createGetter(){
    return function get(target, key, receiver){
        // 對獲取的值進行放射
        const res = Reflect.get(target, key, receiver);
        console.log('屬性獲取',key)
        if(isObject(res)){// 如果獲取的值是對象類型,則返回當前對象的代理對象
            return reactive(res);
        }
        return res;
    }
}
function createSetter(){
    return function set(target, key, value, receiver){
        const oldValue = target[key];
        const hadKey =hasOwn(target, key);
        const result = Reflect.set(target, key, value, receiver);
        if(!hadKey){
            console.log('屬性新增',key,value)
        }else if(hasChanged(value, oldValue)){
            console.log('屬性值被修改',key,value)
        }
        return result;
    }
}
export const mutableHandlers ={
    get,// 當獲取屬性時調用此方法
    set// 當修改屬性時調用此方法
}


這裡我只選了對最常用到的get和set方法的代碼,還應該有 hasdeletePropertyownKeys 。這裡為了快速掌握核心流程就先暫且跳過這些代碼

5、effect實現

我們再來看effect的代碼,預設effect會立即執行,當依賴的值發生變化時effect會重新執行

export let activeEffect = undefined;
// 依賴收集的原理是 藉助js是單線程的特點, 預設調用effect的時候會去調用proxy的get,此時讓屬性記住
// 依賴的effect,同理也讓effect記住對應的屬性
// 靠的是數據結構 weakMap : {map:{key:new Set()}}
// 稍後數據變化的時候 找到對應的map 通過屬性出發set中effect
function cleanEffect(effect) {
    // 需要清理effect中存入屬性中的set中的effect 
    // 每次執行前都需要將effect只對應屬性的set集合都清理掉
    // 屬性中的set 依然存放effect
    let deps = effect.deps
    for (let i = 0; i < deps.length; i++) {
        deps[i].delete(effect)
    }
    effect.deps.length = 0;

}

// 創建effect時可以傳遞參數,computed也是基於effect來實現的,只是增加了一些參數條件而已
export function effect<T = any>(
    fn: () => T,
    options?: ReactiveEffectOptions    
){
    // 將用戶傳遞的函數編程響應式的effect
    const _effect = new ReactiveEffect(fn,options.scheduler);
    // 更改runner中的this
    _effect.run()
    const runner = _effect.run.bind(_effect);
    runner.effect = _effect; // 暴露effect的實例
    return runner// 用戶可以手動調用runner重新執行
}
export class ReactiveEffect {
    public active = true;
    public parent = null;
    public deps = []; // effect中用了哪些屬性,後續清理的時候要使用
    constructor(public fn,public scheduler?) { } // 你傳遞的fn我會幫你放到this上
    // effectScope 可以來實現讓所有的effect停止
    run() {
        // 依賴收集  讓熟悉和effect 產生關聯
        if (!this.active) {
            return this.fn();
        } else {
            try {
                this.parent = activeEffect
                activeEffect = this;
                cleanEffect(this); // vue2 和 vue3中都是要清理的 
                return this.fn(); // 去proxy對象上取值, 取之的時候 我要讓這個熟悉 和當前的effect函數關聯起來,稍後數據變化了 ,可以重新執行effect函數
            } finally {
                // 取消當前正在運行的effect
                activeEffect = this.parent;
                this.parent = null;
            }
        }
    }
    stop() {
        if (this.active) {
            this.active = false;
            cleanEffect(this);
        }
    }
}


在effect方法調用時會對屬性進行取值,此時可以進行依賴收集。

effect(()=>{
    console.log(state.name)
    // 執行用戶傳入的fn函數,會取到state.name,state.age... 會觸發reactive中的getter
    app.innerHTML = 'name:' + state.name + 'age:' + state.age + 'address' + state.address.city
    
})


6、依賴收集

核心代碼

// 收集屬性對應的effect
export function track(target, type, key){}// 觸發屬性對應effect執行
export function trigger(target, type, key){}

function createGetter(){
    return function get(target, key, receiver){
        const res = Reflect.get(target, key, receiver);
        // 取值時依賴收集
        track(target, TrackOpTypes.GET, key);
        if(isObject(res)){
            return reactive(res);
        }
        return res;
    }
}


function createSetter(){
    return function set(target, key, value, receiver){
        const oldValue = target[key];
        const hadKey =hasOwn(target, key);
        const result = Reflect.set(target, key, value, receiver);
        if(!hadKey){
            // 設置值時觸發更新 - ADD
            trigger(target, TriggerOpTypes.ADD, key);
        }else if(hasChanged(value, oldValue)){
             // 設置值時觸發更新 - SET
            trigger(target, TriggerOpTypes.SET, key, value, oldValue);
        }
        return result;
    }
}


track的實現

const targetMap = new WeakMap();
export function track(target: object, type: TrackOpTypes, key: unknown){
    if (shouldTrack && activeEffect) { // 上下文 shouldTrack = true
        let depsMap = targetMap.get(target);
        if(!depsMap){// 如果沒有map,增加map
            targetMap.set(target,(depsMap =newMap()));
        }
        let dep = depsMap.get(key);// 取對應屬性的依賴表
        if(!dep){// 如果沒有則構建set
            depsMap.set(key,(dep =newSet()));
        }
    
        trackEffects(dep, eventInfo)
    }
}

export function trackEffects(
  dep: Dep,
  debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
  //let shouldTrack = false
  //if (effectTrackDepth <= maxMarkerBits) {
   // if (!newTracked(dep)) {
     // dep.n |= trackOpBit // set newly tracked
     // shouldTrack = !wasTracked(dep)
    //}
  //} else {
    // Full cleanup mode.
  //  shouldTrack = !dep.has(activeEffect!)
  } 

  if (!dep.has(activeEffect!) {
    dep.add(activeEffect!)
    activeEffect!.deps.push(dep)
    //if (__DEV__ && activeEffect!.onTrack) {
    //  activeEffect!.onTrack({
    //    effect: activeEffect!,
    //    ...debuggerEventExtraInfo!
    //  })
   // }
  }
}


trigger實現

export function trigger(target, type, key){
    const depsMap = targetMap.get(target);
    if(!depsMap){
        return;
    }
    const run=(effects)=>{
        if(effects){ effects.forEach(effect=>effect()); }
    }
    // 有key 就找到對應的key的依賴執行
    if(key !==void0){
        run(depsMap.get(key));
    }
    // 數組新增屬性
    if(type == TriggerOpTypes.ADD){
        run(depsMap.get(isArray(target)?'length':'');
    }}


依賴關係

作者:京東物流 喬盼盼

來源:京東雲開發者社區 自猿其說Tech 轉載請註明來源


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

-Advertisement-
Play Games
更多相關文章
  • 無論你的夢想有多麼高遠,記住,一切皆有可能。 我們從前面的學習知道一個 React 組件不僅僅只包含 DOM 結構的,還應該樣式和 Javascript 邏輯的。這裡我們學習下如何構建 CSS 樣式。 1. 邏輯表示 JSX 中使用大括弧語法來包裹 JS 表達式(簡單邏輯代碼)。例如: { 1 + ...
  • 最近公司項目有個掃碼打開訂單付款的功能大概是這樣的(uniapp 項目) 微信支付暫且不說網上教程也很豐富 重點講講支付寶(吐槽下支付寶小程式審核,真是太慢了,一天只能審核大概3-4次 每次審核要耗時 好幾個小時) 基本開發思路是這樣的(vue/uni-app): 1.打開頁面前獲取傳入參數(onl ...
  • 前兩期講了小程式開發的準備工作以及前期需要如何調試,今天我們就來介紹下開發一個支付寶小程式頁面需要瞭解哪些信息。 一個小程式頁面的整體功能的構成離不開頁面展示(AXML)、頁面樣式(ACSS)以及頁面邏輯(JS)這三方面,下麵本文將從這三方面具體展開。 一、AXML(組件) AXML 頁面一般用來做 ...
  • 最近在一個大屏項目遇到一個需求:用戶可以通過一個按鈕,觸發頁面部分模塊全屏。通過以下API可以實現: Element.requestFullscreen()方法用於發出非同步請求使元素進入全屏模式。 且全屏狀態變化會觸發以下事件: fullscreenchange 事件會在瀏覽器進入或退出全屏模式後立 ...
  • 1、需求 使用Vue + Element UI 實現在列表的操作欄新增一個複製按鈕,複製當前行的數據可以打開新增彈窗後亦可以跳轉到新增頁面,本文實現為跳轉到新增頁面。 2、實現 1)列表頁 index.vue <el-table> <!-- 其他列 --> <el-table-column labe ...
  • ES6中的...(展開)語法是一種可以將數組或對象展開為函數參數或數組字面量的語法。它通常用於函數調用或數組字面量的展開。 在函數調用中,...可以將一個數組展開為函數的參數列表。例如: js複製代碼 function sum(a, b, c) { return a + b + c; } const ...
  • React實現視覺差效果緩動輪播 效果如下(圖片幀率低看起來有點卡頓,看個大概就行): 分享一下思路: 1.正常引入一個輪播組件(站在巨人肩膀省時省力),去除指示點、引導箭頭等不需要的元素,有些組件支持配置,不支持就手動覆蓋CSS樣式了 2.找到組件中用於顯示展示當前圖片的類名 3.添加transf ...
  • Node.js 是一個基於 Chrome V8 引擎的 JavaScript 運行環境,使用了一個事件驅動、非阻塞式 I/O 模型,讓 JavaScript 運行在服務端的開發平臺。 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...