本篇文章主要是描述tree组件的懒加载实现,没看第一篇文章的请移步Element源码之tree组件分析
1.思路:
首先我们想一下树的懒加载肯定需要一个是否开启懒加载机制的字段,element-ui中定义为lazy,然后就是加载过程,也就是获取数据并根据数据插入到树型结构中,这个过程肯定是组件内部实现的,因为上篇笔记已经讲述过了树结构的构造(非懒加载),如果这个时候让使用组件的人去操作这个过程的话肯定有问题,也是不合理的。我们设计组件的时候应该尽量把组件设计实现细节封装起来,用户操作的时候只需要传对应的属性或数据进去。
当我们获取到数据(一般是调用后台接口)的时候,就把数据传进去。
文件分析还是从tree.vue开始
1.1 tree.vue
代码我就不贴全了,上篇文章是有的。我就贴新增的部分
props: {
...
lazy: {
type: Boolean,
default: false,
},
load: Function,
}
this.store = new TreeStore({
key: this.nodeKey,
data: this.data,
lazy: this.lazy, // 新增
props: this.props,
load: this.load, // 新增
});
tree.vue组件主要是在props定义lazy属性和load函数,并在创建TreeStore实例的时候把对应的值传进去。
1.2 tree-store.js
if (this.lazy && this.load) {
const loadFn = this.load;
loadFn(this.root, (data) => {
this.root.doCreateChildren(data);
});
}
这段代码乍一看好像有点懵逼,不要急,我们看下官方文档。
load方法传参第一个值是node节点, 第二个是一个回调,数据就是从这里传进去。参考写法示例:
loadNode(node, resolve) {
if (node.level === 0) {
return resolve([{ name: 'region' }]);
}
if (node.level > 1) return resolve([]);
setTimeout(() => {
const data = [{
name: 'leaf',
leaf: true,
}, {
name: 'zone',
}];
resolve(data);
}, 500);
},
接下来我们理下思路,当我们执行resolve方法的时候,就把数据传入loadFn方法的第二个参数中,data为形参,并执行this.root.doCreateChildren(data)方法。
doCreateChildren方法就是在当前节点下面创建子节点。
doCreateChildren(array, defaultProps = {}) {
array.forEach((item) => {
this.insertChild(objectAssign({ data: item }, defaultProps), undefined, true);
});
}
此段代码就是在当前节点下循环插入子节点。
好了,这只是第一步,只完成了根节点的渲染,当我们展开树结构的时候也是需要根据传进来的数据来插入子节点的。
在上文有个node.js 文件中有个expand函数,当展开的时候就会触发它,上篇文章我们只是改变了expanded状态来控制树节点的显隐。如果是懒加载的话,需要先进行数据加载,再设置expanded状态。
// 懒加载属性为true并且有定义加载函数 并且没有加载完毕 就需要加载数据
shouldLoadData() {
return this.store.lazy === true && this.store.load && !this.loaded;
}
expand() {
const done = () => {
this.expanded = true;
};
// 懒加载
if (this.shouldLoadData()) {
this.loadData((data) => {
done();
});
} else {
done(); // 非懒加载
}
}
接下来的重点就是loadData方法实现。
loadData(callback, defaultProps = {}) {
if (this.store.lazy === true && this.store.load && !this.loaded && (!this.loading || Object.keys(defaultProps).length)) {
this.loading = true;
const resolve = (children) => {
this.childNodes = [];
this.doCreateChildren(children, defaultProps);
this.loaded = true;
this.loading = false;
this.updateLeafState();
if (callback) {
callback.call(this, children);
}
};
this.store.load(this, resolve);
} else {
if (callback) {
callback.call(this);
}
}
}
上述代码通过执行this.store.load(this, resolve);把数据插入进去,那么有人就有疑问了,这里是怎么判断children数据的呢。不要急,先看下loadNode方法里是有根据node节点的层级塞数据的,而我们在插入数据的时候也就是执行insertChild方法的时候就把当前level + 1了,所以,这样就能保证children数据能对应上相应的level。
最后要注意的是要更新节点状态。
// 构造函数新增部分
const props = store.props;
if (props && typeof props.isLeaf !== 'undefined') {
const isLeaf = getPropertyFromData(this, 'isLeaf');
if (typeof isLeaf === 'boolean') {
this.isLeafByUser = isLeaf;
}
}
updateLeafState() {
// 新增
if (this.store.lazy === true && this.loaded !== true && typeof this.isLeafByUser !== 'undefined') {
this.isLeaf = this.isLeafByUser;
return;
}
const childNodes = this.childNodes;
if (!this.store.lazy || (this.store.lazy === true && this.loaded === true)) {
this.isLeaf = !childNodes || childNodes.length === 0;
return;
}
this.isLeaf = false;
}
如果传过来的数据设置了isLeaf为true,则需要更新节点状态。