vm_databinding
介绍
vm_databinding是基于typescript的装饰器以mvvm模式开发,实现cocos creator 数据绑定框架。用户子需要关心数据和业务逻辑,操作cocos UI方面的事情交给vm来处理。vm的响应式设计在改变数据的同时,UI也随之改变。
支持的版本
cocos creator 3.x 或2.4.x
灵感的由来
最近开发的项目主要偏数据展示的功能,在UI上会需要大量调用设置sprite、label属性的工作,工作量不少。最近也在学习Typescript的装饰器(Decorator),发现之前装饰器的使用场景非常丰富,给已有的方法和类扩展一些新的行为,比如记录日志,注入调试,大大解放的双手,提高生产力。cocos中@ccclass和@property就是这个运用。知道装饰器这一点并不能完成我的预期, 之前开发插件中,发现Vue这个前端流行框架实现了mvvm的响应式编程。Vue的原理分析,结果是es6 proxy对象,将对象进行转化拦截,get触发track,set时执行trigger更新UI effect。突然头顶上的电灯被点亮了,没错就是它vm_databinding。
解决什么问题
1. 如何达到启动并绑定数据
@vm
这个是一个类的装饰器,像@ccclass一样放在Component顶部。Component生命周期中的onLoad方法是入口,通过重写这个方法给类赋予新的属性,收集属性,完成绑定事件等工作。
@vm
@ccclass("test")
export class test extends Component {
....
}
2. vue中的数据model是data,vm中的model呢
data = {
a:"苹果",
show:1,
...
}
没错也是data,把你需要的数据放在data对象中就好了,更改data对应的数据将响应UI更新。之前有考虑过要不要放在data里,后来发现还是放在里面吧,一是比较方便管理查找,二是proxy代理方便做数据绑定。
@vm
@ccclass("test")
export class test extends Component {
data = {
a:"苹果",
show:1,
color:Color.RED,
...
}
setData(data:any){
this.data.show = data.show;
this.data.a = '香蕉';
}
...
}
3. 数据有了,如何绑定view我们的Sprite或者Label呢
@vbind(handler:string | object)
@property(Label)
aaa: Label = null;
@vbind
绑定UI上的控件,放在属性上面。handler可以字符串或对象或方法。
-
字符串形式
里面可以使绑定组件(Component)的默认属性,比如Label的string,Sprite的spriteFrame等等。和格式
@vm
@ccclass("test")
export class test extends Component {
data = {
a:"苹果",
count:5,
show:1,
color:Color.RED,
icon:'icon/1',//对应assets/resouses/icon/1
...
}
@vbind('a') //绑定data.a 等价 this.goodsName.string='苹果'
@property(Label)
goodsName: Label = null;
@vbind('${count}个')// 绑定data.count 等价 this.count.string='5个'
@property(Label)
count: Label = null;
@vbind("icon")//绑定data.icon 等价 this.sprite1.spriteFrame = load('icon/1')
@property(Sprite)
sprite1: Sprite = null;
...
}
-
方法形式
如果你有自己的特殊需求,比如,有逻辑控制的显示内容。可以这样用
@vm
@ccclass("test")
export class test extends Component {
data = {
state:1,
...
}
@vbind(function(){
return(this.data.state == 0)?"闲置":"生产中"
}) //绑定data.state 等价 this.stateLabel.string = (this.data.state == 0)?"闲置":"生产中"
@property(Label)
stateLabel: Label = null;
@vbind((t:test)=>(t.data.state == 0)?"闲置":"生产中")
@property(Label)
stateLabel: Label = null;
...
}
-
对象形式
如果组件多个属性需要绑定数据时,可以这样用
@vm
@ccclass("test")
export class test extends Component {
data = {
name:"苹果",
count:5,
color:Color.RED,
fontSize:18,
icon:'icon/1',
opacity:100,
angle:80,
scale:new Vec3(2,1,2),
...
}
@vbind({
spriteFrame: "icon",
'UIOpacity.opacity': 'opacity',
angle: 'angle',
scale: 'scale',
})
@property(Sprite)
sprite2: Sprite = null;
@vbind({
'LabelOutline.color':'color',
string: "我有物品${name}:${count}个",
color: "color",
fontSize: (t:test)=>t.data.fontSize+1
})
@property(Label)
lable6: Label = null;
...
}
看上去是不是非常简单,配置一下绑定关心就可以了!
4. 常用的点击事件有吗有的。
/**
* 绑定点击事件
* @param {string | function } handler 处理点击事件的方法名称或者方法
* @param {any} tag 用户自定义数据
* @returns
*/
vclick(handler: any, tag?: any)
@vclick
绑定点击事件,可以传入当前类的方法或者匿名方法。点击触发
@vm
@ccclass("test")
export class test extends Component {
data = {
name:"苹果",
isshow:false,
...
}
@vclick("clickBtn", 1)
@property(Button)
addBtn: Button = null;
@vclick("clickBtn", 2) //绑定this.clickBtn 传入自定义值 2
@property(Node)
removeBtn: Node = null;
//传入方法
@vclick(function(b,data){ //函数绑定当前this 可以用this调用
log(`click btn ${b.name}-- ${data}`)
this.data.isshow = !this.data.isshow;
}, 2)
@property(Node)
btn: Node = null;
/**
* @param {Button} b 按钮
* @param {any} data 用户自定义数据
*/
clickBtn(b, data) {
if (data == 1) {
//add
} else if (data == 2) {
//remove
}
}
...
}
5. 有些组件需要双向绑定,比如Slider的progress改变啦,绑定数据会更新吗
当然会,有交互的有Slider、Toggle、EditBox都会双向绑定的。注意,应为是双向绑定这些组件只支持string类型的形式,不支持function和复杂表达式。
@vm
@ccclass("test")
export class test extends Component {
data = {
progress:0.1,
checked:true,
content:'',
...
}
@vbind("progress") //默认绑定 progress属性
@property(Slider)
slider: Slider = null;
@vbind("checked")//默认绑定isChecked属性
@property(Toggle)
toggle: Toggle = null;
@vbind("content")//默认绑定string属性
@property(EditBox)
editBox: EditBox = null;
@vbind({ progress: "progress" })
@property(Slider)
slider2: Slider = null;
@vbind({ isChecked: "checked" })
@property(Toggle)
toggle2: Toggle = null;
@vbind({ string: "content" })
@property(EditBox)
editBox2: EditBox = null;
...
}
6. @property
这个修饰器还需要UI上去拖拽对应node,有简单的方法直接找到吗
/**
* 通过 属性名称 或者标签查询对应的node 或组件
*
* @vsearch(Sprite) //找到名称为icon的组件
* icon:Label = null;
*
* @vsearch(Label,"aa") //找到UI名称为aa的组件
* version:Label = null;
*
* @param className 组件类型
* @param tag 名称
* @returns
*/
vsearch(className: any, tag?: string)
有通过@vsearch
可以方便找到对应属性名称的节点或直接找到组件,最要UI名称对应的上 名称唯一,名称唯一,名称唯一就能找到(重要的话说三千遍)。这样就不用拖拽了,当然如果是数组形式的还是用 @property
吧 数组形式目前不支持@vbind
@vm
@ccclass("test")
export class test extends Component {
data = {
progress:0.1,
checked:true,
content:'',
...
}
@vbind("progress")
@vsearch(Slider) //替换property 找名称为slider的组件,名称唯一
slider: Slider = null;
@vbind("checked")
@vsearch(Toggle)
toggle: Toggle = null;
@vbind("content")
@vsearch(EditBox)
editBox: EditBox = null;
@vsearch([EditBox]) //不支持vbind
editBox: EditBox[] = null;
...
}
7. ScrollView,Layout 填数据好麻烦呀有没有简单的方法
/**
*循环添加预制件到容器中,一般是ScrollView Layout ToggleGrop
*@example
* @vfor({ prefab: "itemPrefab", component: Item, data: "goodsList" })
* @property(Node)
* content: Node = null;
*
* @param {object} handler 配置信息
* @param {string} handler.prefab 预制体名称
* @param {string} handler.component 预制体脚本名称
* @param {string} handler.data 预制体数据数组
* @returns
*/
vfor(handler: vforType)
有的,@vfor
可以帮你简化向UI容器里添加元素,数据的更改也会更新UI容器的内容。
物品数据
export interface Goods{
name:string,
count:number,
icon:string
price?:number
}
容器元素脚本
@vm
@ccclass("Item")
export class Item extends Component {
data = {
prefabData:null,
};
@vbind("${prefabData.name}")
@vsearch(Label)
goodsName:Label = null;
@vbind("${prefabData.price}元")
@vsearch(Label)
goodsPrice:Label = null;
@vbind("${prefabData.count}个")
@vsearch(Label)
goodsCount:Label = null;
@vbind("${prefabData.icon}")
@vsearch(Sprite)
goodsIcon:Sprite = null;
onLoad(){
}
//必须有的方法
setData(goods:Goods){
this.data.prefabData = goods
}
...
}
@vm
@ccclass("test")
export class test extends Component {
data = {
goodsList:null, //物品数组
...
}
@property(Prefab)
itemPrefab: Prefab = null;
/**
* prefab绑定预制件 itemPrefab
* component 绑定脚本 Item
* data 绑定this.data.goodsList
*/
@vfor({ prefab: "itemPrefab", component: Item, data: "goodsList" })
@property(Node)
content: Node = null;
setData(){
this.data.goodsList = model.getGoodsList();//网络服务器或者本地获取物品列表
}
}
这么好用的mvvm数据绑定框架为什么不试试呢?
版本
1.0
使用
-
clone on github https://github.com/baibai2013/vm_databinding
clone on gitee https://gitee.com/bobai/ccc_databinding
-
move assets/lib to your project.
-
enjoy it!
tips:
If you are using version 2.4.x,use 2.4.x/assets/lib.
待解优化
- 全局的vm,目前只是绑定局部的数据data之后绑定全局的model可能会更好