用一行代码监听 Web Element 的所有事件,ocev.js 介绍

什么是 ocev

ocev 是一个事件库,设计目的是为了简化事件处理的复杂性,同时支持 promise/stream 的方式处理事件.

支持代理 web 元素的所有事件,并用 ocev 的 api 进行处理.

所有 api 都最大化的支持 typescript,提供最完整的类型提示

github

文档地址

一个代理事件的示例 codesandbox

安装

bash 复制代码
npm install ocev

基本使用

下面是 ocev 最基本的使用方法

typescript 复制代码
import { SyncEvent } from "ocev"

// 定义事件的类型
type EventHandlerMap = {
  event1: (arg1: string, arg2: number) => void
  event2: (arg1: number, arg2: string) => void
}
// 实例化
const syncEvent = SyncEvent.new<EventHandlerMap>()

queueMicrotask(() => {
  // 触发事件
  syncEvent.emit("event1", "1", 2)
  syncEvent.emit("event2", 3, "4")
})

// 监听事件
const cancel = syncEvent
  .on("event1", (arg1, arg2) => {})
  .once("event2", (arg1, arg2) => {})
  .on("event1", (arg1, arg2) => {}, {
    debounce: { // 防抖
      waitMs: 200,
      maxWaitMs: 500,
    },
  })

// 取消注册
// cancel()

// 等待事件触发 Promise
await syncEvent.waitUtil("event1", { timeout: 1000 })

// 创建事件流 Stream
const eventStream = syncEvent.createEventStream(["event1", "event2"])

ocev 的特点

通过上面的示例可以看到 ocev 本质上是一个 (pub/sub)库 , 但是 ocev 还可以代理 web element 所有的事件 , 使用 ocev 可以用promise/stream 处理所有的事件

ocev 有两个类, SyncEvent,EventProxy, 下面的例子都是基于 EventProxy 的,EventProxy 本质上是 SyncEvent 的封装

1. 简化 web 的事件处理方式

我一直都觉得 web 的事件处理方式过于复杂,如果你在使用 react,你很有可能要写这种代码

tsx 复制代码
useEffect(() => {
  const callback = () => {}
  target.addEventListener("event", callback)

  return () => {
    target.removeEventListener("event", callback)
  }
}, [target])

对于多个事件,你要写这种代码

tsx 复制代码
useEffect(() => {
  const callback1 = () => {}
  target.addEventListener("event1", callback1)

  const callback2 = () => {}
  target.addEventListener("event2", callback2)

  // ....

  return () => {
    target.removeEventListener("event1", callback1)
    target.removeEventListener("event2", callback2)
    // ....
  }
}, [target])

你注册了多少个,就要清理多少个,这写起来很繁琐

如果你是用 ocev, 你的代码会是这样的,无限调用,一次性清理

tsx 复制代码
import { EventProxy } from "ocev"

useEffect(() => {
  return EventProxy.new(target)
    .on("event1", (...args) => {}) // 支持完整的类型提示
    .once("event2", (...args) => {})
    .on("event3", (...args) => {})
}, [target])

ocev 的方法 on/once 返回的是一个函数,该函数可以继续调用方法 once,on, 更详细的介绍请看文档

2. Promise support

考虑一个场景, 你要建立一个 websocket 连接, 并等待连接打开, 并设置最大等待连接时长,然后处理消息和异常, 为了保证正确的释放资源, 你可能会写如下的代码

typescript 复制代码
async function setupWebSocket(
  url: string,
  successCallback: (ws: WebSocket) => void,
  errorCallback: (err: Error) => void,
  timeout: number
) {
  const ws = new WebSocket(url)

  const timeID = setTimeout(() => {
    errorCallback(new Error("timeout"))
    ws.removeEventListener("open", onOpen)
    ws.removeEventListener("error", onError)
  }, timeout)

  function onOpen() {
    successCallback(ws)
    clearTimeout(timeID)
  }

  function onError() {
    errorCallback(new Error("can't connect to server"))
    clearTimeout(timeID)
  }

  ws.addEventListener("open", onOpen)
  ws.addEventListener("error", onError)
}

ocev 支持 Promise 处理事件,如果用ocev来处理,代码是会是这样的

typescript 复制代码
 import { EventProxy } from "ocev"

async function setupWebSocket(url: string, timeout: number) {
  const ws = new WebSocket(url)
  // 等 'open' 事件触发或者超时抛出异常
  await EventProxy.new(ws).waitUtil("open", { timeout })
  //  或者等 error 事件触发 mapToError 将事件的结果映射成 error
  //  await EventProxy.new(ws).waitUtilRace([
  //     { event: "open", timeout },
  //     { event: "error",
  //       mapToError: () => new Error("websocket connect error"),
  //     },
  //   ])

  return ws
}

Promise 让事件处理变得简单优雅,使用 Promise 处理代码可以让逻辑更加的清晰

更进一步,看看怎么用 ocev 来实现消息处理 (Stream)

typescript 复制代码
import { EventProxy } from "ocev"

async function setupWebSocket(url: string, timeout: number) {
  const ws = EventProxy.new(new WebSocket(url))

  await ws.waitUtilRace([
    { event: "open", timeout },
    {
      event: "error",
      mapToError: () => new Error("websocket connect error"),
    },
  ])

  // 转换成事件流
  const eventStream = ws.createEventStream(["close", "message", "error"])
  // 另外一种写法(ReadableStream)
  // const readableStream = ws.createEventReadableStream(["close", "message", "error"])

  // 所有事件会被推送到一个队列里面,轮流触发
  for await (const { event, value } of eventStream) {
    switch (event) {
      case "error": {
        throw Error("websocket connect error")
      }
      case "close": {
        throw Error("websocket connection closed")
      }
      case "message": {
        // 支持类型提示
        const message = value[0].data
        // handle message
        break
      }
      default:
        throw new Error("unreachable")
    }
  }
}

通过异步迭代器,你可以将事件转换成 stream ,在处理消息流的时候,还可以使用 策略 来丢弃一些消息

通过 Promise/Stream , 代码变得清晰可读,当你将所有的代码都转变成 async/await 之后,你可以这样处理重连的逻辑

typescript 复制代码
let reconnectCount = 0
for (;;) {
  try {
    await setupWebSocket("", 1000)
  } catch (error) {
    reconnectCount += 1
  }
}

如果你是要建立 WebRTC 连接,你可以这么写

typescript 复制代码
import { EventProxy } from "ocev"

async function connect(timeout: number) {
  const connection = new RTCPeerConnection()

  await EventProxy.new(connection).waitUtil("connectionstatechange", {
    timeout,
    // 只有当 where 返回 true 的时候才会 resolve
    where: (ev) => connection.connectionState === "connected",
  })

  return connection
}

3. 观察一个 web 对象的所有事件

你知道 video 在播放的时候触发了哪些事件吗?使用 ocev 可以直接观察一个 web 对象的所有事件

typescript 复制代码
import { EventProxy } from "ocev"
EventProxy.new(videoDom).proxyAllEvent().any((eventName, ...args) => {
  console.log(eventName)
})

放在 react 里面可以这么写

tsx 复制代码
import { EventProxy } from "ocev"
import { useEffect, useRef } from "react"

function Video() {
  const videoDomRef = useRef<HTMLVideoElement>(null)
  useEffect(() => {
    return EventProxy.new(videoDomRef.current!, { proxyAllEvent: true }).any((eventName, ...args) => {
      console.log(eventName)
    })
  }, [])

  const url = "" // 你的  video  链接

  return <video muted autoPlay src={url} ref={videoDomRef} />
}

打开控制台, 你会看到 video 触发的所有事件和它的顺序

不只是 video 元素,几乎所有 web element 都可以被 EventProxy 代理

一个示例codesandbox

更多

上面只是 ocev 的一部分 api 用法,如果你想了解更多的内容,请看sync-event 文档

如果你有好的建议和想法,欢迎 pr 和 issue , github 地址

相关推荐
清灵xmf4 小时前
TypeScript 类型进阶指南
javascript·typescript·泛型·t·infer
Amd79411 小时前
Nuxt.js 应用中的 prepare:types 事件钩子详解
typescript·自定义·配置·nuxt·构建·钩子·类型
王解13 小时前
Jest项目实战(4):将工具库顺利迁移到GitHub的完整指南
单元测试·github
油泼辣子多加13 小时前
2024年11月4日Github流行趋势
github
梓羽玩Python14 小时前
推荐一款用了5年的全能下载神器:Motrix!全平台支持,不限速下载网盘文件就靠它!
程序员·开源·github
小牛itbull21 小时前
ReactPress:重塑内容管理的未来
react.js·github·reactpress
鱼满满记1 天前
1.6K+ Star!GenAIScript:一个可自动化的GenAI脚本环境
人工智能·ai·github
梦魇梦狸º1 天前
腾讯轻量云服务器docker拉取不到镜像的问题:拉取超时
docker·容器·github
王解1 天前
Jest项目实战(2): 项目开发与测试
前端·javascript·react.js·arcgis·typescript·单元测试
Huazie1 天前
一篇搞定 Hexo Diversity 主题接入!支持多主题自由切换!
javascript·github·hexo