首页 > 数据库 > > 一站式前端开发框架Snowball介绍
snowball 是一个一站式前端开发框架,你可以使用snowball轻松构建出一套web app/hybrid app。snowball内置了view层,但同时也支持React。它比React全家桶轻量又支持更多功能,如下:redux不同,snowball的状态管理更符合OOP思想。React、Vue和Angular等框架。React。Controller、Service、View层,Controller层用来组织Service层,并通过injectable注解将数据注入到View层。该路由方案专为多团队协作开发设计,将多个库整合成一个单页应用,让所有业务都使用相同的跳转动画、手势返回、页面缓存。
发布后到业务库共用一份核心库的js/css/image/iconfont,减少下载资源的大小。
一个核心框架库+多个业务库。业务库之间不依赖,可单独发布。
复制代码snowball 统一控制路由,需要在 snowball 中注册需要加载的业务asset-manifest.json文件,snowball 通过路由匹配到业务,并加载manifest中的js和css。registerRoutes({...}) 方法注册子路由snowball 在业务js/css加载完成后,根据业务注册的子路由跳至对应页面。navigation.forward 和 navigation.back 方法来控制页面跳转的动画效果。使用 navigation.forward 跳转页面后,点击浏览器返回上一页会自带返回动画。若无需跳转动画可使用 navigation.transitionTo 方法。手势返回功能,navigation.forward 跳转到新页面之后,左滑页面可返回上一页。render时会监听dom数量,若dom数量超过指定数量(默认20k),会自动umount老页面的dom。snowball的视图层采用专有的模版语言、实时模版编译和fiber模式渲染。视图层接收string类型模版,组件实例化后,snowball会对模版进行实时编译,生成虚拟dom。渲染阶段会对实体dom的生成和变更进行分片渲染,避免界面卡顿。
// 这是一个简单的 `component` 示例
@component({
tagName: 'Order',
template: `<div @click={user.name='new name'}>{user.name}</div>
<ul>
<li sn-repeat="item,i in orderList" @click={this.handleOrder(item, i)}>{i}:{item.tradeCode}</li>
</ul>`
})
class Order extends Model {
handleOrder(item, i) {
console.log(item, i);
}
}
new Order({
user: {
name: 'UserName'
},
orderList: [{
tradeCode: '1234'
}]
}).appendTo(document.body)
复制代码React和Angular等框架。React等框架。fiber模式进行异步渲染,性能好。Model和Collection,Collection类中包含多种常用数组操作方法immutable,数据变更后对比非常方便git clone git@github.com:sorrymeika/snowball.gitcd snowball && npm installnpm run project yourProjectName to create your own projectimport { env, Model } from "snowball"https://github.com/sorrymeika/juicy to get the full example!cd yourProject && npm start to start development server, it'll open the project url in browser automatically!npm run test to run test cases!npm run build to build the production bundle.npm run sprity to build sprity images.http://localhost:3000/dist/#/if you get some error about canvas
brew install pkgconfig if show "pkg-config: command not found"brew install cairo if show "No package 'cairo' found"or
or
canvas module from package.json业务项目打包后会剔除掉`react`,`react-dom`,`polyfill`等框架和框架中的公共组件/公共样式
复制代码snowball会将React等框架注册到 window.Snowball 上snowball-loader, 该loader会将 import React from "react" 替换成 const React = window.Snowball.Reactsnowball 会分大版本(1.x和2.x)和小版本(1.x.x和1.x.x),小版本升级(自动化测试)业务不感知。大版本升级业务需处理。snowball 会尽量保证兼容性。让大版本升级尽量平滑。Controller、Service、View层Controller层用来组织Service层,并通过injectable注解将数据注入到View层import { Model, Collection, Reaction, attributes } from 'snowball';
import { controller, injectable, service, observer } from 'snowball/app';
// Model 的接口必须定义
interface IUser {
userId: number;
userName: string;
}
// Model
class UserModel extends Model {
static defaultAttributes = {
}
attributes: IUser
};
const user = new UserModel({
userName: 'aaa'
});
console.log(user.get(''));
// 可用 Reactive Object 替换 Model
class User implements IUser {
@attributes.number
userId;
@attributes.string
userName;
constructor(user: IUser) {
User.init(this, user);
}
}
// Reaction 需和 Reactive Object 配合使用
// observer 基于 Reaction 实现
const user = new User();
const reaction = new Reaction(() => {
console.log('it works!');
});
reaction.track(() => {
console.log(user.userId);
});
setTimeout(() => {
user.userId = Date.now();
reaction.destroy();
}, 1000);
// Service 的接口必须定义
interface IUserService {
user: IUser;
setUserName(): void;
loadUser(): Promise<IUser>;
}
// Service
@service
class UserService implements IUserService {
constructor() {
this._user = new User();
}
get user() {
return this._user
}
loadUser() {
}
setUserName(userName) {
this.user.userName = userName;
}
}
// observer 组件
@observer(['userService', 'buttonStatus'])
class App extends Component<{ userService: IUserService }, never> {
@attributes.string
ohNo = 'oh, no!!';
ohYes = () => {
this.ohNo = 'oh, yeah!!';
}
render() {
const { userService } = this.props;
return (
<div
onClick={userService.setUserName.bind(null)}
>
{userService.user.userName}
<p onClick={this.ohYes}>{this.ohNo}</p>
</div>
)
}
}
// Controller
@controller(App)
class AppController {
@injectable userService: IUserService;
@injectable buttonStatus;
constructor({ location }) {
this.userService = new UserService();
this.buttonStatus = observable(1);
}
pgOnInit() {
this.userService.loadUser();
}
@injectable
buttonClick() {
this.buttonStatus.set(0);
}
}
复制代码template{expression} 和 sn-属性 来绑定数据<header class="header {titleClass}">这是标题{title}{title?'aaa':encodeURIComponent(title)}</header>
<div class="main">
<h1>{title}</h1>
<ul>
<li>时间:{util.formateDate(date,'yyyy-MM-dd')}</li>
<li>user:{user.userName}</li>
<li>friend:{friend.friendName}</li>
<li sn-repeat="msg in messages">msg:{msg.content}</li>
<li sn-repeat="item in collection">item:{item.name}</li>
</ul>
<sn-template id="item"><li>{name}</li></sn-template>
<ul>
<li sn-repeat="item in list">{item.name}</li>
<sn-item props="{{ name: item.name }}" sn-repeat="item in list"></sn-item>
</ul>
</div>
复制代码sn-[events] dom事件
model.onButtonClick = function(userName) {
alert(userName);
}
// 设置 `model` 的事件代理
model.delegate = {
onButtonClick: function(user) {
alert(user.userName);
}
}
复制代码<div>
<button sn-tap="this.onButtonClick(user.userName)">Click 0</button>
<button sn-tap="delegate.onButtonClick(user)">Click 1</button>
</div>
复制代码sn-repeat 循环var model = new ViewModel(this.$el, {
title: '标题',
list: [{
name: 1,
children: [{
name: '子'
}]
}, {
name: 2
}]
});
复制代码<div class="item" sn-repeat="item,i in list|filter:like(item.name,'2')|orderBy:name asc,id desc,{orderByWhat} {ascOrDesc}">
<p>这是标题{title},加上{item.name}</p>
<ul>
<li sn-repeat="child in item.children|orderBy:this.orderByFunction">{i}/{child.name+child.age}</li>
</ul>
</div>
复制代码[sn-if] [sn-else-if] [sn-else] 条件控制<div class="item" sn-if="{!title}">当title不为空时插入该element</div>
<div class="item" sn-else-if="{title==3}">当title不为空时插入该element</div>
<div class="item" sn-else>当title不为空时插入该element</div>
复制代码sn-display 控件是否显示(有淡入淡出效果,若不需要动画效果可使用sn-visible或sn-if)<div class="item" sn-display="{title}">当title不为空时显示</div>
复制代码sn-html 设置innerHTML<div class="item" sn-html="{title}"></div>
复制代码sn-component 引入其他组建
var model = new ViewModel({
components: {
tab: require('widget/tab')
},
el: template,
delegate: this,
attributes: {
title: '标题',
list: [{
name: 1,
children: [{
name: '子'
}]
}, {
name: 2
}]
}
});
复制代码
<div class="tab" sn-component="tab" sn-props="{{items:['生活服务','通信服务']}}"></div>
或
<sn-tab class="tab" props="{{items:['生活服务','通信服务']}}"></sn-tab>
复制代码vm.Observer 类ViewModel, Model, Collection, List, Dictionary, DictionaryList, Emitter, State 都是 Observer 的子类,分别有不同的作用import { Observer, ViewModel, Model, Collection, List, Emitter, State } from 'snowball';
var viewModel = new ViewModel({
el: `<div>
<sn-template id="item"><li>{name}</li></sn-template>
<h1>{title}</h1>
<ul>
<li sn-repeat="item in list">{item.name}</li>
<sn-item props="{{ name: item.name }}" sn-repeat="item in list"></sn-item>
</ul>
</div>`,
attributes: {
title: '标题',
list: [{
name: '列表'
}]
}
});
var model = new Model({
id: 1,
name: '名称'
});
var collection = new Collection([{
id: 2,
name: '名称2'
}]);
collection.add(model);
collection.add([{ id: 3, name: '名称3' }]);
viewModel.set({
data: model,
list: collection
})
复制代码vm.Model|vm.Dictionary 类Observer 的属性变化不能被监听,Model|Dictionary 的属性变化可被监听Model 是深拷贝,且是 immutable 的,Dictionary 浅拷贝对象,Observer 不拷贝对象可接收值类型vm.List|vm.Collection|vm.DictionaryList 类List 的子项是 Observer,Collection 的子项是 Model,DictionaryList 的子项是 DictionaryList 性能优于 Dictionary 优于 Collectionvar collection = new Collection([{
id: 2,
name: '名称2'
}]);
collection.add(model);
collection.add([{ id: 3, name: '名称3' }]);
// 原数据中ID存在相同的则更新,否则添加
collection.update([{ id: 2, name: '新名称2' },{ id: 3, name: '新名称3' }], 'id');
// 根据ID更新
collection.updateBy('id', { id: 3, name: '新名称' });
// 更换数组
collection.updateTo([{ id: 3, name: '新名称' }], 'id');
复制代码(Observer|...).prototype.get 方法Model.prototype.attributes|Collection.prototype.array 属性(只读)var data = new Model({
id: 1,
name: 'immutable data'
})
// 同等于 data.get()
var oldAttributes = data.attributes;
// 数据无变化
data.set({
id: 1
});
console.log(oldAttributes == data.attributes);
// true
data.set({
name: '数据变化了'
});
console.log(oldAttributes == data.attributes);
// false
console.log(data.get('id'))
// 1
复制代码(Observer|...).prototype.set 方法Model、Collection// 通过 `set` 方法来改变数据
// 此时关联了 `user` 的 `home` 的数据也会改变
// 若原先的 `userName` 已是'asdf',则不会触发view更新
user.set({
userName: 'asdf'
});
home.set({
title: 1,
user: {
age: 10
}
});
// 通过 `collection.set` 方法覆盖数据
// 更新数据使用 `collection.update|updateBy` 等方法性能会更好
collection.set([{
id: 1,
name: 'A'
}]);
复制代码(Observer|...).prototype.observe 方法// 监听所有数据变动
model.observe(function(e) {
});
// Model|Dictionary 可监听 `user` 属性的数据变动
model.observe('user', function(e) {
});
// Model 监听 `user.userName` 属性变动
model.observe('user.userName', function(e) {
});
复制代码(Observer|...).prototype.unobserve 方法(Observer|...).prototype.compute 方法// 计算
var computed = model.compute(({ user, id, homePageId }) => {
return user + id + homePageId;
});
computed.observe((value) => {
});
computed.get();
复制代码Model.prototype.collection(key) 方法model.collection('productList').add([{ id: 1 }]);
复制代码Model.prototype.model(key) 方法home.model('settings').attributes;
复制代码(Collection|Model).prototype._ 方法
/**
* 搜索子Model/Collection,
* 支持多种搜索条件
*
* 搜索子Model:
* model._('user') 或 model._('user.address')
*
* 根据查询条件查找子Collection下的Model:
* model._('collection[id=222][0].options[text~="aa"&value="1"][0]')
* model._('collection[id=222][0].options[text~="aa"&value="1",attr^='somevalue'|attr=1][0]')
*
* 且条件:
* model._("collection[attr='somevalue'&att2=2][1].aaa[333]")
*
* 或条件:
* model._("collection[attr^='somevalue'|attr=1]")
*
* 不存在时添加,不可用模糊搜索:
* model._("collection[attr='somevalue',attr2=1][+]")
*
* @param {string} search 搜索条件
* @param {any} [def] collection[attr='val'][+]时的默认值
*/
home._('collection[name~="aa"|id=1,type!=2]').toJSON();
/**
* 查询Collection的子Model/Collection
*
* 第n个:
* collection._(1)
*
* 查询所有符合的:
* collection._("[attr='val']")
* 数据类型也相同:[attr=='val']
* 以val开头:[attr^='val']
* 以val结尾:[attr$='val']
* 包含val,区分大小写:[attr*='val']
* 包含val,不区分大小写:[attr~='val']
* 或:[attr='val'|attr=1,attr='val'|attr=1]
* 且:[attr='val'&attr=1,attr='val'|attr=1]
*
* 查询并返回第n个:
* collection._("[attr='val'][n]")
*
* 一个都不存在则添加:
* collection._("[attr='val'][+]")
*
* 结果小于n个时则添加:
* collection._("[attr='val'][+n]")
*
* 删除全部搜索到的,并返回被删除的:
* collection._("[attr='val'][-]")
*
* 删除搜索结果中第n个,并返回被删除的:
* collection._("[attr='val'][-n]")
*
* @param {string} search 查询条件
* @param {object} [def] 数据不存在时默认添加的数据
*
* @return {array|Model|Collection}
*/
collection._('[name="aa"]').toJSON();
复制代码Collection.prototype.add 方法// 通过 `collection.add` 方法添加数据
collection.add({ id: 2, name: 'B' })
collection.add([{ id: 3, name: 'C' }, { id: 4, name: 'D' }])
复制代码Collection.prototype.update 方法// 通过 `collection.update` 方法更新数据
collection.update([{ id: 3, name: 'C1' }, { id: 4, name: 'D1' }], 'id');
collection.update([{ id: 3, name: 'C1' }, { id: 4, name: 'D1' }], function(a, b) {
return a.id === b.id;
});
复制代码Collection.prototype.updateTo 方法var arr = [{ id: 3, name: 'C1' }, { id: 4, name: 'D1' }];
// 通过 `collection.updateTo` 方法更新数据
collection.updateTo(arr, 'id');
复制代码Collection.prototype.updateBy 方法var data = [{ id: 3, name: 'C1' }, { id: 4, name: 'D1' }];
/**
* 根据 comparator 更新Model
* collection.updateBy('id', { id: 123 name: '更新掉name' })
* collection.updateBy('id', [{ id: 123 name: '更新掉name' }])
*
* @param {String} comparator 属性名/比较方法
* @param {Object} data
* @param {boolean} renewItem 是否覆盖匹配项
*
* @return {Collection} self
*/
collection.updateBy(id, data, true|false);
复制代码Collection.prototype.unshift 方法collection.unshift({ id: 1 });
复制代码Collection.prototype.splice 方法collection.splice(0,1,[{ id: 1 }]);
复制代码Collection.prototype.size 方法 | Collection.prototype.length 属性Collection.prototype.map 方法Array.prototype.mapCollection.prototype.find 方法collection.find('id', 1);
复制代码Collection.prototype.filter 方法Array.prototype.filterCollection.prototype.remove 方法collection.remove('id', 1);
collection.remove(model);
collection.remove(function(item) {
return true|false;
});
复制代码Collection.prototype.clear 方法Collection.prototype.each 方法Collection.prototype.toArray | Collection.prototype.toJSON 方法(Observer|Model|Collection).prototype.destroyobservableobservable()// 自动根据数据类型生成 observable object
// plainObject对应Model, array对应Collection, 其他对应Observer
const observer = observable(0|{}|[]|'');
// 设置数据
observer.set(1);
// 数据无变化不会触发事件
observer.observe((val) => {
console.log(val);
});
// 移除监听
observer.unobserve((val) => {
console.log(val);
});
// 传入function生成 observable object,它是只读的,不能set
const observer = observable((fn)=>{
document.body.addEventListener('click', fn);
return () => {
document.body.removeEventListener('click', fn);
}
});
复制代码vm.State 类const state = new State();
// 异步设置触发事件,并且会触发3次
state.set(1);
state.set(2);
state.set(3);
console.log(state.get());
// undefined
复制代码vm.Emitter 类const emitter = new Emitter();
// 同步触发事件,并且会触发3次
emitter.set(1);
emitter.set(2);
emitter.set(3);
console.log(emitter.get());
// 3
复制代码vm.attributesclass User {
@attributes.number
userId = 0;
@attributes.string
userName;
@attributes.object
auth;
constructor(data) {
User.init(this, data);
}
}
const user = new User();
user.userId = 1;
user.userName = '张三';
// 监听user
User.observe(user, ()=>{
});
// 监听user.userId
User.observe(user, 'userId', ()=>{
});
// 计算user.userId
User.compute(user, 'userId', (userId)=>{
return 'userId:' + userId;
});
// user to plainObject
User.get(user);
User.set(user, {
userId: 1
});
User.set(user, (userModel) => {
userModel.set({
userId: 10
})
});
for (var key in user) {
console.log(key);
}
// userId
// userName来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/69999831/viewspace-3136952/ ,如需转载,请注明出处,否则将追究法律责任。