使用类似 Vue3 里的组合式API

现在的前端框架如 Vue 和 React 都推崇组合式API,用起来非常爽,我最近接触了 cocos 后对这种非组合式的写法不禁有些抵触,便借鉴了 Vue 的思路简单实现了类似的效果,开发组各位大佬对这种写法怎么看?
下面是我改造后的一步两步代码
hooks.js 提供组合式的能力

let curUpdateArr = null;

function myClass(config) {

  const { setup } = config;

  const newConfig = Object.assign({}, config)

  const updateArr = [];

  newConfig.update = () => {

    updateArr.forEach(item => item())

  }

  newConfig.onLoad = function () {

    curUpdateArr = updateArr

    let data = setup(this)

    curUpdateArr = null

    for (let key in data) {

      if (typeof data[key] === 'function') {

        this[key] = data[key]

      } else {

        Object.defineProperty(this, key, {

          get() {

            return data[key].value

          },

          set(value) {

            data[key].value = value

          }

        })

      }

    }

  }

  return cc.Class(newConfig)

}

function useState(value) {

  return { value }

}

function update(func) {

  curUpdateArr.push(func)

}

module.exports = { myClass, useState, update };

useTimer.js 封装了一个计时器

const { useState, update } = require("hooks")

function useTimer(initVal = 0) {

  let timer = useState(initVal)

  update(dt => {

    timer.value += dt;

  })

  return {

    timer

  }

}

module.exports = useTimer

Game.js 用组合式API重写了代码,和 Vue 很像 :yum:

const { myClass, useState, update } = require("hooks")

const useTimer = require("useTimer")

module.exports = myClass({

  extends: cc.Component,

  properties: {

    // 地面节点,用于确定星星生成的高度

    ground: {

      default: null,

      type: cc.Node

    },

    // Player 节点,用于获取主角弹跳的高度,和控制主角行动开关

    player: {

      default: null,

      type: cc.Node

    },

    // 这个属性引用了星星预制资源

    starPrefab: {

      default: null,

      type: cc.Prefab

    },

    // 星星产生后消失时间的随机范围

    maxStarDuration: 0,

    minStarDuration: 0,

    // score label 的引用

    scoreDisplay: {

      default: null,

      type: cc.Label

    }

  },

  setup(props) {

    // 初始化计时器

    let { timer } = useTimer()

    let starDuration = useState(0);

    function gameOver() {

      // 停止 Player 节点的跳跃动作

      props.player.stopAllActions();

      // 重新加载场景 game

      cc.director.loadScene('game');

    }

    update(() => {

      // 每帧更新计时器,超过限度还没有生成新的星星

      // 就会调用游戏失败逻辑

      if (timer.value > starDuration.value) {

        gameOver();

      }

    })

    // 获取地平面的 y 轴坐标

    const groundY = props.ground.y + props.ground.height / 2

    function getNewStarPosition() {

      let randX = 0;

      // 根据地平面位置和主角跳跃高度,随机得到一个星星的 y 坐标

      let randY = groundY + Math.random() * props.player.getComponent('Player').jumpHeight + 50;

      // 根据屏幕宽度,随机得到一个星星 x 坐标

      let maxX = props.node.width / 2;

      randX = (Math.random() - 0.5) * 2 * maxX;

      // 返回星星坐标

      return cc.v2(randX, randY);

    }

    function spawnNewStar() {

      // 使用给定的模板在场景中生成一个新节点

      let newStar = cc.instantiate(props.starPrefab);

      // 将新增的节点添加到 Canvas 节点下面

      props.node.addChild(newStar);

      // 为星星设置一个随机位置

      newStar.setPosition(getNewStarPosition());

      // 在星星脚本组件上保存 Game 对象的引用

      newStar.getComponent('Star').game = props;

      // 重置计时器,根据消失时间范围随机取一个值

      starDuration.value = props.minStarDuration + Math.random() * (props.maxStarDuration - props.minStarDuration);

      timer.value = 0;

    }

    // 生成一个新的星星

    spawnNewStar();

    let score = useState(0);

    function gainScore() {

      score.value += 1;

      // 更新 scoreDisplay Label 的文字

      props.scoreDisplay.string = 'Score: ' + score.value;

    }

    return {

      timer,

      starDuration,

      spawnNewStar,

      gainScore

    }

  },

})