公司决定项目开发全部转向Cocos Creator 作为学习开发了15个月Layabox的自然而然要切换引擎,带来的是一些新的挑战(入坑)和学习(填坑)的机会,我重新打开之前的学习整理的Cocos的资料,牛人博客和论坛上大家写的教程和讨论的话题,虽然这些学习不难,但时间紧,任务重,为了快速进入状态,还是有些迫切的,读到官方文档,关于循环引用和属性延迟这个小节,当时有些疑惑,查阅了一些资料,觉得值得记录一下。
Cocos 官方是这样解释和给出方法
属性延迟定义
如果两个类相互引用,脚本加载阶段就会出现循环引用,循环引用将导致脚本加载出错:
Game.js
1
2
3
4
5
6
7
8
9
10
11
12 var Item = require("Item");
var Game = cc.Class({
properties: {
item: {
default: null,
type: Item
}
}
});
module.exports = Game;Item.js
1
2
3
4
5
6
7
8
9
10
11
12 var Game = require("Game");
var Item = cc.Class({
properties: {
game: {
default: null,
type: Game
}
}
});
module.exports = Item;上面两个脚本加载时,由于它们在 require 的过程中形成了闭环,因此加载会出现循环引用的错误,循环引用时 type 就会变为 undefined。
因此我们提倡使用以下的属性定义方式:
Game.js
1
2
3
4
5
6
7
8
9
10 var Game = cc.Class({
properties: () => ({
item: {
default: null,
type: require("Item")
}
})
});
module.exports = Game;Item.js
1
2
3
4
5
6
7
8
9
10 var Item = cc.Class({
properties: () => ({
game: {
default: null,
type: require("Game")
}
})
});
module.exports = Item;这种方式就是将 properties 指定为一个 ES6 的箭头函数(lambda 表达式),箭头函数的内容在脚本加载过程中并不会同步执行,而是会被 CCClass 以异步的形式在所有脚本加载成功后才调用。因此加载过程中并不会出现循环引用,属性都可以正常初始化。
箭头函数的用法符合 JavaScript 的 ES6 标准,并且 Creator 会自动将 ES6 转义为 ES5,用户不用担心浏览器的兼容问题。
你可以这样来理解箭头函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 // 箭头函数支持省略掉 `return` 语句,我们推荐的是这种省略后的写法:
properties: () => ({ // <- 箭头右边的括号 "(" 不可省略
game: {
default: null,
type: require("Game")
}
})
// 如果要完整写出 `return`,那么上面的写法等价于:
properties: () => {
return {
game: {
default: null,
type: require("Game")
}
}; // <- 这里 return 的内容,就是原先箭头右边括号里的部分
}
// 我们也可以不用箭头函数,而是用普通的匿名函数:
properties: function () {
return {
game: {
default: null,
type: require("Game")
}
};
}
我自己用的是typescript项目 查阅得到的两个答案:
查阅结果
ts版本下的表现形式代码部分(无报错)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 // A.ts
import { B } from "./B";
const { ccclass, property } = cc._decorator;
export class A extends cc.Component {
(B)
x: B = null
}
// B.ts
import { A } from "./A";
const { ccclass, property } = cc._decorator;
export class B extends cc.Component {
(A)
x: A = null
}特别说明:
在ts中,
import
语句是没有循环引用问题的,因为import
输出的是一个值的引用,只有在具体使用时才会去取值,在这篇文章中有更具体的描述http://es6.ruanyifeng.com/#docs/module-loader#ES6-模块与-CommonJS-模块的差异。但是通过
@property()
定义的属性,是直接通过编辑器初始化的,相当于直接使用了,则会造成循环引用的问题。
解决方案
设计好的分层,避免循环引用(老项目要重构许久)
传入cc.Node,在onLoad()中实现属性的延迟定义
这个方法就比较简单了,逻辑也比较顺畅,很适合新手去使用,实现方案如下所示:import { A } from "./A"; const { ccclass, property } = cc._decorator; @ccclass export class B extends cc.Component { @property(cc.Node) x: A = null onLoad() { this.x = this.x.getComponent(A) } }
通过箭头函数实现属性的延迟定义(和cocos 官方不谋而合)
后记
写代码尽量保持单向引用文件,如果不能引用的可以通过拆分重新设计结构,其实,后面的方法都是亡羊补牢,真正好的方法是避免灾难的发生。
另外关于阮一峰博客的文章由于时间关系没有读太细,后续我会再认真读一下,把理解写出来。