借模改df开源插件的机会,第一次真正使用hook开发,目的是实现添加多策略自动执行. 此插件涉及的hook有useState, useEffect, useCallback

插件内容

一、首先,整理对比现有插件功能,第一个版本要模改的插件是crawl planets, 第二个版本参考的是range attack

先看第一个版本:

crawl planets提供的功能是根据一些条件,筛选出合适的来源星球和目标星球进行单次攻击。第一版是直接在crawl planets上做了修改,实现多策略循环自动攻击。实现过程就不赘述,我们来看下修改后的插件样子:

image.png

这个插件使用的是原生js实现,缺点很明显—代码太多,样式丑陋。

接下来我们看下用hook实现的开源插件—range attack:

image.png

缺陷:

  1. Planet Type 不能多选
  2. Planet 过滤条件不全
  3. 没有多策略
  4. 没有定时循环执行策略

优点:

  1. Source Planet 和 Target Planet 分开,清晰明了
  2. 交互友好
  3. ui比较美观

二、 综合以上信息,我整理了一份脑图,可当做 To Do List 使用

React Hook初探以及问题记录 - 图3

三、根据脑图中的内容,最后开发完的插件是这样的:

image.pngimage.png

添加与优化的内容有:

  1. 把原来 crawl planets 插件中的 Planet 筛选条件迁移过来
  2. 下拉框单选改为按钮多选
  3. 添加 Plans Information 模块(添加策略)
  4. Executions Times (执行次数) 和 Executions Interval (执行周期) 从 input range 改为按钮点击和可输入的形式
  5. 添加的策略展示在界面上,并可点击X来停止并删除该策略

本次模改df插件学到的hook知识

一、 useState

二、 useEffect

三、 useCallback

遇到的难点

难点1: useState更新问题

useState执行之后不能马上拿到更新后的值。
按照class component写法,setState第二个参数的函数中可以拿到最新的state值以供使用。
查阅hook各种文档后发现,useState之后一般使用useEffet来拿到最新的state值,当然useEffect第二个数组参数要写上这个state,来表示该state改变时,触发这个useEffect,以便执行一些操作。

难点2: 多个计时器问题

由于 plans list 是维护在列表里的,那刚开始按照我的编程思路,可以把计时器对象分别保存在数组的每个 plan 对象来维护,这样不会互相影响。代码如下:

  1. const [planList, setPlanList] = useState(''); // 策略列表
  2. // 添加策略
  3. const onAddPlan = () => {
  4. let arr = [...planList]
  5. arr.push({
  6. name: 'xxx',
  7. times: 10, // 执行次数
  8. interval: 10, // 执行间隔(秒)
  9. timer: null // 计时器对象
  10. })
  11. setPlanList([...arr])
  12. }
  13. useEffect (()=>{
  14. let newPlanList = [...planList]
  15. newPlanList.forEach((planItem, planIndex) => {
  16. if (planItem.times > 1) {
  17. planItem.timer = setTimeout(() => {
  18. planItem.times --
  19. setPlanList([...newPlanList])
  20. }, Number(planItem.executionsInterval) * 1000)
  21. } else {
  22. clearTimeout(planItem.timer)
  23. newPlanList.splice(planIndex, 1)
  24. setPlanList([...newPlanList])
  25. }
  26. })
  27. }, [planList])

此时,单个策略可以正常运行,过程如下:
定时器更新 => 更新planList => useEffect执行 => 定时器更新 => 更新planList => useEffect执行…

但当我添加第二个策略时,此时 planList 长度为2,也就是有两个定时器了,这时候运行过程其实和上面一样,只不过更新 planList => useEffect 执行=> 导致 planList 所有的定时器都重新执行了,甚至执行了 N 次——>定时器已经乱套了….

是的,我犯了低级错误,每次定时任务执行前,应该先清空之前的定时器,此时 useEffect 代码如下:

  1. useEffect (()=>{
  2. let newPlanList = [...planList]
  3. newPlanList.forEach((planItem, planIndex) => {
  4. clearTimeout(planItem.timer) // 新增代码行
  5. if (planItem.times > 1) {
  6. // ...
  7. } else {
  8. // ...
  9. }
  10. })
  11. }, [planList])

这时候解决了定时器执行N次的问题。但还存在一个问题,我每增加一个定时任务,所有的定时器都被初始化了。

真是令人头大且脱发…

我甚至在想,只要定时任务抽成一个组件,每个定时组件内部去控制自己的定时任务,就可以完美解决这个问题了,但我不打算自掏腰包去部署一个cdn来支持我在单文件js引入一个组件来使用。

于是我请教了hook大神—hook怎么管理多个定时任务?大神给出了思路:只需维护一个interval就可以解决我这个问题,我震惊了,并仔细阅读了大神的代码

  1. const doIt = (args) => {
  2. let newPlanList = [...planList]
  3. newPlanList.forEach((planItem, planIndex) => {
  4. const nowTime = new Date().getTime()
  5. if (planItem.times > 1 && Math.floor(Math.abs(planItem.lastModifiedTime - nowTime) / 1000) == planItem.interval) {
  6. planItem.times--
  7. planItem.lastModifiedTime = nowTime
  8. setPlanList([...planList])
  9. }
  10. if (!newPlanList.find(item => item.times > 1)) {
  11. clearInterval(timer)
  12. timer = null
  13. }
  14. })
  15. }
  16. const createInterval = () => {
  17. if (timer) return
  18. timer = setInterval((func, args) => {
  19. func && func(args)
  20. }, 1000, doIt)
  21. }
  22. useEffect(() => {
  23. if (timer) {
  24. clearInterval(timer)
  25. timer = null
  26. }
  27. if (planList.length > 0) {
  28. createInterval()
  29. }
  30. }, [planList])

妙啊!!!我在办公室都喊出声了!用一个每秒执行一次的 interval , 检查每个任务的最后执行时间,来确定是否要再次执行这个任务。这是什么脑子才能想出如此妙哉方法啊…这又一次印证了大学老师说的最多的一句话—编程最重要的是思想…

此时已经完美实现了这个【多策略自动循环执行】的插件功能,撒花