打开微信开发者工具,新建项目,模板选择【TS-基础模板】,当然你也可以选择带有Sass或是Less的基础版本,根据项目开发习惯和代码风格自行选择,这里仅是作为一个示例,目录结构如下所示,相关基础配置可参考微信官方文档,这里不再复述。
/miniprogram # 小程序主目录
├── pages/ # 页面目录,存放小程序的各个页面
│ ├── index/ # 示例页面
│ ├── logs/ # 日志页面示例
├── utils/ # 工具函数目录
│ └── util.ts # 示例工具函数,供各页面调用
├── typings/ # 类型定义目录
│ └── types/ # 自定义类型和第三方库类型定义
│ ├── wx/ # 微信小程序 API 类型定义
│ │ └── index.d.ts # 微信 API 类型声明文件
│ └── index.d.ts # 自定义类型声明文件
├── app.json # 全局配置文件,定义小程序的页面路径、窗口样式等
├── app.ts # 小程序入口文件,包含生命周期函数
├── app.wxss # 全局样式文件
├── .eslintrc.js # ESLint 配置文件,用于代码风格检查
├── package.json # npm 配置文件,管理依赖和脚本
├── project.config.json # 小程序项目配置文件,微信开发工具使用
├── project.private.config.json # 私有配置文件,仅供本地开发使用
└── tsconfig.json # TypeScript 配置文件,定义编译选项
想在这篇文章跟大家着重分享下,我在根目录添加的static
文件夹,里面除了存放一些常规的静态资源和图片,还把项目的功能模块、主包资源存放其中,方便后续维护。而我也是想从这一个文件夹,来跟大家分享,微信小程序在引入TypeScript之后,要如何降低项目的维护成本、提升团队协作效率以及增强项目可扩展性。
/miniprogram
│
├── /pages # 主包
├── /pagesA # 分包A
├── /pagesB # 分包B
├── /pagesC # 分包C
├── /static # 存放静态资源
│ ├── /actions # 调用配置相关方法
│ ├── /api # 主包接口
│ ├── /behaviors # behaviors相关
│ ├── /config # 配置相关
│ │ ├── api.ts # API请求封装
│ │ ├── app.ts # 项目配置数据
│ │ └── env.ts # 环境配置文件
│ ├── /images # 图片文件
│ ├── /libs # 第三方SDK
│ ├── /scss # 样式文件
│ ├── /state # globalData相关
│ ├── /types # interface相关
│ └── /utils # ts文件
│ ├── common.ts # 公共方法 - 不涉及接口请求
│ ├── report.ts # 公共方法 - 涉及接口请求
│ └── utils.ts # 工具函数
├── app.ts # 小程序的主入口文件
└── app.json # 小程序的全局配置文件
actions
文件夹存放了一些调用配置相关的方法,我们通常会在app.globalData
当中存放一些全局数据,例如用户信息。我们可以在里面新建一个userActions.ts
的文件,用来存放多个页面会使用到的用户相关方法,以下是一个更新用户头像信息的例子。
// static/actions/userActions.ts
const app = getApp<IAppOption>();
/**
* 更新用户头像信息
* @param imgUrl - 新的头像图片地址
* @returns boolean - 是否更新成功
*/
export function setUserAvatar(imgUrl: string): boolean {
if (!imgUrl || typeof imgUrl !== "string") return false;
if (app.globalData.userInfo) {
app.globalData.userInfo.avatar = imgUrl;
return true;
}
return false;
}
api
文件夹存放主包的API接口,每个分包都有单独的api
文件夹用于存放请求接口,这里也是用用户信息的接口作为例子,这里用到RequestParams
类型我会在后面封装API的部分详细讲解。
// static/api/userApi.ts
export const getUserInfoParams: RequestParams = {
url: '/userInfo/get',
method: 'GET',
};
behaviors
文件夹用于存放Behavior
文件,类似于一些编程语言中的mixins
或traits
,具体使用方式可参考微信官方文档 自定义组件-behaviors 部分。这里需要注意的是,你在Behavior
里写的方法,需要在声明文件里同步声明该方法,如果有相应的参数和返回值也同样写上,否则会提示你类型Interface上不存在该属性的警告。
// static/behaviors/userBehavior.ts
export const userBehavior = Behavior({
methods: {
getUserInfo(){
console.log('this is user-info.')
},
}
// typings/types/wx/lib.wx.component.d.ts
interface InstanceMethods<D extends DataOption> {
...
getUserInfo(): void;
}
config
配置相关文件夹,可根据实际项目进行存放,我这里存放了生产和测试环境的变量配置,以及微信小程序的AppId、订阅消息的推送id,第三方插件的AppID之类的项目配置信息,仅涉及数据读取而不做修改的部分。还有就是项目构建过程中,必不可少的api请求封装。
// static/config/api.ts
import { NODE_ENV } from '@/static/config/env';
declare global {
// 请求参数类型
type RequestParams = {
url: string;
method: 'GET' | 'POST';
header?: Record<string, string>;
timeout?: number;
};
}
// 定义所有 API 响应的通用结构
interface ApiResponse<T> {
data: T;
error?: any;
message: string;
prompt: string;
status: number;
}
type ApiRequestParams = {
url: string;
method: 'GET' | 'POST';
header?: Record<string, string>;
data?: any;
params?: any;
toastMessage?: string; // 加载提示信息
toastMask?: boolean; // 加载提示蒙层
timeout?: number; // 请求超时时间
loadingTime?: number; // 延迟显示 loading 的时间
showToast?: boolean; // 是否显示错误提示
showLoading?: boolean; // 是否显示加载提示
};
export const apiRequest = <T>(apiParams: ApiRequestParams): Promise<ApiResponse<T>> => {
return new Promise((resolve, reject) => {
// 提取参数并设置默认值
const {
url,
method,
header,
data,
params,
showLoading = true,
toastMessage = '加载中',
toastMask = true,
timeout = 5000,
loadingTime = 1000,
showToast = true,
} = apiParams;
const defaultHeaders = {
'content-type': 'application/x-www-form-urlencoded',
'version': NODE_ENV.version, // 版本号
};
const headers = { ...defaultHeaders, ...(header || {}) };
let loadingTimer: number | null = null;
// 延迟显示 loading
if (showLoading) {
loadingTimer = setTimeout(() => {
wx.showLoading({
title: toastMessage,
mask: toastMask,
});
}, loadingTime) as unknown as number;
}
// 处理 GET 请求or特殊请求的查询参数
let requestUrl = NODE_ENV.mallApi + url;
if (method === 'GET' && data && Object.keys(data).length > 0) {
const queryString = Object.entries(data)
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`)
.join('&');
requestUrl += (url.includes('?') ? '&' : '?') + queryString;
}
if (params && Object.keys(params).length > 0) {
const queryString = Object.entries(params)
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`)
.join('&');
requestUrl += (url.includes('?') ? '&' : '?') + queryString;
}
wx.request({
url: requestUrl,
method,
header: headers,
data: method === 'POST' ? data : undefined,
timeout,
success: (res) => {
if (loadingTimer) clearTimeout(loadingTimer);
wx.hideLoading();
const { status } = res.data as ApiResponse<T>;
if (res.statusCode === 200 && status === 200) {
const response = res.data as ApiResponse<T>;
resolve({ ...response });
} else if (res.statusCode === 200 && (status == 511100)) {
// 511100为token失效, 这里跳转登录页
} else {
if (showToast) {
wx.showToast({
title:
(res?.data as ApiResponse<T>)?.prompt || `太火爆了, 请稍后重试(${res?.statusCode || status || '超时'})`,
icon: 'none',
});
}
reject(res?.data);
}
},
fail: (err) => {
if (loadingTimer) clearTimeout(loadingTimer);
wx.hideLoading();
if (showToast) {
wx.showToast({
title: '请求失败',
icon: 'none',
});
}
reject(err);
},
});
});
};
几个需要说明的地方,在多个分包多个功能模块都需要引入api文件的话,过于麻烦,所以这里选择把RequestParams
进行全局声明;可以将apiRequest
挂载到app.globalData
上方便引用,也可以直接在页面中获取调用;NODE_ENV
里包含了项目域名和版本号,大家可根据实际项目进行调整;setTimeout
使用as unknown as number
的原因,是TypeScript会将其识别为Timeout
对象,所以临时转换为未知类型unknown
,再进行类型断言,如果你的项目没有这个报错可直接as number
;在请求处理中单独处理params
部分,这是在实际接口当中,可能会传递多种查询参数,例如data
代表业务参数,而params
可能是通用的公共参数(如分页、鉴权等)。
就算是高效的API封装也需要强大的后端支撑,为了保证接口调用的稳定性和响应速度,选择一个可靠、适配小程序业务需求的后端环境至关重要。在这里,腾讯云新一代的云服务器产品轻量应用服务器 TencentCloud Lighthouse为我们提供了一个理想的选择,它能为我们API接口的运行提供以下支持:
当然无论你是选择轻量应用服务器 TencentCloud Lighthouse还是标准云服务器 CVM,你都可以趁现在参加 腾讯云11.11上云拼团Go 享受全年最优惠的价格。下单满足条件的商品(即卡片展示带有 “可拼团”角标的商品),还可以参加拼团活动,双人即可成团,成团即可享受二重好礼:赠送最高3个月的时长,或者多拿10%的资源包。这里有个小技巧:你可将多个商品合并下单去拼团,这样只需要去发起1次拼团,则每个”可拼团“标签的商品都能享受赠送。
images
文件夹存放一些必要的图片,主要应对当网络环境较差的情况下,依旧能显示涉及业务的图片部分,比如关闭按钮;libs
文件夹存放第三方SDK;scss
文件夹存放全局的样式文件,以及主包一些公共样式文件;state
文件夹用于存放globalData
相关的数据,types
文件夹存放interface
相关的文件,这两个分别举个例子。
// static/state/userState.ts
export const userState = {
user: {
nickName: 'Nian糕',
picture: '/static/images/avatar.png',
},
}
// static/types/userTypes.ts
export interface UserInfo {
nickName: string;
picture: string;
}
utils
文件夹我单独区分成三个文件,根据是否涉及接口请求的公共方法common.ts
文件和report.ts
文件,还有公共函数utils.ts
文件,前两个或许还好理解,但utils.ts
文件是否跟common.ts
文件有功能边界重合的部分,大家或许有不一样的想法,可以根据实际项目进行调整。我这里区分两个文件的理由是:common.ts
主要服务于页面调用,偏向具体业务场景;utils.ts
文件则是更加通用和底层,除了页面还有其他工具类文件都可以调用它。
// static/utils/common.ts
import { NODE_ENV } from '@/static/config/env';
import { appConfig } from '@/static/config/appConfig';
// 跳转其他小程序
export function toProgram(linkItem?: DrinkLinkItemType, type = 'default') {
let program = {
appId: linkItem?.AppId || appConfig.drinkAppId,
path: linkItem?.path || '',
extraData: linkItem?.extraData || {},
envVersion: linkItem?.envVersion || (NODE_ENV.type === 'Production' ? 'release' : 'trial'),
}
if (type === 'default') wx.navigateToMiniProgram(program);
// 半屏小程序
if (type === 'half') wx.openEmbeddedMiniProgram(program);
}
// static/utils/utils.ts
/**
* 转化成时间戳
* 兼容iOS
* @param { Sting } date 日期
*/
export const toTimeStamp = (date: string): number => new Date(date.replace(/-/g, '/')).getTime();
行文过程中出现错误或不妥之处在所难免,希望大家能够给予指正,以免误导更多人,最后,如果你觉得我的文章写的还不错,希望能够点一下👍🏻和⭐️
OTZ!拜托了!这对我真的很重要!!!
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。