OpenTiny 跨端、跨框架組件庫升級TypeScript,10萬行代碼重獲新生

来源:https://www.cnblogs.com/huaweiyun/archive/2023/04/10/17302482.html
-Advertisement-
Play Games

摘要:一份精心準備的《JS項目改造TS指南》文檔供大家參考,順便介紹TS 基礎知識和 TS 在 Vue 中的實踐。 本文分享自華為雲社區《歷史性的時刻!OpenTiny 跨端、跨框架組件庫正式升級 TypeScript,10 萬行代碼重獲新生!》,作者:Kagol。 根據 The Software ...


摘要:一份精心準備的《JS項目改造TS指南》文檔供大家參考,順便介紹TS 基礎知識和 TS 在 Vue 中的實踐。

本文分享自華為雲社區《歷史性的時刻!OpenTiny 跨端、跨框架組件庫正式升級 TypeScript,10 萬行代碼重獲新生!》,作者:Kagol。

根據 The Software House 發佈的《2022 前端開發市場狀態調查報告》數據顯示,使用 TypeScript 的人數已經達到 84%,和 2021 年相比增加了 7 個百分點。

3月16日發佈了 TypeScript 5.0 版本。TypeScript 可謂逐年火熱,使用者呈現逐年上升的趨勢,再不學起來就說不過去。

我們 OpenTiny 近期做了一次大的升級,將原來運行了 9年 的 JavaScript 代碼升級到了 TypeScript,並通過 Monorepo 進行子包的管理,還在用 JavaScript 的朋友抓緊升級哦,我特意準備了一份《JS項目改造TS指南》文檔供大家參考,順便介紹了一些 TS 基礎知識和 TS 在 Vue 中的一些實踐。

通過本文你將收穫:

  • 通過瞭解 TS 的四大好處,說服自己下定決心學習 TS
  • 5 分鐘學習 TS 最基礎和常用的知識點,快速入門,包教包會
  • 瞭解如何在 Vue 中使用 TypeScript,給 Vue2 開發者切換到 Vue3 + TypeScript 提供最基本的參考
  • 如何將現有的 JS 項目改造成 TS 項目

1 學習 TS 的好處

1.1 好處一:緊跟潮流:讓自己看起來很酷

如果你沒學過 TS
你的前端朋友:都 2023 年了,你還不會 TS?給你一個眼色你自己感悟吧

如果你學過 TS
你的前端朋友:哇,你們的項目已經用上 Vue3 + TS 啦,看起來真棒!教教我吧

如果說上面那個好處太虛了,那下麵的3條好處可都是實實在在能讓自己受益的。

1.2 好處二:智能提示:提升開發者體驗和效率

當迴圈一個對象數組時,對象的屬性列表可以直接顯示出來,不用到對象的定義中去查詢該對象有哪些屬性。

通過調用後臺介面獲取的非同步數據也可以通過TS類型進行智能提示,這樣相當於集成了介面文檔,後續後臺修改欄位,我們很容易就能發現。

Vue 組件的屬性和事件都可以智能提示。

下圖是我們OpenTiny跨端跨框架前端組件庫中的 Alert 組件,當在組件標簽中輸入 des 時,會自動提示 description 屬性;當輸入 @c 時,會自動提示 @close 事件。

1.3 好處三:錯誤標記:代碼哪裡有問題一眼就知道

在 JS 項目使用不存在的對象屬性,在編碼階段不容易看出來,到運行時才會報錯。

在 TS 項目使用不存在的對象屬性,在IDE中會有紅色波浪線標記,滑鼠移上去能看到具體的錯誤信息。

在 JS 項目,調用方法時拼錯單詞不容易被髮現,要在運行時才會將錯誤暴露出來。

在 TS 項目會有紅色波浪線提示,一眼就看出拼錯單詞。

1.4 好處四:類型約束:用我的代碼就得聽我的

你寫了一個工具函數 getType 給別人用,限定參數只能是指定的字元串,這時如果使用這個函數的人傳入其他字元串,就會有紅色波浪線提示。

Vue 組件也是一樣的,可以限定組件 props 的類型,組件的使用者如果傳入不正確的類型,將會有錯誤提示,比如:我們 OpenTiny 的 Alert 組件,closable 只能傳入 Boolean 值,如果傳入一個字元串就會有錯誤提示。

2 極簡 TS 基礎,5分鐘學會

以下內容雖然不多,但包含了實際項目開發中最實用的部分,對於 TS 入門者來說也是能很快學會的,學不會的找我,手把手教,包教包會,有手就會寫。

2.1 基本類型

用得較多的類型就下麵5個,更多類型請參考:TS官網文檔

  • 布爾 boolean
  • 數值 number
  • 字元串 string
  • 空值 void:表示沒有任何返回值的函數
  • 任意 any:表示不被類型檢查

用法也很簡單:

let isDone: boolean = false;
let myFavoriteNumber: number = 6;
let myName: string = 'Kagol';
function alertName(name: string): void { 
 console.log(`My name is ${name}`); 
}

預設情況下,name 會自動類型推導成 string 類型,此時如果給它賦值為一個 number 類型的值,會出現錯誤提示。

let name = 'Kagol' 
name = 6

如果給 name 設置 any 類型,表示不做類型檢查,這時錯誤提示消失。

let name: any = 'Kagol' 
name = 6

2.2 函數

主要定義函數參數和返回值類型。

看一下例子:

const sum = (x: number, y: number): number => { 
 return x + y  
}

以上代碼包含以下 TS 校驗規則:

  • 調用 sum 函數時,必須傳入兩個參數,多一個或者少一個都不行
  • 並且這兩個參數的類型要為 number 類型
  • 且函數的返回值為 number 類型

少參數:

多參數:

參數類型錯誤:

返回值:

用問號 ? 可以表示該參數是可選的。

const sum = (x: number, y?: number): number => { 
 return x + (y || 0); 
} 
sum(1) 

如果將 y 定義為可選參數,則調用 sum 函數時可以只傳入一個參數。

需要註意的是,可選參數必須接在必需參數後面。換句話說,可選參數後面不允許再出現必需參數了。

給 y 增加預設值 0 之後,y 會自動類型推導成 number 類型,不需要加 number 類型,並且由於有預設值,也不需要加可選參數。

const sum = (x: number, y = 0): number => { 
 return x + y  
}
sum(1) 
sum(1, 2) 

2.3 數組

數組類型有兩種表示方式:

  • 類型 + 方括弧 表示法
  • 泛型 表示法
// `類型 + 方括弧` 表示法
let fibonacci: number[] = [1, 1, 2, 3, 5]
// 泛型表示法
let fibonacci: Array<number> = [1, 1, 2, 3, 5]

這兩種都可以表示數組類型,看自己喜好進行選擇即可。

如果是類數組,則不可以用數組的方式定義類型,因為它不是真的數組,需要用 interface 進行定義

interface IArguments {
 [index: number]: any;
  length: number;
 callee: Function;
}
function sum() {
 let args: IArguments = arguments
}

IArguments 類型已在 TypeScript 中內置,類似的還有很多:

let body: HTMLElement = document.body;
let allDiv: NodeList = document.querySelectorAll('div');
document.addEventListener('click', function(e: MouseEvent) {
 // Do something
});

如果數組裡的元素類型並不都是相同的怎麼辦呢?

這時 any 類型就發揮作用啦啦

let list: any[] = ['OpenTiny', 112, { website: 'https://opentiny.design/' }];

2.4 介面

介面簡單理解就是一個對象的“輪廓”

interface IResourceItem {
  name: string;
 value?: string | number;
 total?: number;
 checked?: boolean;
}

介面是可以繼承介面的

interface IClosableResourceItem extends IResourceItem {
 closable?: boolean;
}

這樣 IClosableResourceItem 就包含了 IResourceItem 屬性和自己的 closable 可選屬性。

介面也是可以被類實現的

interface Alarm {
 alert(): void;
}
class Door {
}
class SecurityDoor extends Door implements Alarm {
 alert() {
 console.log('SecurityDoor alert')
 }
}

如果類實現了一個介面,卻不寫具體的實現代碼,則會有錯誤提示

2.5 聯合類型 & 類型別名

聯合類型是指取值可以為多種類型中的一種,而類型別名常用於聯合類型。

看以下例子:

// 聯合類型
let myFavoriteNumber: string | number
myFavoriteNumber = 'six'
myFavoriteNumber = 6
// 類型別名
type FavoriteNumber = string | number
let myFavoriteNumber: FavoriteNumber

當 TypeScript 不確定一個聯合類型的變數到底是哪個類型的時候,我們只能訪問此聯合類型的所有類型里共有的屬性或方法:

function getLength(something: string | number): number {
 return something.length
}
// index.ts(2,22): error TS2339: Property 'length' does not exist on type 'string | number'.
//   Property 'length' does not exist on type 'number'.
上例中,length 不是 string 和 number 的共有屬性,所以會報錯。
訪問 string 和 number 的共有屬性是沒問題的:
function getString(something: string | number): string {
 return something.toString()
}

2.6 類型斷言

類型斷言(Type Assertion)可以用來手動指定一個值的類型。

語法:值 as 類型,比如:(animal as Fish).swim()

類型斷言主要有以下用途:

  • 將一個聯合類型斷言為其中一個類型
  • 將一個父類斷言為更加具體的子類
  • 將任何一個類型斷言為 any
  • 將 any 斷言為一個具體的類型

我們一個個來看。

用途1:將一個聯合類型斷言為其中一個類型

interface Cat {
  name: string;
 run(): void;
}
interface Fish {
  name: string;
 swim(): void;
}
const animal: Cat | Fish = new Animal()
animal.swim()

animal 是一個聯合類型,可能是貓 Cat,也可能是魚 Fish,如果直接調用 swim 方法是要出現錯誤提示的,因為貓不會游泳。

這時類型斷言就派上用場啦啦,因為調用的是 swim 方法,那肯定是魚,所以直接斷言為 Fish 就不會出現錯誤提示。

const animal: Cat | Fish = new Animal()
(animal as Fish).swim()

用途2:將一個父類斷言為更加具體的子類

class ApiError extends Error {
  code: number = 0;
}
class HttpError extends Error {
 statusCode: number = 200;
}
function isApiError(error: Error) {
 if (typeof (error as ApiError).code === 'number') {
 return true;
 }
 return false;
}

ApiError 和 HttpError 都繼承自 Error 父類,error 變數的類型是 Error,去取 code 變數肯定是不行,因為取的是 code 變數,我們可以直接斷言為 ApiError 類型。

用途3:將任何一個類型斷言為 any

這個非常有用,看一下例子:

function getCacheData(key: string): any {
 return (window as any).cache[key];
}
interface Cat {
  name: string;
 run(): void;
}
const tom = getCacheData('tom') as Cat;

getCacheData 是一個歷史遺留函數,不是你寫的,由於他返回 any 類型,就等於放棄了 TS 的類型檢驗,假如 tom 是一隻貓,裡面有 name 屬性和 run() 方法,但由於返回 any 類型,tom. 是沒有任何提示的。

如果將其斷言為 Cat 類型,就可以 點 出 name 屬性和 run() 方法。

用途4:將 any 斷言為一個具體的類型

這個比較常見的場景是給 window 掛在一個自己的變數和方法。

window.foo = 1;
// index.ts:1:8 - error TS2339: Property 'foo' does not exist on type 'Window & typeof globalThis'.
(window as any).foo = 1;

由於 window 下沒有 foo 變數,直接賦值會有錯誤提示,將 window 斷言為 any 就沒問題啦啦。

2.7 元組

數組合併了相同類型的對象,而元組(Tuple)合併了不同類型的對象。

let tom: [string, number] = ['Tom', 25];

給元組類型賦值時,數組每一項的類型需要和元組定義的類型對應上。

當賦值或訪問一個已知索引的元素時,會得到正確的類型:

let tom: [string, number];
tom[0] = 'Tom';
tom[1] = 25;
tom[0].slice(1);
tom[1].toFixed(2);

也可以只賦值其中一項:

let tom: [string, number];
tom[0] = 'Tom';

但是當直接對元組類型的變數進行初始化或者賦值的時候,需要提供所有元組類型中指定的項。

let tom: [string, number];
tom = ['Tom'];
// Property '1' is missing in type '[string]' but required in type '[string, number]'.

當添加越界的元素時,它的類型會被限製為元組中每個類型的聯合類型:

let tom: [string, number];
tom = ['Tom', 25];
tom.push('male');
tom.push(true);
// Argument of type 'true' is not assignable to parameter of type 'string | number'.

push 字元串和數字都可以,布爾就不行。

2.8 枚舉

枚舉(Enum)類型用於取值被限定在一定範圍內的場景,比如一周只能有七天,顏色限定為紅綠藍等。

enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat}

枚舉成員會被賦值為從 0 開始遞增的數字,同時也會對枚舉值到枚舉名進行反向映射:

console.log(Days.Sun === 0) // true
console.log(Days[0] === 'Sun') // true
console.log('Days', Days)

手動賦值:未手動賦值的枚舉項會接著上一個枚舉項遞增。

enum Days {Sun = 7, Mon = 1, Tue, Wed, Thu, Fri, Sat}

2.9 類

給類加上 TypeScript 的類型很簡單,與介面類似:

class Animal {
  name: string
 constructor(name: string) {
 this.name = name
 }
 sayHi(welcome: string): string {
 return `${welcome} My name is ${this.name}`
 }
}

類的語法涉及到較多概念,請參考:

2.10 泛型

泛型(Generics)是指在定義函數、介面或類的時候,不預先指定具體的類型,而在使用的時候再指定類型的一種特性。

可以簡單理解為定義函數時的形參。

設想以下場景,我們有一個 print 函數,輸入什麼,原樣列印,函數的入參和返回值類型是一致的。

一開始只需要列印字元串:

function print(arg: string): string {
 return arg
}

後面需求變了,除了能列印字元串,還要能列印數字:

function print(arg: string | number): string | number {
 return arg
}

假如需求又變了,要列印布爾值、對象、數組,甚至自定義的類型,怎麼辦,寫一串聯合類型?顯然是不可取的,用 any?那就失去了 TS 類型校驗能力,淪為 JS。

function print(arg: any): any {
  return arg
}

解決這個問題的完美方法就是泛型!

print 後面加上一對尖括弧,裡面寫一個 T,這個 T 就類似是一個類型的形參。

這個類型形參可以在函數入參里用,也可以在函數返回值使用,甚至也可以在函數體裡面的變數、函數裡面用。

function print<T>(arg: T): T {
 return arg
}

那麼實參哪裡來?用的時候傳進來!

const res = print<number>(123)

我們還可以使用泛型來約束後端介面參數類型。

import axios from 'axios'
interface API {
 '/book/detail': {
      id: number,
 },
 '/book/comment': {
      id: number
      comment: string
 }
 ...
}
function request<T extends keyof API>(url: T, obj: API[T]) {
 return axios.post(url, obj)
}
request('/book/comment', {
  id: 1,
  comment: '非常棒!'
})

以上代碼對介面進行了約束:

  • url 只能是 API 中定義過的,其他 url 都會提示錯誤
  • 介面參數 obj 必須和 url 能對應上,不能少屬性,屬性類型也不能錯

而且調用 request 方法時,也會提示 url 可以選擇哪些

如果後臺改了介面參數名,我們一眼就看出來了,都不用去找介面文檔,是不是很厲害!

泛型的例子參考了前端阿林的文章:

  • 輕鬆拿下 TS 泛型

3 TS 在 Vue 中的實踐

3.1 定義組件 props 的類型

不使用 setup 語法糖

export default defineComponent({
  props: {
    items: {
      type: Object as PropType<IResourceItem[]>,
 default() {
 return []
 }
 },
    span: {
      type: Number,
 default: 4
 },
    gap: {
      type: [String, Number] as PropType<string | number>,
 default: '12px'
 },
    block: {
      type: Object as PropType<Component>,
 default: TvpBlock
 },
 beforeClose: Function as PropType<() => boolean>
 }
})

使用 setup 語法糖 – runtime 聲明

import { PropType, Component } from 'vue'
const props = defineProps({
  items: {
    type: Object as PropType<IResourceItem[]>,
 default() {
 return []
 }
 },
  span: {
    type: Number,
 default: 4
 },
  gap: {
    type: [String, Number] as PropType<string | number>,
 default: '12px'
 },
  block: {
    type: Object as PropType<Component>,
 default: TvpBlock
 },
 beforeClose: Function as PropType<() => boolean>
})

使用 setup 語法糖 – type-based 聲明

import { Component, withDefaults } from 'vue'
interface Props {
  items: IResourceItem[]
  span: number
  gap: string | number
  block: Component
 beforeClose: () => void
}
const props = withDefaults(defineProps<Props>(), {
 items: () => [],
  span: 4,
  gap: '12px',
  block: TvpBlock
})
IResourceItem:
interface IResourceItem {
  name: string;
 value?: string | number;
 total?: number;
 checked?: boolean;
 closable?: boolean;
}

3.2 定義 emits 類型

不使用 setup 語法糖

export default defineComponent({
  emits: ['change', 'update'],
 setup(props, { emit }) {
 emit('change')
 }
})

使用 setup 語法糖

<script setup lang="ts">
// runtime
const emit = defineEmits(['change', 'update'])
// type-based
const emit = defineEmits<{
 (e: 'change', id: number): void
 (e: 'update', value: string): void
}>()
</script>

3.3 定義 ref 類型

預設會自動進行類型推導

import { ref } from 'vue'
// inferred type: Ref<number>
const year = ref(2020)
// => TS Error: Type 'string' is not assignable to type 'number'.
year.value = '2020'

兩種聲明 ref 類型的方法

import { ref } from 'vue'
import type { Ref } from 'vue'
// 方式一
const year: Ref<string | number> = ref('2020')
year.value = 2020 // ok!
// 方式二
// resulting type: Ref<string | number>
const year = ref<string | number>('2020')
year.value = 2020 // ok!

3.4 定義 reactive 類型

預設會自動進行類型推導

import { reactive } from 'vue'
// inferred type: { title: string }
const book = reactive({ title: 'Vue 3 Guide' })

使用介面定義明確的類型

import { reactive } from 'vue'
interface Book {
  title: string
 year?: number
}
const book: Book = reactive({ title: 'Vue 3 Guide' })

3.5 定義 computed 類型

預設會自動進行類型推導

import { ref, computed } from 'vue'
const count = ref(0)
// inferred type: ComputedRef<number>
const double = computed(() => count.value * 2)
// => TS Error: Property 'split' does not exist on type 'number'
const result = double.value.split('')

兩種聲明 computed 類型的方法

import { ComputedRef, computed } from 'vue'
const double: ComputedRef<number> = computed(() => {
 // type error if this doesn't return a number
})
const double = computed<number>(() => {
 // type error if this doesn't return a number
})

3.6 定義 provide/inject 類型

provide

import { provide, inject } from 'vue'
import type { InjectionKey } from 'vue'
// 聲明 provide 的值為 string 類型
const key = Symbol() as InjectionKey<string>
provide(key, 'foo') // providing non-string value will result in error

inject

// 自動推導為 string 類型
const foo = inject(key) // type of foo: string | undefined
// 明確指定為 string 類型
const foo = inject<string>('foo') // type: string | undefined
// 增加預設值
const foo = inject<string>('foo', 'bar') // type: string
// 類型斷言為 string
const foo = inject('foo') as string

3.7 定義模板引用的類型

<script setup lang="ts">
import { ref, onMounted } from 'vue'
const el = ref<HTMLInputElement | null>(null)
onMounted(() => {
 el.value?.focus()
})
</script>
<template>
 <input ref="el" />
</template>

3.8 定義組件模板引用的類型

定義一個 MyModal 組件

<!-- MyModal.vue -->
<script setup lang="ts">
import { ref } from 'vue'
const isContentShown = ref(false)
const open = () => (isContentShown.value = true)
defineExpose({
  open
})
</script>

在 App.vue 中引用 MyModal 組件

<!-- App.vue -->
<script setup lang="ts">
import MyModal from './MyModal.vue'
const modal = ref<InstanceType<typeof MyModal> | null>(null)
const openModal = () => {
 modal.value?.open()
}
</script>

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

-Advertisement-
Play Games
更多相關文章
  • pg Extended Query PostgreSQL: Documentation: 15: 55.2. Message Flow 多個階段,可復用 Parse → DESCRIBE statement → SYNC Parse 解析, 將 sql 文本字元串,解析成 named prepare ...
  • 摘要:相比於傳統的微服務架構,雲原生和 serverless 技術更加靈活、高效,能夠更好地滿足用戶的需求。 本文分享自華為雲社區《《鳳凰架構》學習和思考——雲原生時代的服務架構演進史》,作者:breakDawn。 隨著雲原生的概念越來越火,服務的架構應該如何發展和演進,成為很多程式員關心的話題。大 ...
  • Q1:==springboot項目,如何使用elasticsearch的api增刪改查?查詢中有哪些方式,如果模糊查詢、排序查詢、分頁查詢?分別闡述下這些查詢方式的用法?最後舉一個完整的例子== 答: 在Spring Boot項目中使用Elasticsearch的API增刪改查,需要引入spring ...
  • GreatSQL社區月報 | 2023.03 GreatSQL 是一個開源的 MySQL 技術路線資料庫社區,社區致力於通過開放的社區合作,構建國內自主 MySQL 版本及開源資料庫技術,推動中國開源資料庫及應用生態繁榮發展。 為了幫助社區的小伙伴們更好地瞭解 GreatSQL 社區的實時進展,我們 ...
  • 前端工程化實戰是指通過組織工作流程、使用工具和技術來提高前端開發效率和質量的一種方法。常見的前端工程化實踐包括模塊化開發、自動化構建、代碼檢查和測試、性能優化等。下麵將簡要介紹模塊化開發、性能優化和組件化實踐。 ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 前言 我們公司目前在做基於tiptap的線上協同文檔,最近需要做導出 pdf、word 需求。 導出 word 文檔使用的是html-docx-js-typescript,是用 typescript 重寫了一下html-docx-js,可 ...
  • 其他章節請看: 低代碼 系列 中後臺集成低代碼預研 背景 筆者目前維護一個 react 中後臺系統(以 spug 為例),每次來了新的需求都需要前端人員重新開發。 前面我們已經對低代碼有了一定的認識,如果能通過一個可視化的配置頁面就能完成前端開發,將極大的提高前端(或後端)的效率。甚至能加快企業內部 ...
  • 在前面介紹的隨筆《基於SqlSugar的開發框架循序漸進介紹(7)-- 在文件上傳模塊中採用選項模式【Options】處理常規上傳和FTP文件上傳》中介紹過在文件上傳處理的過程中,整合了本地文件上傳和基於FTP方式的上傳文件的處理整合。本篇隨筆繼續介紹文件上傳的處理,基於選項模式【Options】方... ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...