分享一个解耦的小功能

在用creator开发中,经常会遇到小功能或者一些小界面,比如说简单的排行榜Item,只有头像Sprite和几个Label。我们当然可以新建个预制和Component来单独处理它们,但这些往往只有几行代码的js多了后我们经常会不想新建文件,而是内嵌在父节点中,比如以下代码,Item单独拿出来只有三行代码

//RankListLayer.js
updateList: function(rankData) {
    for(let i = 0; i < rankData.length; i++) {
        let itemData = rankData[i]

        let rankItem = cc.instantiate(this.rankItemPre)
        rankItem.parent = this.listContent

        rankItem.getChildByName("rank").getComponent(cc.Label).string = itemData.rank
        rankItem.getChildByName("name").getComponent(cc.Label).string = itemData.name
        rankItem.getComponentInChildren(cc.Sprite).spriteFrame = itemData.iconFrame

        //item单独拿出的话
        //rankItem.getComponent("RankItem").updateView(itemData)
    }
}

内嵌在父节点中的话,getChildByNamegetComponentInChildren这种就跟creator中的节点树和节点名耦合性略高了。下面的代码就是要分享的解耦这部分的功能

//为节点添加属性
let NodeAttrStruct = cc.Class({
    name: "NodeAttrStruct",
    properties: {
        keyName: {
            default: "",
            tooltip: "字段名包含Btn,Label,Spr会转为相应的组件,否则为Node"
        },
        valNode: cc.Node,
    }
})

cc.Class({
    extends: cc.Component,
    properties: {
        attrStruct: [NodeAttrStruct],
    },

    onLoad: function() {
        let attrData = {}
        for(let i = 0; i < this.attrStruct.length; i++) {
            let itemStruct = this.attrStruct[i]
            let keyName = itemStruct.keyName
            let valNode = itemStruct.valNode
            if(!valNode) {
                continue
            }
            if(keyName.indexOf("Btn") != -1) {
                attrData[keyName] = valNode.getComponent(cc.Button)
            } else if(keyName.indexOf("Label") != -1) {
                attrData[keyName] = valNode.getComponent(cc.Label)
            } else if(keyName.indexOf("Spr") != -1) {
                attrData[keyName] = valNode.getComponent(cc.Sprite)
            } else {
                attrData[keyName] = valNode
            }
        }
       //使用attr为节点附加属性
        this.node.attr(attrData)
    }
})

以最初的代码为例,挂载此脚本到Rankitem上,添加三个属性值,拖对应节点上去

然后代码就可以变为

updateList: function(rankData) {
    for(let i = 0; i < rankData.length; i++) {
        let itemData = rankData[i]

        let rankItem = cc.instantiate(this.rankItemPre)
        rankItem.parent = this.listContent         //要先添加到节点树触发onLoad

        rankItem.rankLabel.string = itemData.rank       //字段名为挂载的组件上定义的
        rankItem.nameLabel.string = itemData.name
        rankItem.iconSpr.spriteFrame = itemData.iconFrame

        //rankItem.getChildByName("rank").getComponent(cc.Label).string = itemData.rank
        //rankItem.getChildByName("name").getComponent(cc.Label).string = itemData.name
        //rankItem.getComponentInChildren(cc.Sprite).spriteFrame = itemData.iconFrame

        //item单独拿出的话
        //rankItem.getComponent("RankItem").updateView(itemData)
    }
}

其实这样还是有关联,但是字段名相比于节点树结构和节点名来说,几乎可以算是不会修改。算是解耦一半吧。

人类迷惑行为大赏。。。。

这是把一个简单的东西变得更加复杂。

首先从功能模块来看,设定这是个排行榜 ,那么节点命名我估计等到你有了孙子都不会变
一般来说。开发当中节点名都是永远不变的,除非你的功能发生了改动。

再次假设假设这个节点名变换的非常频繁。
那我直接在getChildByName当中每次更改一次就好了。跟在编辑器里面重新拖 valNode 和 keyName 相比,代码改动反而更简单

node的本意就是将ui转化成树状图,父节点跟子节点的关联才是整个核心所在。不可避免产生关联,这也是优势也是劣势。

laya的ui编辑有个功能,就是设定节点的var属性,在代码里可以直接用设定好的变量名来访问节点,可以说非常方便。
我参考这个功能,给节点取有规律的名字,在代码里直接使用,不再getChildByName或find,也不怕节点位置改变。

譬如节点取名$name_lbl,代码里直接申明name_lbl: cc.Label; 在onLoad里调用一次Utils.loadVarFromNode(this.node, ‘$’, this); 意思是遍历一次节点树,把$开头的节点都挂靠在this上。然后就可以直接使用this.name_lbl.string了。

这么做不用拖节点,不用写property,不用担心节点位置改变,不用查找节点和组件。
坏处是为了几个变量,遍历节点树,可能感觉有点不值。另外有些人更喜欢拖放的方式。

我觉得没必要,像这种我就直接children[index]取子节点了,如果你经常变,那没有万金油方法