分析el-upload

分析el-upload

“name”: “element-ui”,
“version”: “2.10.1”,

组件文档: 文档
源码在packages/upload/下: 链接

1
2
3
4
5
6
7
8
|-- upload
|-- index.js
|-- src
|-- ajax.js
|-- index.vue
|-- upload-dragger.vue
|-- upload-list.vue
|-- upload.vue

样式在packages/theme-chalk/src/index.scss

index.vue

整体集成了各组件:
Upload组件
UploadList组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// index.vue

import UploadList from './upload-list';
import Upload from './upload';
// import ElProgress from 'element-ui/packages/progress'; // 这里没有用到ElProgress, 实际在UploadList内才有用到

render(h) {
// ...

const trigger = this.$slots.trigger || this.$slots.default;
const uploadComponent = <upload {...uploadData}>{trigger}</upload>;

return (
<div>
{ this.listType === 'picture-card' ? uploadList : ''}
{
this.$slots.trigger
? [uploadComponent, this.$slots.default]
: uploadComponent
}
{this.$slots.tip}
{ this.listType !== 'picture-card' ? uploadList : ''}
</div>
);
}

Upload组件

upload.vue 藏了一个获取文件所需的<input>,
若需响应拖放, 则在内容外层包一个注册了drag事件的upload-dragger

1
2
3
4
5
6
7
8
<div {...data} tabindex="0" >
{
drag
? <upload-dragger disabled={disabled} on-file={uploadFiles}>{this.$slots.default}</upload-dragger>
: this.$slots.default
}
<input class="el-upload__input" type="file" ref="input" name={name} on-change={handleChange} multiple={multiple} accept={accept}></input>
</div>

$slots.default是上层index.vue传下来的”trigger”

1
2
3
4
5
6
7
8
9
const data = {
class: {
'el-upload': true
},
on: {
click: handleClick,
keydown: handleKeydown
}
};

...data里注册了点击事件和键盘事件(space键和enter键), 转而触发<input>的click事件 this.$refs.input.click()
所以trigger slot里的并不是真正的trigger, 它爹才是, 只不过它爹捕获了冒泡上来的click事件
jsx相关用法见vue官方文档

upload-dragger.vue注册了drag相关事件

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<div
class="el-upload-dragger"
:class="{
'is-dragover': dragover
}"
@drop.prevent="onDrop"
@dragover.prevent="onDragover"
@dragleave.prevent="dragover = false"
>
<slot></slot>
</div>
</template>

onDropthis.$emit('file', e.dataTransfer.files)给上级的upload.vue处理
参考 DataTransfer.files

文件上传

挨个文件调用this.post()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// upload.vue

post(rawFile) {
const { uid } = rawFile;
const options = {
headers: this.headers,
withCredentials: this.withCredentials,
file: rawFile,
data: this.data,
filename: this.name,
action: this.action,
onProgress: e => {
this.onProgress(e, rawFile);
},
onSuccess: res => {
this.onSuccess(res, rawFile);
delete this.reqs[uid];
},
onError: err => {
this.onError(err, rawFile);
delete this.reqs[uid];
}
};
const req = this.httpRequest(options);
this.reqs[uid] = req;
if (req && req.then) {
req.then(options.onSuccess, options.onError);
}
},

httpRequest方法的默认实现在同文件夹的ajax.js里, 也可由<el-upload>http-request属性传入
(element-ui官方文档没写清楚http-request的参数)
http-request

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// ajax.js
// httpRequest的默认实现是以下函数

export default function upload(option) {
// ...
const formData = new FormData();
formData.append(option.filename, option.file, option.file.name);

const xhr = new XMLHttpRequest();
if (xhr.upload) {
xhr.upload.onprogress = function progress(e) {
if (e.total > 0) {
e.percent = e.loaded / e.total * 100;
}
option.onProgress(e);
};
}
xhr.onerror = function error(e) { /* ... */ };
xhr.onload = function onload() { /* ... */ };
xhr.open('post', option.action /* 就是el-upload里传下来的action */, true);
xhr.withCredentials = true;
xhr.setRequestHeader( /* ... */ );
xhr.send(formData);
return xhr;
}

XMLHttpRequest.upload用法见文档

UploadList组件

封装了三种不同显示方式

list-type: “text”
list-type: "text"

list-type: “picture”
list-type: "picture"

list-type: “picture-card”
list-type: "picture-card"