C#和TS的基础语法热身后,在深入语言之前,有必要搞清楚C#和TS代码组织方式。C#和TS都是面向对象语言,但前者是类型语言,所有代码都必须包含在类中(此处类是一个广义概念,它还包括结构、枚举、接口等);后者是函数式语言,函数可以作为值传递和返回,有全局作用域,代码不需要包含在类中。这种差异,导致两者的代码组织方式,存在较大差异。
一、C#的代码组织方式-命名空间
- C#的代码组织方式有两个层面:**一是文件层面,从里到外分别为“文件>文件夹>项目>解决方案”,这个层面只是文件的组织形式,并不是代码的组织形式。二是代码层面,从里到外分别为“类class(此处类也是一个广义概念)> 命名空间namespace > 程序集assembly。这是代码的实际组织形式,一个程序集里面有多个命名空间,一个命名空间里有很多类,一个类必须属于某个命名空间,一个命名空间必须属于某个程序集。
- 两个层面的关系:**是并行的,没有必然联系。比如通常一个项目就是一个程序集,一个文件夹就是一个命名空间,一个文件就是一个类。但是,一个文件里也可以定义多个类,一个类在某个文件夹中,但它却可能属于另一个命名空间,一个文件夹也可以不是一个命名空间。实际操作中,反而建议将两者耦合起来,即一个项目对应一个程序集,一个文件夹对应一个命名空间,一个文件对应一个类,不要给自己找麻烦。例外情况,如果多个类的关联性非常强,比如DTO及其验证扩展类,可以考虑放在一个文件中,这样便于管理。
- 引入外部资源:**在依赖项中,引用同一个解决方案的不同项目,或引用第三方nuget类库,或引用本地程序集文件(DLL),然后使用using引用相应的命名空间,即可使用这个命名空间下的类型。
1.1 文件层面的结构
1.2 代码层面的结构
二、TS/JS/UTS的代码组织-模块化
- 模块化:**任何包含 export 语句的文件,就是一个模块(module)。相应地,如果文件不包含 export 语句,就是一个全局的脚本文件,代码会合并到全局作用域中。模块内的代码,在一个独立的作用域内。模块内部的变量、函数、类等元素,默认只在内部可见,如果需要暴露给外部使用,必须用 export 命令导出,其它模块使用 import 命令来导入。
- 目前存在两个模块化方案,ES6模块化和CommonJS模块化。uniapp和uniapp-x使用ES6(前端基本都使用ES6),unicloud使用CommonJS(unicloud的后端的nodejs,目前nodejs对CommonJS支持更好)。
- 如何引入外部模块:方法一:将需要的模块文件直接复制到当前项目中(模块需要export相关接口);方法二:安装第三方的npm包。然后使用import(ES6模块化)或者require(CommonJS模块化)引入,即可使用该模块中导出的元素。
- 命名空间:TS中还有命名空间的特性,但使用模块化后,不建议再使用。
2.1 ES6模块化
//a.uts,js和ts一样
export const a = 1 //导出常量
export let b = 1 //导出变量
export function myFunction(){} //导出函数
export class MyClass{} //导出类
export type MyType{name:string,age:number} //导出type
export interface IMyInterface{name:string,getName():void} //导出接口
export default const c = {name:"MC",age:28} //默认导出
//b.uts, js和ts一样
import {a,b,myFunction} from "./a.uts" //解构导入
import {MyClass as OtherClass} from "./a.uts" //解构导入并重命名
import MyC from "./a.uts" //导入默认,这里导入的是c,导入时可以重命名
import * as All from "./a.uts" // 导入所有,并挂在All对象上,比如通过All.a访问a
export const d = 10 //在一个文件里,即可以导入,也可以导出
//vue中使用模块化的案例
import axios from 'axios'; //安装了axios的npm包后,导入axios,注意路径格式
import { getToken } from '@/utils/auth' //@表示src文件夹,需要在vite.config中设置
//如果一个文件不包含 export 语句,但是希望把它当作一个模块,可以在脚本头部添加一行语句
export {};
2.2 CommonJS模块化
//在node.js环境下,没有type和interface
//a1.js,导出单个值,使用module.export
module.export = 100 //导出单个值
//b1.js
const a = require("./a1.js")
//a2.js,导出单个值,使用export.XXX
export.add = ()=>{} //导出单个值
//b2.js
const add = require("./a1.js")
//a3.js,导出多个值,使用module.export
const a = 1
const b = {name:"MC",age:28}
function myFunction(){}
class MyClass{}
module.export = {a,b,myFunction,Myclass} //使用module.exports导出
//b3.js
const {a,b,myFunction,Myclass} = require("./a3.js")
2.3 类型声明
第三方库,无论是JS,还是TS,最后都会被编译成JS。而要在TS中使用,就需要借助类型申明。一般类型声明在单独的声明文件中,通常命名为*.d.ts。正常情况下,应用开发很少接触类型声明,因为绝大多数第三方库,都提供了类型声明文件。有以下几种情况:
- 首先会找三方库本身提供的d.ts类型申明文件,如node_modules/jquery/index.d.ts。或者在package.json文件中配置,如"types": “xxx.d.ts”
- 如果三方库本身没有提供,可以安装社区提供的声明文件,如npm i --save-dev @types/jquery,会自动安装在node_modules/@types/jquery/index.d.ts,TS会自动加载@types这个目录的类型申明文件,如果想修改自动加载的路径,可以修改配置文件tsconfig.json,“compilerOptions”: { “typeRoots”: [“./typings”],会去与tsconfig同级的typings目录找。
- 以上两种方法找不到,就需要自己写类型申明文件
//1、声明类型============================================================
//let/const/var、function、class、enum、module、namespace,使用declare声明
//interface和type,可以不用declare声明,但建议统一加上
//declare的module或namespace里面,可以不需要再declare
//例子:
declare let x:number; //声明x为number类型
declare let x; //申请x为any类型
//声明方法
declare function sayHello(
name:string
):void;
//声明类
declare class Animal {
constructor(name:string);
eat():void;
sleep():void;
}
//声明枚举
declare Color{
Green,
Red,
Blue
}
//声明type和interface,declare可用可不用
type Student = {
name:string,
age:number
}
declare interface Student{
name:string,
age:number
}
//声明模块,模块内可以不用再declare
declare module AnimalLib {
class Animal {
constructor(name:string);
eat(): void;
sleep(): void;
}
type Animals = 'Fish' | 'Dog';
}
//2、类型申明文件的例子===================================================
//模块moment的类型申明文件
//类型申明和模块化是两个不同组件,如果项目中需要使用到类型,还是要export
//但是模块和命名空间的申明中,可以不用再export
declare module 'moment' {
export interface Moment {
format(format:string): string;
add(
amount: number,
unit: 'days' | 'months' | 'years'
): Moment;
subtract(
amount:number,
unit:'days' | 'months' | 'years'
): Moment;
}
function moment(
input?: string | Date
): Moment;
export default moment; //默认导出
}
//加载配置类型声明文件,在package.json中配置
{
"name": "awesome",
"author": "Vandelay Industries",
"version": "1.0.0",
"main": "./lib/main.js",
"types": "./lib/main.d.ts"
}
//3、为外部模块扩展属性或方法,需要借助declare=============================
//《函数方法》章节,也有扩展方法的说明
//例1:为外部模块扩展对象的类型
// a.ts,包含export,是一个模块
export interface A {
x: number;
}
// b.ts,利用interface的类型合并特性
import { A } from './a';
declare module './a' {
interface A {
y: number;
}
}
const a:A = { x: 0, y: 0 };
//例2:为全局模块中的内置类型扩展方法
export {}; //将当前文件模块化
declare global {
interface String {
toSmallString(): string;
}
}
String.prototype.toSmallString = ():string => {
// 具体实现
return '';
};