鸿蒙harmony--TypeScript函数详解
新的一年开始了,每天给自己一个微笑,笑里有幸福,原谅生活中的不完美,宽容生活中的不容易,删除昨天的烦恼,开启今天的快乐!
目录
一,函数类型表达式
二,调用签名
三,泛型函数
3.1 约束条件
3.2 使用约束值
3.3 指定类型参数
3.4 如何编写良好的泛型函数
3.4.1 下推类型参数
3.4.2 使用更少的类型参数
3.4.3 类型参数应该出现两次
四,可选参数
4.1 回调中的可选参数
五,其他需要了解的类型
5.1 void
5.2 object
5.3 unknown
5.4 never
5.5 Function
六,剩余形参
七,剩余实参
八,参数解构
一,函数类型表达式
描述函数的最简单方法是使用函数类型表达式。在语法上类似于箭头函数。
function yuanZhen(yuanzhen1:(name:string,age:number)=>void){
yuanzhen1("袁震",30)
}
function yuanZhen2(name:string,age:number){
console.log(name+age)
}
yuanZhen(yuanZhen2)
yuanzhen1:(name:string,age:number)=>void 相当于一个参数为name,age类型为string,number的返回值为空的函数
注意,参数名称是必需的。函数类型 (string) => void
的意思是“一个带有名为 string
、类型为 any
的参数的函数”!
也可以使用类型别名来命名函数类型,这样使代码看起来更简洁
type yz = (name:string,age:number)=>void
function yuanZhen(yuanzhen1:yz){
yuanzhen1("袁震",30)
}
function yuanZhen2(name:string,age:number){
console.log(name+age)
}
yuanZhen(yuanZhen2)
二,调用签名
在 JavaScript 中,函数除了可调用之外还可以具有属性。但是,函数类型表达式语法不允许声明属性。如果想用属性描述可调用的东西,可以在对象类型中编写调用签名:
type YuanZhen = {
name:string
(age:number):boolean
}
function doSomething(yuan:YuanZhen){
yuan.name="袁震"
console.info("name="+yuan.name+"age="+yuan(30))
}
function yuanZhen2(age:number){
return age>30
}
yuanZhen2.name="袁震2"
doSomething(yuanZhen2)
注意,与函数类型表达式相比,语法略有不同 - 在参数列表和返回类型之间使用 :
而不是 =>
三,泛型函数
编写一个函数,其中输入的类型与输出的类型相关,或者两个输入的类型以某种方式相关。下面说一下返回数组第一个元素的函数:
function yz(arr:any[]){
return arr[0]
}
这个函数完成了它的工作,但不幸的是返回类型为 any
。如果函数返回数组元素的类型会更好。
在 TypeScript 中,当我们想要描述两个值之间的对应关系时,会使用泛型。我们通过在函数签名中声明一个类型参数来做到这一点:
function yz2<T>(arr:T[]):T|undefined{
return arr[0]
}
export default class HanShu{
test(){
console.log("s="+yz2([1,2,3]))
console.log("s="+yz2(["11","22","33"]))
}
}
3.1 约束条件
我们编写了一些泛型函数,可以处理任何类型的值。有时我们想关联两个值,但只能对某个值的子集进行操作。在这种情况下,我们可以使用约束来限制类型参数可以接受的类型种类。
编写一个返回两个值中较长者的函数。为此,我们需要一个 length
属性,它是一个数字。我们通过编写 extends
子句将类型参数限制为该类型:
function yz3<T extends {length:number}>(a:T,b:T){
if(a.length>b.length){
return a
}else {
return b
}
}
export default class HanShu{
test(){
//ok
yz3([1,2,3],[1,2,3,4])
//ok
yz3("111","2222")
//Argument of type 'number' is not assignable to parameter of type '{ length: number; }'.
yz3(1,2)
}
}
只有拥有length属性的参数可以传进来,对yz3(1, 2)
的调用被拒绝,因为 number
类型没有 .length
属性。
因为我们将 T
限制为 { length: number }
,所以我们可以访问 a
和 b
参数的 .length
属性。如果没有类型约束,我们将无法访问这些属性,因为这些值可能是没有长度属性的其他类型。
3.2 使用约束值
这是使用泛型约束时的一个常见错误:
function yz4<T extends {length:number}>(obj:T,min:number):T{
if(obj.length>= min){
return obj
}else {
//Type '{ length: number; }' is not assignable to type 'T'.
// '{ length: number; }' is assignable to the constraint of type 'T',
// but 'T' could be instantiated with a different subtype of constraint
// '{ length: number; }'. <ArkTSCheck>
return { length: min }
}
}
看起来这个函数似乎还可以,Type
被约束为 { length: number }
,并且该函数返回 Type
或与该约束匹配的值。
问题是该函数 promise 返回与传入相同类型的对象,而不仅仅是与约束匹配的某个对象。
也就是如果我传入了string,但是返回了{length:number},如果我调用string的其他方法就会报错。
3.3 指定类型参数
TypeScript 通常可以在泛型调用中推断出预期的类型参数,但并非总是如此。例如,假设你编写了一个函数来组合两个数组:
function combine<Type>(arr1: Type[], arr2: Type[]): Type[] {
return arr1.concat(arr2);
}
通常使用不匹配的数组调用此函数会出错:
//Type 'string' is not assignable to type 'number'.
const arr = combine([1, 2, 3], ["hello"]);
但是,如果你打算这样做,你可以手动指定 Type
:
const arr = combine<string | number>([1, 2, 3], ["hello"]);
3.4 如何编写良好的泛型函数
3.4.1 下推类型参数
function firstElement1<Type>(arr: Type[]) {
return arr[0];
}
function firstElement2<Type extends any[]>(arr: Type) {
return arr[0];
}
// a: number (good)
const a = firstElement1([1, 2, 3]);
// b: any (bad)
const b = firstElement2([1, 2, 3]);
乍一看,这些似乎相同,但 firstElement1
是编写此函数的更好方法。它推断的返回类型是 Type
,但 firstElement2
的推断返回类型是 any
,因为 TypeScript 必须使用约束类型来解析 arr[0]
表达式,而不是 “等待” 在调用期间解析元素。
3.4.2 使用更少的类型参数
function filter1<Type>(arr: Type[], func: (arg: Type) => boolean): Type[] {
return arr.filter(func);
}
function filter2<Type, Func extends (arg: Type) => boolean>(
arr: Type[],
func: Func
): Type[] {
return arr.filter(func);
}
我们创建了一个不关联两个值的类型参数 Func
。这总是一个危险信号,因为这意味着想要指定类型参数的调用者必须无缘无故地手动指定额外的类型参数。Func
没有做任何事情,只是让函数更难阅读和推断!
3.4.3 类型参数应该出现两次
function greet<Str extends string>(s: Str) {
console.log("Hello, " + s);
}
greet("world");
//更简单的方法
function greet(s: string) {
console.log("Hello, " + s);
}
类型参数用于关联多个值的类型。如果一个类型参数只在函数签名中使用一次,它就没有任何关系。这包括推断的返回类型;例如,如果 Str
是 greet
的推断返回类型的一部分,它将关联参数和返回类型,因此尽管在书面代码中只出现一次,但它会被使用两次。
四,可选参数
函数通常采用可变数量的参数。
我们可以通过使用 ?
将参数标记为可选来在 TypeScript 中对此进行建模:
function yz5(a?:number){
return 30
}
export default class HanShu{
test(){
yz5()
yz5(1)
}
}
尽管参数被指定为 number
类型,但 a 参数实际上将具有 number | undefined
类型,因为 JavaScript 中未指定的参数获取值 undefined
。
还可以提供参数默认值:
function yz6(a=10){
return 30
}
export default class HanShu{
test(){
yz6()
yz6(20)
}
}
现在在yz6 的主体中,a将具有 number
类型,因为任何 undefined
参数都将被 10
替换。请注意,当参数是可选的时,调用者始终可以传递 undefined
4.1 回调中的可选参数
一旦你了解了可选参数和函数类型表达式,在编写调用回调的函数时很容易犯以下错误:
function myForEach(arr: any[], callback: (arg: any, index?: number) => void) {
for (let i = 0; i < arr.length; i++) {
callback(arr[i], i);
}
}
人们在编写 index?
作为可选参数时通常的意图是他们希望这两个调用都是合法的:
myForEach([1, 2, 3], (a) => console.log(a));
myForEach([1, 2, 3], (a, i) => console.log(a, i));
这实际上意味着 callback
可能会被一个参数调用。换句话说,函数定义表明实现可能如下所示:
function myForEach(arr: any[], callback: (arg: any, index?: number) => void) {
for (let i = 0; i < arr.length; i++) {
// I don't feel like providing the index today
callback(arr[i]);
}
}
反过来,TypeScript 将强制执行此含义并触发实际上不可能的错误:
myForEach([1, 2, 3], (a, i) => {
console.log(i.toFixed());
//'i' is possibly 'undefined'.
});
在 JavaScript 中,如果你调用一个参数多于参数的函数,多余的参数将被忽略。TypeScript 的行为方式相同。具有较少参数(相同类型)的函数总是可以代替具有更多参数的函数。
注意:为回调编写函数类型时,切勿编写可选参数,除非你打算在不传递该参数的情况下调用该函数
五,其他需要了解的类型
5.1 void
void
表示不返回值的函数的返回值。只要函数没有任何 return
语句,或者没有从这些返回语句返回任何显式值,它就是推断类型:
function noop() {
return;
}
5.2 object
特殊类型 object
指的是任何非基础值(string
、number
、bigint
、boolean
、symbol
、null
或 undefined
)。这与空对象类型 { }
不同,也与全局类型 Object
不同。你很可能永远不会使用 Object
。
请注意,在 JavaScript 中,函数值是对象:它们有属性,在它们的原型链中有 Object.prototype
,是 instanceof Object
,你可以在它们上调用 Object.keys
,等等。因此,函数类型在 TypeScript 中被视为 object
。
5.3 unknown
unknown
类型代表任何值。这类似于 any
类型,但更安全,因为使用 unknown
值做任何事情都是不合法的:
function f1(a: any) {
a.b(); // OK
}
function f2(a: unknown) {
a.b();
//'a' is of type 'unknown'.
}
这在描述函数类型时很有用,因为你可以描述接受任何值而不在函数体中包含 any
值的函数。
相反,你可以描述一个返回未知类型值的函数:
function safeParse(s: string): unknown {
return JSON.parse(s);
}
// Need to be careful with 'obj'!
const obj = safeParse(someRandomString);
5.4 never
有些函数从不返回值:
function fail(msg: string): never {
throw new Error(msg);
}
never
类型表示从未观察到的值。在返回类型中,这意味着函数抛出异常或终止程序的执行。
当 TypeScript 确定联合中没有任何内容时,never
也会出现。
function fn(x: string | number) {
if (typeof x === "string") {
// do something
} else if (typeof x === "number") {
// do something else
} else {
x; // has type 'never'!
}
}
5.5 Function
全局类型 Function
描述了 bind
、call
、apply
等属性,以及 JavaScript 中所有函数值上的其他属性。它还具有 Function
类型的值始终可以被调用的特殊属性;这些调用返回 any
:
function doSomething(f: Function) {
return f(1, 2, 3);
}
这是一个无类型的函数调用,通常最好避免,因为不安全的 any
返回类型。
如果你需要接受任意函数但不打算调用它,则类型 () => void
通常更安全。
六,剩余形参
除了使用可选参数或重载来制作可以接受各种固定参数计数的函数之外,我们还可以使用剩余参数定义接受无限数量参数的函数。
剩余参数出现在所有其他参数之后,并使用 ...
语法:
function multiply(n: number, ...m: number[]) {
return m.map((x) => n * x);
}
// 'a' gets value [10, 20, 30, 40]
const a = multiply(10, 1, 2, 3, 4);
在 TypeScript 中,这些参数上的类型注释隐式为 any[]
而不是 any
,并且给出的任何类型注释必须采用 Array<T>
或 T[]
形式,或者元组类型
七,剩余实参
我们可以使用扩展语法从可迭代对象(例如数组)中提供可变数量的参数。例如,数组的 push
方法接受任意数量的参数:
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
arr1.push(...arr2);
八,参数解构
你可以使用参数解构来方便地将作为参数提供的对象解包到函数体中的一个或多个局部变量中。在 JavaScript 中,它看起来像这样:
function sum({ a, b, c }) {
console.log(a + b + c);
}
sum({ a: 10, b: 3, c: 9 });
对象的类型注释遵循解构语法:
function sum({ a, b, c }: { a: number; b: number; c: number }) {
console.log(a + b + c);
}
这看起来有点冗长,但你也可以在此处使用命名类型:
// Same as prior example
type ABC = { a: number; b: number; c: number };
function sum({ a, b, c }: ABC) {
console.log(a + b + c);
}