12 changed files with 447 additions and 7 deletions
After Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 161 KiB |
After Width: | Height: | Size: 62 KiB |
After Width: | Height: | Size: 21 KiB |
After Width: | Height: | Size: 7.8 KiB |
@ -0,0 +1,140 @@ |
|||||||
|
# 如何灵活应用布局组件,尽可能的减少DOM数量 |
||||||
|
|
||||||
|
众所周知,DOM数量越多,前端越卡.很多前端者开发者绞尽脑汁,使出浑身解数,就是为了减少dom数量,提升页面性能 |
||||||
|
|
||||||
|
列举一些常见的增加DOM数量的写法以及优化方案 |
||||||
|
|
||||||
|
## 列表节点的层级偏移 |
||||||
|
|
||||||
|
 |
||||||
|
|
||||||
|
```javascript |
||||||
|
const render = () => { |
||||||
|
const layer = this.options.layer; // 表示节点层级 |
||||||
|
|
||||||
|
const items = []; |
||||||
|
|
||||||
|
for (let i = 0; i <= layer; i++) { |
||||||
|
items.push({ |
||||||
|
type: "bi.layout", |
||||||
|
width: 12 |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
items.push({ |
||||||
|
type: "bi.label", |
||||||
|
text: "节点" |
||||||
|
}); |
||||||
|
|
||||||
|
items.push({ |
||||||
|
type: "bi.icon_button", |
||||||
|
title: "按钮" |
||||||
|
}); |
||||||
|
|
||||||
|
return { |
||||||
|
type: "bi.horizontal_adapt", |
||||||
|
items, |
||||||
|
}; |
||||||
|
}; |
||||||
|
``` |
||||||
|
|
||||||
|
这种场景,如果每个层级都需要用一个空的div占据空间,随着树的层级加深,dom数大大增加 |
||||||
|
|
||||||
|
常用解决方案,通过布局`lgap`实现,注意利用el的隔离特性,不要让`lgap`影响到item |
||||||
|
|
||||||
|
有兴趣的可以参考一下FineUI树控件的迭代过程,如何减少DOM |
||||||
|
|
||||||
|
```javascript |
||||||
|
const render = () => { |
||||||
|
const layer = this.options.layer; // 表示节点层级 |
||||||
|
|
||||||
|
const items = []; |
||||||
|
|
||||||
|
for (let i = 0; i <= layer; i++) { |
||||||
|
items.push({ |
||||||
|
type: "bi.layout", |
||||||
|
width: 12 |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
return { |
||||||
|
type: "bi.horizontal_adapt", |
||||||
|
items: [ |
||||||
|
{ |
||||||
|
el: { |
||||||
|
type: "bi.label", |
||||||
|
text: "节点" |
||||||
|
}, |
||||||
|
lgap: layer * 12, |
||||||
|
}, { |
||||||
|
type: "bi.icon_button", |
||||||
|
title: "按钮" |
||||||
|
} |
||||||
|
] |
||||||
|
}; |
||||||
|
}; |
||||||
|
``` |
||||||
|
|
||||||
|
## 垂直流布局的卡片间隔 |
||||||
|
|
||||||
|
 |
||||||
|
|
||||||
|
数值排列的卡片,灰色的间隔,常见的错误是通过DOM来实现分割条 |
||||||
|
|
||||||
|
```javascript |
||||||
|
const render = () => { |
||||||
|
|
||||||
|
return { |
||||||
|
type: "bi.vertical", |
||||||
|
items: [ |
||||||
|
{ |
||||||
|
el: { |
||||||
|
type: "bi.label", |
||||||
|
cls: "bi-card", |
||||||
|
text: "card1" |
||||||
|
}, |
||||||
|
}, { |
||||||
|
type: "bi.layout", |
||||||
|
cls: "bi-background", |
||||||
|
height: 10, |
||||||
|
}, { |
||||||
|
el: { |
||||||
|
type: "bi.label", |
||||||
|
cls: "bi-card", |
||||||
|
text: "card2" |
||||||
|
}, |
||||||
|
}, |
||||||
|
] |
||||||
|
}; |
||||||
|
}; |
||||||
|
``` |
||||||
|
|
||||||
|
如上写法每多一个卡片,都需要一个额外的空DOM |
||||||
|
|
||||||
|
应对这种场景,我们可以给容器统一设置`bi-background`灰色背景,然后对每一个卡片添加`bi-card`样式, 间隔用`bgap`实现. |
||||||
|
|
||||||
|
```javascript |
||||||
|
const render = () => { |
||||||
|
|
||||||
|
return { |
||||||
|
type: "bi.vertical", |
||||||
|
cls: "bi-background", |
||||||
|
items: [ |
||||||
|
{ |
||||||
|
el: { |
||||||
|
type: "bi.label", |
||||||
|
cls: "bi-card", |
||||||
|
text: "card1" |
||||||
|
}, |
||||||
|
bgap: 10, |
||||||
|
}, { |
||||||
|
el: { |
||||||
|
type: "bi.label", |
||||||
|
cls: "bi-card", |
||||||
|
text: "card2" |
||||||
|
}, |
||||||
|
}, |
||||||
|
] |
||||||
|
}; |
||||||
|
}; |
||||||
|
``` |
@ -0,0 +1,96 @@ |
|||||||
|
# 每个布局组件都对应一个DOM节点吗 |
||||||
|
|
||||||
|
答案显然是否定的,那么有没有仔细探究一下,哪些场景不是对应一个DOM呢? |
||||||
|
|
||||||
|
举个例子: 如下布局有什么问题? |
||||||
|
|
||||||
|
```javascript |
||||||
|
const render = () => { |
||||||
|
|
||||||
|
return [ |
||||||
|
{ |
||||||
|
el: { |
||||||
|
type: "bi.vertical", |
||||||
|
items: [ |
||||||
|
{ |
||||||
|
type: "bi.label", |
||||||
|
cls: "bi-card", |
||||||
|
text: "card1" |
||||||
|
} |
||||||
|
] |
||||||
|
}, |
||||||
|
}, { |
||||||
|
type: "bi.absolute", |
||||||
|
invisible: () => !this.model.tipVisible, |
||||||
|
items: [ |
||||||
|
{ |
||||||
|
el: { |
||||||
|
type: "bi.icon_label", |
||||||
|
title: "tip" |
||||||
|
}, |
||||||
|
right: 0, top: 0 |
||||||
|
} |
||||||
|
] |
||||||
|
} |
||||||
|
]; |
||||||
|
}; |
||||||
|
``` |
||||||
|
|
||||||
|
 |
||||||
|
|
||||||
|
观察DOM结构可以发现,`bi.vertical`和`bi.absolute`布局组件共享了一个DOM,且最终布局效果不会产生相互影响. |
||||||
|
|
||||||
|
但是共享一个DOM需要额外注意一点,设置invisible属性时候需要知晓副作用. |
||||||
|
|
||||||
|
如上示例,`bi.absolute`布局 invisible为true的时候,`bi.vertical`也会消失不见. |
||||||
|
原因也很简单: invisible是通过对DOM设置`display:none`样式,自然导致了这种结果. 解决方式也很简单,将invisible控制放到`bi.absolute`组件内部元素 |
||||||
|
|
||||||
|
|
||||||
|
另一个示例,web组件中常用的`bi.iframe`组件 |
||||||
|
|
||||||
|
如下示例: iframe可以正常渲染吗? |
||||||
|
|
||||||
|
```javascript |
||||||
|
class IframeWidget extends BI.Widget { |
||||||
|
props = { |
||||||
|
baseCls: "test-frame" |
||||||
|
}; |
||||||
|
|
||||||
|
render() { |
||||||
|
return { |
||||||
|
type: "bi.iframe", |
||||||
|
src: "http://www.bing.com" |
||||||
|
}; |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
 |
||||||
|
|
||||||
|
答案是不行的,这是为什么呢?这是因为`bi.iframe`组件和当前IframeWidget组件共用一个DOM,父组件控制的node类型为div,覆盖掉了`bi.iframe`设定的iframe类型 |
||||||
|
|
||||||
|
同理bi.image组件也有类似场景 |
||||||
|
|
||||||
|
解决这类问题也很容易,给`bi.iframe`一个专用的DOM即可 |
||||||
|
|
||||||
|
```javascript |
||||||
|
class IframeWidget extends BI.Widget { |
||||||
|
props = { |
||||||
|
baseCls: "test-frame" |
||||||
|
}; |
||||||
|
|
||||||
|
render() { |
||||||
|
return { |
||||||
|
type: "bi.adaptive", |
||||||
|
items: [ |
||||||
|
{ |
||||||
|
type: "bi.iframe", |
||||||
|
src: "http://www.bing.com", |
||||||
|
height: "100%", |
||||||
|
width: "100%", |
||||||
|
} |
||||||
|
] |
||||||
|
}; |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
@ -0,0 +1,21 @@ |
|||||||
|
# effect的妙用 |
||||||
|
|
||||||
|
FineUI组件中text,value,cls,css,selected,items,columnSize,rowSize,invisible,disabled等属性都已经支持了响应式 |
||||||
|
|
||||||
|
但是,总有一些属性没有支持响应式,或者一些组件提供的props并未支持响应式,这时候只能回归`_store`+`watch`了吗? |
||||||
|
|
||||||
|
非也,有effect属性 |
||||||
|
|
||||||
|
```javascript |
||||||
|
const state = Fix.define({ |
||||||
|
state1: 0 |
||||||
|
}); |
||||||
|
const render = () => { |
||||||
|
return { |
||||||
|
type: "bi.my_custom_wdiget", |
||||||
|
effect: (customWidget) => { |
||||||
|
customWidget.customMethod(state.state1); |
||||||
|
} |
||||||
|
}; |
||||||
|
}; |
||||||
|
``` |
@ -0,0 +1,83 @@ |
|||||||
|
# 不用resize实现拖动调整宽高布局 |
||||||
|
|
||||||
|
我们都知道,布局组件有个`resize`方法,以前常用的动态宽度或高度的实现方式大多借用了该方法 |
||||||
|
|
||||||
|
例如可拖动宽度的布局 |
||||||
|
|
||||||
|
```javascript |
||||||
|
class Resizeable extends BI.Widget { |
||||||
|
props = { |
||||||
|
baseCls: "test" |
||||||
|
}; |
||||||
|
|
||||||
|
render() { |
||||||
|
return { |
||||||
|
type: "bi.htape", |
||||||
|
ref: ref => this.container = ref, |
||||||
|
items: [ |
||||||
|
{ |
||||||
|
type: "bi.draggabel", // 假设为拖拽组件 |
||||||
|
text: "left", |
||||||
|
width: 100, |
||||||
|
onDragg: size => this.resize(size), |
||||||
|
}, { |
||||||
|
type: "bi.label", |
||||||
|
text: "right" |
||||||
|
} |
||||||
|
] |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
resize(size) { |
||||||
|
this.container.attr("items")[0].width = size; |
||||||
|
this.container.resize(); |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
多数时候我们将拖动组件绑定在左侧组件上,此时需要监听拖动回调,然后修改容器的items属性,最后调用resize |
||||||
|
|
||||||
|
有没有更简便的方式呢?有的 |
||||||
|
|
||||||
|
FineUI3.0 版本新增的自适应布局,充分利用了flex布局特性,为我们提供了更便捷,更内聚的实现方式 |
||||||
|
|
||||||
|
```javascript |
||||||
|
class Draggable extends BI.Widget { |
||||||
|
props = { |
||||||
|
baseCls: "test" |
||||||
|
}; |
||||||
|
|
||||||
|
render() { |
||||||
|
return { |
||||||
|
type: "bi.draggabel", // 假设为拖拽组件 |
||||||
|
text: "left", |
||||||
|
onDragg: size => this.element.width(size), |
||||||
|
}; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class Resizeable extends BI.Widget { |
||||||
|
props = { |
||||||
|
baseCls: "test" |
||||||
|
}; |
||||||
|
|
||||||
|
render() { |
||||||
|
return { |
||||||
|
type: "bi.horizontal", |
||||||
|
verticalAlign: "stretch", |
||||||
|
columnSize: ["", "fill"], |
||||||
|
items: [ |
||||||
|
{ |
||||||
|
type: Draggable.xtype, |
||||||
|
text: "left", |
||||||
|
}, { |
||||||
|
type: "bi.label", |
||||||
|
text: "right" |
||||||
|
} |
||||||
|
] |
||||||
|
}; |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
利用水平布局自适应的实现方式,左侧为实际宽度,右侧占满剩余宽度. 这时候左侧仅需关注自身宽度,无须考虑与外部联动 |
@ -0,0 +1,99 @@ |
|||||||
|
# 利用bi.loader快速封装分页加载更多列表 |
||||||
|
|
||||||
|
长列表,从后端分页加载,搜索也由后端处理. 非常常见的需求 |
||||||
|
|
||||||
|
用过ButtonGroup的都知道格式化好items,然后直接populate就行了. |
||||||
|
|
||||||
|
Loader也类似,只是他还具有itemsCreator的能力 [bi.loader](https://code.fineres.com/projects/VISUAL/repos/fineui/browse/src/base/combination/loader.js) |
||||||
|
|
||||||
|
```javascript |
||||||
|
class MyList extends BI.Widget { |
||||||
|
|
||||||
|
hasNext = false; |
||||||
|
|
||||||
|
render() { |
||||||
|
return { |
||||||
|
type: "bi.loader", |
||||||
|
itemsCreator: (op, callback) => { |
||||||
|
const times = op.times; // times标记分页码 |
||||||
|
setTimeout(() => { |
||||||
|
callback(this.formatItems(/* some datas */)); |
||||||
|
}); |
||||||
|
}, |
||||||
|
hasNext: () => this.hasNext |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
formatItems(items) { |
||||||
|
// 一般格式化节点常用的状态 |
||||||
|
let keyword; |
||||||
|
let selected; |
||||||
|
let disabled; |
||||||
|
|
||||||
|
return items.map(item => { |
||||||
|
return { |
||||||
|
...item, |
||||||
|
keyword, |
||||||
|
selected, |
||||||
|
disabled, |
||||||
|
}; |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
常见操作手法 |
||||||
|
|
||||||
|
### bi.loader依旧支持items属性,如果非空,会直接渲染出来作为第一页 |
||||||
|
|
||||||
|
|
||||||
|
### bi.loader可以直接调用空的populate方法,其会自动触发itemsCreator请求数据 |
||||||
|
|
||||||
|
利用这种机制可以将数据请求集中化. 无需在筛选条件变化之后,手动去请求来数据,然后再populate到Loader里面 |
||||||
|
|
||||||
|
```javascript |
||||||
|
class MyList extends BI.Widget { |
||||||
|
|
||||||
|
watch = { |
||||||
|
"keyword | filter | perPage": () => { |
||||||
|
this.loader.populate(); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
render() { |
||||||
|
return { |
||||||
|
type: "bi.loader", |
||||||
|
ref: ref => this.loader = ref, |
||||||
|
itemsCreator: (op, callback) => { |
||||||
|
const times = op.times; // times标记分页码 |
||||||
|
requestItems(op.times, callback(/* some datas */)); |
||||||
|
}, |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
requestItems(page, cb) { |
||||||
|
// 一般常用的请求条件 |
||||||
|
let keyword; |
||||||
|
let filter; |
||||||
|
let perPage; |
||||||
|
|
||||||
|
fetchData({ |
||||||
|
page, |
||||||
|
keyword, |
||||||
|
filter, |
||||||
|
perPage, |
||||||
|
}).then(cb); |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
### hasNext的处理怎么才能更优雅 |
||||||
|
|
||||||
|
查阅内部源码,hasNext可以利用count props,也可以由外部回调 |
||||||
|
|
||||||
|
这样就有两种操作姿势 |
||||||
|
|
||||||
|
1. 在itemsCreator回调的时候修改props`this.loader.attr("count",res.allCount)` |
||||||
|
2. 在itemsCreator回调的时候保存状态,hasNext回调给loader `hasNext:() => this.hasNext` |
||||||
|
|
||||||
|
孰优孰劣见仁见智,但有没有更符合开发直觉的方式呢? 例如 `callback({items,hasNext,count})` |
Loading…
Reference in new issue