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
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...