前言
- 市面上找了一圈关于这个得文章,也看了官方的社区和给出的解决方案,都没讲清楚怎么优雅的实现 class 反射
需求
- 当前在开发的应用是基于鸿蒙
API 13
,具体依赖如下。
- 众所周知,Axios 默认会根据服务端响应数据,自动解析
data
,如果我们定义的数据模型全部是 interface
会导致一个问题,没法实现 UI响应式更新
,那这个时候,我们就需要定义 class
,并借助 @ObserverV2
、@Trace
来实现 UI响应式更新
,同时使用 class-transformer
中的 plainToClassFormExist
和 Type
方法来实现 class
实例化
- 如果是深层嵌套,还需借助
class-transformer
中的 Type
装饰器来实现(最好使用别名,防止跟鸿蒙自带的 @Type
装饰器冲突,鸿蒙自带的 @Type
是为了持久化模型反序列化用的)
json
复制代码
{
"modelVersion": "5.0.1",
"description": "Please describe the basic information.",
"dependencies": {
"@pura/harmony-utils": "^1.2.5",
"@pura/harmony-dialog": "^1.0.9",
"@ohos/axios": "^2.2.4",
"@umeng/common": "^1.1.3",
"@umeng/analytics": "^1.2.4",
"@umeng/utunnel": "^1.1.3",
"@umeng/push": "^2.0.0",
"@umeng/apm": "^1.1.0",
"dayjs": "^1.11.13",
"class-transformer": "^0.5.1",
"reflect-metadata": "^0.1.13"
},
"devDependencies": {
"@ohos/hypium": "1.0.19",
"@ohos/hamock": "1.0.0"
},
"dynamicDependencies": {}
}
过程
utils/HttpUtil
工具类封装
- 首先创建
utils/HttpUtil.ets
- 重点说明,引入了,一个持久化缓存的模型
AppPersistenceModel
,这个看你们自己的需求
Typescript
复制代码
import axios, { AxiosRequestConfig, AxiosResponse } from '@ohos/axios';
import { AppUtil, ToastUtil } from '@pura/harmony-utils';
import { Config } from '../config/Config';
import { PersistenceV2 } from '@kit.ArkUI';
import { AppPersistenceModel } from '../models/AppPersistenceModel';
import { LogUtil } from './LogUtil';
import { jumpLogin } from './HelperUtil';
import { BaseResponse } from '../models/ApiResponseModel';
export interface RequestConfig<D = object> extends AxiosRequestConfig<D> {
ignoreError?: boolean
ignoreLoginError?: boolean
}
const parseCodeMessage = (code: number) => {
const codeMessage: Record<number, string> = {
400: '请求有错误',
405: '请求URL不存在',
500: '服务器发生错误',
502: '网关错误',
503: '服务不可用,服务器暂时过载或维护',
504: '网关超时',
}
return codeMessage[code]
}
interface BaseResponseWithout extends Omit<BaseResponse, 'isSuccess'> {}
export class HttpUtil {
private static request<T, D = object>(config?: RequestConfig<D>): Promise<T> {
config = config || {}
let url = config.url || ''
if (!url.startsWith('http://') && !url.startsWith('https://')) {
url = `${Config.BASE_URL}${url}`
}
// 连接持久化模型
const appPersistenceModel = PersistenceV2.connect(AppPersistenceModel, () => new AppPersistenceModel())!
// 判断是否存在 session_id
if (appPersistenceModel.sessionId) {
if (url.includes('?')) {
url = `${url}&session_id=${appPersistenceModel.sessionId}`
} else {
url = `${url}?session_id=${appPersistenceModel.sessionId}`
}
}
// 设置请求地址
config.url = url
config.headers = config.headers || {}
config.headers['Content-Type'] = 'application/json'
config.headers['X-App'] = 'ZGT_ZTRUST_APP'
config.headers['X-App-Platform'] = 'harmony'
config.headers['X-App-Version'] = AppUtil.getBundleInfoSync().versionName
config.headers['X-TRACE-ID'] = appPersistenceModel.traceId
config.headers['X-DEVICE-ID'] = appPersistenceModel.deviceUUID
const ignoreError = config?.ignoreError
const ignoreLoginError = config?.ignoreLoginError
return axios.request(config).then((res: AxiosResponse<T, D>) => {
const cookies = res.headers['set-cookie']
let sessionId = ''
cookies?.map((cookie: string) => {
if (cookie.includes('PHPSESSID')) {
sessionId = cookie.split(';')[0].split('=')[1]
}
})
// 本地不存在 session_id 且服务端返回 session_id 时,将 session_id 存入本地
if (sessionId && !appPersistenceModel.sessionId) {
appPersistenceModel.sessionId = sessionId
}
// 输入日志
LogUtil.debug(
`[HTTP] ${config?.method} ${res.status} ${url}`,
config?.params ? `[GET] ${JSON.stringify(config?.params)}` : '',
config?.data ? `[POST] ${JSON.stringify(config?.data)}` : '',
res.data as object,
)
// 判断状态码是否正常
if (res.status >= 200 && res.status < 300) {
return res
}
return Promise.reject({
code: res.status,
message: parseCodeMessage(res.status),
})
}).then(res => {
if (typeof res.data === 'object') {
const data = res.data as BaseResponseWithout
if (typeof data.code === 'undefined' || data.code < 0) {
return Promise.reject(res.data)
}
}
return res.data
}).catch((err: BaseResponseWithout) => {
err.code = err.code || -1
if (err.code > 0) {
err.code *= -1
}
err.message = err.message || `请求异常,状态码:${err.code}`
if (!ignoreError) {
ToastUtil.showToast(err.message)
}
if (err.code === -1001) {
// todo: 清除登录数据
// 跳转登录页
if (!ignoreLoginError) {
jumpLogin()
}
}
return Promise.resolve(err as T)
})
}
static get<T>(config?: RequestConfig): Promise<T> {
config = config || {}
config.method = 'GET'
return HttpUtil.request<T>(config)
}
static post<T, D>(config?: RequestConfig<D>): Promise<T> {
config = config || {}
config.method = 'POST'
return HttpUtil.request<T, D>(config)
}
static put<T, D>(config?: RequestConfig<D>): Promise<T> {
config = config || {}
config.method = 'PUT'
return HttpUtil.request<T, D>(config)
}
static delete<T, D>(config?: RequestConfig<D>): Promise<T> {
config = config || {}
config.method = 'DELETE'
return HttpUtil.request<T, D>(config)
}
static patch<T, D>(config?: RequestConfig<D>): Promise<T> {
config = config || {}
config.method = 'PATCH'
return HttpUtil.request<T, D>(config)
}
}
config/Config
配置文件
Typescript
复制代码
export class Config {
static readonly BASE_URL = 'https://api.xxxxxxxxxx.com/test'
}
models/AppPersistenceModel
持久化模型文件
Typescript
复制代码
@ObservedV2
export class AppPersistenceModel {
// 隐私协议是否同意
@Trace isAgreePrivacy: boolean = false
// 设备跟踪ID
@Trace traceId: string = ''
// 设备UUID
@Trace deviceUUID: string = ''
// 用户会话ID
@Trace sessionId: string = ''
// 显示更新版本弹窗日期
@Trace showUpdateVersionDialogDate: string = ''
}
LogUtil
工具类
- 不是我吹牛逼,这个是真好用,没有使用
@pura/harmony-utils
提供的 LogUtil
Typescript
复制代码
import { hilog } from "@kit.PerformanceAnalysisKit"
interface Params {
domain: number
tag: string
showLog: boolean
maxLineSize: number
}
type Args = string | number | boolean | undefined | null | object
const DOMAIN = 0
const TAG = 'XXXAPP'
const SHOW_LOG = true
const FORMAT = '%{public}s'
const MAX_LINE_SIZE = 1024
const defaultParams: Params = {
domain: DOMAIN,
tag: TAG,
showLog: SHOW_LOG,
maxLineSize: MAX_LINE_SIZE,
}
export class LogUtil {
private static domain: number = DOMAIN
private static tag: string = TAG
private static format: string = FORMAT
private static showLog: boolean = SHOW_LOG
private static maxLineSize: number = MAX_LINE_SIZE
static init(params?: Params) {
LogUtil.domain = params?.domain ?? defaultParams.domain
LogUtil.tag = params?.tag ?? defaultParams.tag
LogUtil.showLog = params?.showLog ?? defaultParams.showLog
LogUtil.maxLineSize = params?.maxLineSize ?? defaultParams.maxLineSize
}
static setDomain(domain: number = DOMAIN) {
LogUtil.domain = domain
}
static setTag(tag: string = TAG) {
LogUtil.tag = tag
}
static setShowLog(showLog: boolean = SHOW_LOG) {
LogUtil.showLog = showLog
}
static setMaxLineSize(maxLineSize: number = MAX_LINE_SIZE) {
LogUtil.maxLineSize = maxLineSize
}
static debug(...args: Args[]): void {
LogUtil.debugTag("DEFAULT", ...args)
}
static debugTag(tag: string, ...args: Args[]): void {
if (!LogUtil.showLog) {
return
}
LogUtil.getMessages(args, (message: string) => {
hilog.debug(LogUtil.domain, `${LogUtil.tag}/DEBUG/${tag}`, LogUtil.format, message)
})
}
static info(...args: Args[]): void {
LogUtil.infoTag("DEFAULT", ...args)
}
static infoTag(tag: string, ...args: Args[]): void {
if (!LogUtil.showLog) {
return
}
LogUtil.getMessages(args, (message: string) => {
hilog.info(LogUtil.domain, `${LogUtil.tag}/INFO/${tag}`, LogUtil.format, message)
})
}
static warn(...args: Args[]): void {
LogUtil.warnTag("DEFAULT", ...args)
}
static warnTag(tag: string, ...args: Args[]): void {
if (!LogUtil.showLog) {
return
}
LogUtil.getMessages(args, (message: string) => {
hilog.warn(LogUtil.domain, `${LogUtil.tag}/WARN/${tag}`, LogUtil.format, message)
})
}
static error(...args: Args[]): void {
LogUtil.errorTag("DEFAULT", ...args)
}
static errorTag(tag: string, ...args: Args[]): void {
if (!LogUtil.showLog) {
return
}
LogUtil.getMessages(args, (message: string) => {
hilog.error(LogUtil.domain, `${LogUtil.tag}/ERROR/${tag}`, LogUtil.format, message)
})
}
static fatal(...args: Args[]): void {
LogUtil.fatalTag("DEFAULT", ...args)
}
static fatalTag(tag: string, ...args: Args[]): void {
if (!LogUtil.showLog) {
return
}
LogUtil.getMessages(args, (message: string) => {
hilog.fatal(LogUtil.domain, `${LogUtil.tag}/FATAL/${tag}`, LogUtil.format, message)
})
}
private static getMessages(args: Args[], callback: (message: string) => void): void {
const messages: string[] = [];
const firstMessage =
`┌───────${LogUtil.tag}──────────────────────────────────────────────────────────────────────────`
const firstMessageLength = firstMessage.length
messages.push(firstMessage)
// 获取堆栈
const stack = new Error().stack;
if (stack) {
const stackArr = stack.split('\n')
for (let i = 0; i < stackArr.length; i++) {
const item = stackArr[i]
if (!item.includes(LogUtil.name)) {
messages.push(`| ${item}`)
break
}
}
// 与第一行保持一样的长度
messages.push(`|${'─'.repeat(firstMessageLength - 1)}`)
}
args.map(item => {
let message = ""
switch (typeof item) {
case 'object':
message = JSON.stringify(item, null, 2)
break
default:
message = String(item)
break
}
if (message.length > LogUtil.maxLineSize) {
// 切割长字符串
const arr = message.match(new RegExp(`.{1,${LogUtil.maxLineSize}}`, 'g'))
if (arr) {
arr.map((item) => {
messages.push(`| ${item}`)
})
} else {
messages.push(`| ${item}`)
}
} else {
// 先按 \n 切割
const arr = message.split('\n')
arr.map((item) => {
messages.push(`| ${item}`)
})
}
})
messages.push(`└${'─'.repeat(firstMessageLength - 1)}`)
messages.map(item => {
callback(item)
})
}
}
models/ApiResponseModel
模型
Typescript
复制代码
export class BaseResponse {
code?: number
message?: string
isSuccess(): boolean {
if (typeof this.code === 'string') {
return parseInt(this.code) === 0
}
return this.code === 0
}
}
export class ApiResponse<T> extends BaseResponse {
dataset?: T
constructor(dataset: T) {
super()
this.dataset = dataset
}
}
services/HttpService.ets
请求服务
- 目前是打算把所有的请求都放这里管理
- 第
21
行很重要,那是将普通的 json
对象,反射到 class 中,并返回
- 这样的话,可以处理自行处理并返回
object
和 array
Typescript
复制代码
import { AppUtil } from "@pura/harmony-utils";
import { plainToClassFromExist } from "class-transformer";
import { ApiResponse } from "../models/ApiResponseModel";
import { UpdateVersionModel } from "../models/UpdateVersionModel";
import { HttpUtil } from "../utils/HttpUtil";
export class HttpService {
/**
* 获取更新版本检查
*/
static getUpdateVersionCheck() {
return HttpUtil.get<ApiResponse<UpdateVersionModel>>(
{
url: '/Mobile/AppVersion/check',
params: {
name: 'xxx',
platform: 'harmony',
version: AppUtil.getBundleInfoSync().versionName
},
ignoreError: true,
},
).then(res => {
return plainToClassFromExist(new ApiResponse(new UpdateVersionModel()), res)
})
}
/**
* 获取启动页广告弹窗
*/
static getOpenAdsSwiper() {
// return HttpUtil
}
}
结果
- 最终,我们就可以直接在
page
中,直接愉快的调用 class
中的 isSuccess
,以及响应式更新啦
Typescript
复制代码
fetchUpdateVersion(): void {
HttpService.getUpdateVersionCheck()
.then((res) => {
if (!res.isSuccess()) {
this.handleUpdateVersionDialogNext()
return
}
const version = res.dataset!.version!
if (this.AppPersistenceModel.showUpdateVersionDialogDate === buildUpdateVersionCache(version)) {
// 当前版本已跳过了,判断是否今天是否已经展示过了
this.handleUpdateVersionDialogNext()
return
}
this.updateVersionModel = res.dataset!
this.visibleUpdateVersionDialog = true
})
}
结尾
- 初次接触鸿蒙开发,上述如有描述不当或者错误,请及时指出,也欢迎评论区探讨或私信我进行探讨。