zsmj
2 years ago
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数量的写法以及优化方案 |
||||
|
||||
## 列表节点的层级偏移 |
||||
|
||||
![示例1](../images/51.png) |
||||
|
||||
```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: "按钮" |
||||
} |
||||
] |
||||
}; |
||||
}; |
||||
``` |
||||
|
||||
## 垂直流布局的卡片间隔 |
||||
|
||||
![示例2](../images/52.png) |
||||
|
||||
数值排列的卡片,灰色的间隔,常见的错误是通过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 |
||||
} |
||||
] |
||||
} |
||||
]; |
||||
}; |
||||
``` |
||||
|
||||
![示例](../images/54.png) |
||||
|
||||
观察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" |
||||
}; |
||||
} |
||||
} |
||||
``` |
||||
|
||||
![示例](../images/55.png) |
||||
|
||||
答案是不行的,这是为什么呢?这是因为`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