导读:本期聚焦于乐少创作的《TypeScript如何定义屏幕唤醒锁Set Screen Wake Lock Active API的状态同步类型》,敬请观看详情。Wake Lock API提供了一种防止设备屏幕自动熄屏的能力,但官方接口在不同浏览器中的实现存在差异,类型定义也不够完善。当页面通过Media Session相关的setWakeLockActive方法设置屏幕唤醒锁状态时,如何在TypeScript里为这些能力写出严谨的类型,成为不少团队接入时遇到的第一个障碍。本文从Wake Lock的基础概念讲起,分析Sentinel实例的生命周期与release事件的触发时机,接着给出完整的类型声明方案,包括WakeLockType、WakeLockSentinel以及navigator.wakeLock的类型扩展写法。文章还覆盖了事件监听的类型收窄、多浏览器兼容的类型合并、以及状态同步时的竞态处理思路,并附上可直接复用的代码示例,帮助你在项目中稳定接入这一API。

屏幕唤醒锁(Screen Wake Lock)是浏览器提供的一个实用能力,典型场景是视频播放、地图导航、在线演示等不希望屏幕自动熄灭的页面。不过很多开发者在TypeScript项目中接入时发现,navigator.wakeLock在默认的lib.dom.d.ts中要么没有声明,要么声明过于简略,尤其是在与Media Session配合设置唤醒锁激活状态(setWakeLockActive这类语义)时,状态同步的类型定义经常报错。这篇文章就来系统地讲清楚这套API的类型该怎么写。

TypeScript如何定义屏幕唤醒锁Set Screen Wake Lock Active API的状态同步类型

Wake Lock API的核心概念与类型结构

Wake Lock API的整体结构并不复杂:通过navigator.wakeLock.request()请求一把锁,返回一个WakeLockSentinel对象,这个对象既是锁的句柄,也是事件源。锁有两种类型:屏幕锁(screen)用于阻止屏幕熄灭,系统锁(system)用于阻止设备休眠,其中系统锁目前浏览器支持非常有限,实际项目中基本只用screen。

类型定义的关键在于对生命周期的准确建模。Sentinel对象有一个released布尔属性表示锁是否已释放,一个release()方法用于主动释放,以及一个release事件,当锁因页面失焦、标签页隐藏、系统策略等原因被浏览器强制释放时触发。很多类型写法的坑就出在把released当成永远可靠的状态,实际上它只是一个快照,真正的状态同步必须依赖事件。

基础类型可以这样声明:

type WakeLockType = 'screen' | 'system';

interface WakeLockSentinelEvent extends Event {
  target: WakeLockSentinel;
}

interface WakeLockSentinel extends EventTarget {
  readonly type: WakeLockType;
  readonly released: boolean;
  onrelease: ((this: WakeLockSentinel, ev: WakeLockSentinelEvent) => void) | null;
  release(): Promise<void>;
}

interface WakeLock {
  request(type: WakeLockType): Promise<WakeLockSentinel>;
}

扩展navigator类型与Media Session状态同步的类型设计

有了基础接口之后,还需要把它挂到navigator上。由于TypeScript的接口声明合并特性,最干净的方式是通过模块声明补充Navigator接口,而不是用as any硬绕过去。这样既保留了类型检查,又不会污染全局的其他逻辑。

declare global {
  interface Navigator {
    wakeLock?: WakeLock; // 可选,因为不是所有浏览器都支持
}

// 使用时的能力检测
async function acquireScreenLock(): Promise<WakeLockSentinel | null> {
  if (!navigator.wakeLock) {
    console.warn('当前浏览器不支持 Wake Lock API');
    return null;
  }
  try {
    return await navigator.wakeLock.request('screen');
  } catch (err) {
    console.warn('请求唤醒锁被拒绝:', err);
    return null;
  }
}

注意这里把wakeLock声明为可选属性。这个细节很重要:Safari和部分移动端浏览器对Wake Lock的支持并不一致,如果把类型写成必有属性,能力检测的分支就会被类型系统误判为多余代码,反而隐藏了运行时风险。

在Media Session场景下做状态同步时,推荐用一个可辨识联合来建模锁的三种状态:未请求、持有中、已释放。这样做的好处是,消费状态的代码必须通过类型收窄才能拿到Sentinel引用,编译期就能挡住“锁已释放还在调用release”这类低级错误:

type LockState =
  | { status: 'idle' }
  | { status: 'locked'; sentinel: WakeLockSentinel }
  | { status: 'released' };

class ScreenLockManager {
  private state: LockState = { status: 'idle' };

  async lock(): Promise<boolean> {
    const sentinel = await acquireScreenLock();
    if (!sentinel) return false;
    this.state = { status: 'locked', sentinel };
    sentinel.addEventListener('release', () => {
      this.state = { status: 'released' };
    });
    return true;
  }

  async unlock(): Promise<void> {
    if (this.state.status === 'locked') {
      await this.state.sentinel.release();
      this.state = { status: 'released' };
    }
  }

  get isActive(): boolean {
    return this.state.status === 'locked';
  }
}

这个例子里的setWakeLockActive语义就体现在isActive这个状态查询上,它始终反映真实的锁状态,而不是简单地读取sentinel.released,因为后者在异常释放路径上可能更新滞后。

事件监听的类型收窄与竞态处理

Wake Lock最容易被忽视的一点是:页面切到后台时锁会被浏览器自动释放,回到前台不会自动恢复。所以完整的实现必须监听visibilitychange事件并重新请求锁。这时就会出现竞态问题——如果用户在极短时间内反复切换标签页,可能同时存在多个request()的Promise在飞行中。

处理思路是引入一个请求序号(或称epoch),只有最新一次请求的结果才允许写入状态,旧的结果直接丢弃。类型上可以给内部状态加一个token字段来配合判断:

type ManagedState =
  | { status: 'idle'; token: 0 }
  | { status: 'locked'; token: number; sentinel: WakeLockSentinel }
  | { status: 'released'; token: number };

class ReentrantLockManager {
  private state: ManagedState = { status: 'idle', token: 0 };
  private nextToken = 1;

  async relock(): Promise<void> {
    const token = this.nextToken++;
    const sentinel = await acquireScreenLock();
    if (!sentinel || token !== this.nextToken - 1) {
      // 已有更新的请求,本次结果作废
      sentinel?.release();
      return;
    }
    this.state = { status: 'locked', token, sentinel };
    sentinel.addEventListener('release', () => {
      if (this.state.status === 'locked' && this.state.token === token) {
        this.state = { status: 'released', token };
      }
    });
  }
}

document.addEventListener('visibilitychange', () => {
  if (document.visibilityState === 'visible') {
    new ReentrantLockManager().relock();
  }
});

另外要注意addEventListener('release', ...)的回调类型。如果你的tsconfig里的lib版本较老,WakeLockSentinel继承自EventTarget时事件回调参数会推断为宽泛的Event,此时可以自定义一个类型守卫函数,把事件对象的target收窄回WakeLockSentinel:

function isWakeLockSentinelEvent(e: Event): e is WakeLockSentinelEvent {
  return e.target !== null && 'released' in (e.target as object);
}

兼容性与降级方案的类型处理

最后一件事是降级。对不支持Wake Lock API的浏览器,常见的替代方案是播放一段无声视频来阻止息屏。为了不让两套逻辑的类型混在一起,可以抽象一个统一的接口:

interface ScreenKeepAwake {
  activate(): Promise<boolean>;
  deactivate(): Promise<void>;
  readonly active: boolean;
  readonly strategy: 'wake-lock' | 'fallback-video';
}

function createKeepAwake(): ScreenKeepAwake {
  if (navigator.wakeLock) {
    return new WakeLockAdapter();
  }
  return new SilentVideoAdapter();
}

把策略名strategy写进类型里还有一个好处:日志和埋点代码可以直接依据这个字段区分上报来源,排查线上问题时能快速判断用户走的是哪条路径。

总结一下,Wake Lock的类型定义核心是三点:把navigator.wakeLock声明为可选属性以强制能力检测、用可辨识联合建模锁的状态机、在重入场景下用token机制防竞态。把这三点落实到位,屏幕唤醒锁的接入就能在TypeScript项目中既严谨又稳妥地跑起来。

TypeScriptWake Lock API类型定义修改时间:2026-09-16 20:04:46

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/0916/58158.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。