缘起
小编其实不是很想写这个,因为源码有啥好些的,但是架不住粉丝的热情,所以就有了今天这篇。今天只有几段关键性的代码,带大家走走思路,剩下的就看大家自己玩了!
el-tree
先说个大菜el-tree,很多人可能觉得el-table才是大菜,但是小编这里想说,两者其实差不了多少,都是自己建个store,把相关数据存store,类似vuex,尤其el-table更明显。
我们直接拿官方文档上的下面这段代码来说话:
<div class="custom-tree-container">
<div class="block">
<p>使用 render-content</p>
<el-tree
:data="data"
show-checkbox
node-key="id"
default-expand-all
:expand-on-click-node="false"
:render-content="renderContent">
</el-tree>
</div>
<div class="block">
<p>使用 scoped slot</p>
<el-tree
:data="data"
show-checkbox
node-key="id"
default-expand-all
:expand-on-click-node="false">
<span class="custom-tree-node" slot-scope="{ node, data }">
<span>{{ node.label }}</span>
<span>
<el-button
type="text"
size="mini"
@click="() => append(data)">
Append
</el-button>
<el-button
type="text"
size="mini"
@click="() => remove(node, data)">
Delete
</el-button>
</span>
</span>
</el-tree>
</div>
</div>
<script>
let id = 1000;
export default {
data() {
const data = [{
id: 1,
label: '一级 1',
children: [{
id: 4,
label: '二级 1-1',
children: [{
id: 9,
label: '三级 1-1-1'
}, {
id: 10,
label: '三级 1-1-2'
}]
}]
}, {
id: 2,
label: '一级 2',
children: [{
id: 5,
label: '二级 2-1'
}, {
id: 6,
label: '二级 2-2'
}]
}, {
id: 3,
label: '一级 3',
children: [{
id: 7,
label: '二级 3-1'
}, {
id: 8,
label: '二级 3-2'
}]
}];
return {
data: JSON.parse(JSON.stringify(data)),
data: JSON.parse(JSON.stringify(data))
}
},
methods: {
append(data) {
const newChild = { id: id++, label: 'testtest', children: [] };
if (!data.children) {
this.$set(data, 'children', []);
}
data.children.push(newChild);
},
remove(node, data) {
const parent = node.parent;
const children = parent.data.children || parent.data;
const index = children.findIndex(d => d.id === data.id);
children.splice(index, 1);
},
renderContent(h, { node, data, store }) {
return (
<span class="custom-tree-node">
<span>{node.label}</span>
<span>
<el-button size="mini" type="text" on-click={ () => this.append(data) }>Append</el-button>
<el-button size="mini" type="text" on-click={ () => this.remove(node, data) }>Delete</el-button>
</span>
</span>);
}
}
};
</script>
对于上面的代码,我们直接看render-content和下面的slot,大家如果觉得不够形象,可以对比官网,其实render-content和slot是相同的,最后渲染也是一致的。下面我们看看源码,
NodeContent: {
props: {
node: {
required: true
}
},
render(h) {
const parent = this.$parent;
const tree = parent.tree;
const node = this.node;
const { data, store } = node;
return (
parent.renderContent
? parent.renderContent.call(parent._renderProxy, h, { _self: tree.$vnode.context, node, data, store })
: tree.$scopedSlots.default
? tree.$scopedSlots.default({ node, data })
: <span class="el-tree-node__label">{ node.label }</span>
);
}
}
上面这段其实就是渲染的源码,位于tree-node.vue,tree-node这个组件其实是节点套节点,也就是我们熟悉的自己调用自己,至于tree.vue则是处理一些公共的事,比如全局store存数据。言归正传,我们直接看return,是不是看到了我们熟悉的renderContent,也就是如果存在renderContent,那么优先回调renderContent,如果不存在,则看看是不是存在slot,即tree.$scopedSlots.default,如果还不存在用node.label渲染。这里涉及一点vue的底层源码,就不过多和大家解释了。
el-table
同理,按照上面的描述,我们先上文档上的代码
<template>
<el-table
:data="tableData"
style="width: 100%">
<el-table-column
prop="date"
label="日期"
width="180">
</el-table-column>
<el-table-column
prop="name"
label="姓名"
width="180">
</el-table-column>
<el-table-column
prop="address"
label="地址">
</el-table-column>
</el-table>
</template>
<script>
export default {
data() {
return {
tableData: [{
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}, {
date: '2016-05-04',
name: '王小虎',
address: '上海市普陀区金沙江路 1517 弄'
}, {
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄'
}, {
date: '2016-05-03',
name: '王小虎',
address: '上海市普陀区金沙江路 1516 弄'
}]
}
}
}
</script>
大家可能会疑问,为啥需要el-table-column,已经有el-table了,其实el-table-column这个组件是通过slot渲染完毕,把数据存在了全局store,同样data也是存在了全局store。然后el-table再利用全局的data和column进行存储,具体大家看下面的代码就好了。
table.vue
// 通过slot运行el-table-column
<div class="hidden-columns" ref="hiddenColumns"><slot></slot></div>
// el-table渲染用的
<table-header
ref="tableHeader"
:store="store"
:border="border"
:default-sort="defaultSort"
:style="{
width: layout.bodyWidth ? layout.bodyWidth + 'px' : ''
}">
</table-header>
<table-body
:context="context"
:store="store"
:stripe="stripe"
:row-class-name="rowClassName"
:row-style="rowStyle"
:highlight="highlightCurrentRow"
:style="{
width: bodyWidth
}">
</table-body>
table-column.js
mounted() {
const owner = this.owner;
const parent = this.columnOrTableParent;
const children = this.isSubColumn ? parent.$el.children : parent.$refs.hiddenColumns.children;
const columnIndex = this.getColumnElIndex(children, this.$el);
// 把column全部存到store
owner.store.commit('insertColumn', this.columnConfig, columnIndex, this.isSubColumn ? parent.columnConfig : null);
},
table-body.js
// 全局的data和column进行渲染
render(h) {
const data = this.data || [];
return (
<table
class="el-table__body"
cellspacing="0"
cellpadding="0"
border="0">
<colgroup>
{
this.columns.map(column => <col name={ column.id } key={column.id} />)
}
</colgroup>
<tbody>
{
data.reduce((acc, row) => {
return acc.concat(this.wrappedRowRender(row, acc.length));
}, [])
}
<el-tooltip effect={ this.table.tooltipEffect } placement="top" ref="tooltip" content={ this.tooltipContent }></el-tooltip>
</tbody>
</table>
);
},