试试用 hook 组织代码吧| 8月更文挑战

306 阅读2分钟

这是我参与8月更文挑战的第21天,活动详情查看:8月更文挑战

什么是hook

这里的 hook 是一种思想,而非 React 中的 hooks。

它通过暴露一系列的接口用于接收 外部 的操作,从而赋予外部 处理主流程的能力

最简单的hook实现

// 主流程
const pool = []

function main(initValue) {
  if (pool.length) {
    for (let i = 0; i < pool.length; i++) {
      initValue = pool[i](initValue)
    }
  }
}

// 外部事件1:add
pool.push(function add(initValue) {
  initValue += 'add something'
})
// 外部事件2: reduce
pool.push(function reduce(initValue) {
  if (initValue.length > 2) {
    return initValue.substring(0, 2) 
  }
})

本例对传入的事件(add 事件、reduce 事件)进行链式执行且将前一个操作的返回值作为后一个操作的入参。

事件间关系有哪些

  • 同步流式

    多个事件链式执行且前一个操作的返回值作为后一个操作的入参

  • 同步非流式

    多个事件依次执行

  • 同步熔断

    所有事件依次执行,每个事件均有停止执行后续事件的能力(通过返回值)

  • 同步循环

    循环执行所有事件,直到任一事件终止执行(通过返回值)

  • 异步并行

    多个异步操作同时执行

  • 异步流式

    多个异步事件链式执行且前一个操作的返回值作为后一个操作的入参

  • 异步熔断

    所有事件依次执行,每个事件均有停止执行后续事件的能力(通过返回值)

  • 异步循环

    循环执行所有事件,直到任一事件终止执行(通过返回值)

异步可通过执行形参中的next函数交由后续事件执行,即:

function event1(args, next) {
  // do some async things
  // when handle over, execute next to finish current event
  next()
}

如何用代码表示这些事件间关系

const config = {
  eventFns: []
}

这个 config 是框架开发者预留 config 对象,用于接收插件开发者的插件函数。

config.eventFns.push(function a() {})
config.eventFns.push(function b() {})
config.eventFns.push(function c() {})

插件开发者传入自定义的插件

对插件的不同调用方式

  • 同步流式
for (let  i = 0; i < config.eventFns.length; i++) {
  config.eventFns[i].call(null)
}

// 同步循环
let flag = true
while (flag) {
  for (let  i = 0; i < config.eventFns.length; i++) {
    if (!config.eventFns[i].call(null)) {
      flag = false
      break
    }
  }
}
  • 异步流式
let result = initValue
function doNext(index) {
  if (index > config.eventFns.length - 1) {
    return
  }
  config.eventFns[index].call(null, result, function(value) {
    result = value
    doNext(index + 1)
  }
}
doNext(0)
  • 异步熔断
function doNext(index) {
  if (index > config.eventFns.length - 1) {
    return
  }
  config.eventFns[index].call(null, function(value) {
    if (value) {
      doNext.bind(null, null, index + 1)
    }
  })
}
doNext(0)

基于Hook思想的封装库

Tapable

封装各种各样的事件间关系,通过暴露基本的两个接口(注册tap、调用call)便于开发者使用。

  • 初始化 (初始化时确定 hook 的类型)
const mySyncHook = new AsyncHook()
  • 注册
mySyncHook.tapAsync('doA-hook', (lastValue) => {
  // do something
})
mySyncHook.tapAsync('doB-hook', (lastValue) => {
  // do something
})
  • 调用
mySyncHook.callASync(initValue).then(result => {
  // ......
})

wepy

  • 注册
this.register('doA-hook', (lastValue) => {
  // do something
})
this.register('doB-hook', (lastValue) => {
  // do something
})
  • 调用 (在调用时确定hook的类型)
this.hookSeq('doA-hook', initValue).then(result => {
  // ......
})

适用场景

可以看到,Hook 思想可以将代码组织的更加合理,更清晰,所以更适合于框架开发以及插件开发的场景。