当 el-upload 遇上 v-for 时应该注意的问题

虽然 element-ui 现在几乎不更新了, 但不能否认它的优秀
而今天要讲的就是这个优秀的框架中优秀的组件 el-upload

有过 element-ui 使用经验的小伙伴大概都用过它的上传组件(el-upload)
单独使用 el-upload 的文档已经很全面, 操作起来也很好用, 没毛病

但当 el-upload 遇上 v-for 时, 要面临的问题就多得多了
笔者就曾遇到过, 特意写下此文

注: 文末附完整 demo


有一次, 我遇到了这样的需求:

  • 有一个数量可增可减的数组
  • 数组每项有上传图片功能



我第一反应, 简单:

<!-- 大概看下就行, 不是完整代码 -->
<div v-for="(item, index) in lists">
	<!--something-->
	<el-upload></el-upload>
	<button>删除</button>
</div>
<button>新建</button>

搞掂



Question 1

经过几个回合的撸码, 该显示的都出来了:

<!-- 大概看下就行, 不是完整代码 -->
<div v-for="(item, index) in lists">
	<el-upload :before-upload="handleBeforeUpload">
		<i class="el-icon-plus"></i>
	</el-upload>
	<el-button type="danger" @click="remove(item, index)">删除</el-button>
</div>
<el-button type="primary" @click="add">新建</el-button>
  • 点两下 “新建”, 新建了两项
  • 选择个图片, 也如愿显示了
  • 删除第一项, 要命! 怎么第二项不见了

这是因为 v-for 少了 key

vue 与 react 不同, 循环里的 key 并不是强制要求, 很多时候列表里缺少 key 并没有任何问题
但 新增删除项 属于必须有 key 的情况



Question 2

因为 handleBeforeUpload 是公共方法, 只传过去的图片, 就不能分辨该图片是来自第几项的
所以给 handleBeforeUpload 带上了个参数 index

<!-- 大概看下就行, 不是完整代码 -->
<div v-for="(item, index) in lists" :key="item.key">
	<el-upload :before-upload="handleBeforeUpload(index)">
		<i class="el-icon-plus"></i>
	</el-upload>
	<el-button type="danger" @click="remove(item, index)">删除</el-button>
</div>
<el-button type="primary" @click="add">新建</el-button>
handleBeforeUpload(file) {
    
    
	console.log(file)
},

不加还好好的, 可加上 index 后 handleBeforeUpload 接收到的 file 就成了 index 的值, 原本的 file 不见了

这是因为, 原始dom数据被 index 覆盖了, 在 handleBeforeUpload() 里就没有 file 的踪影

缺少原始数据, 还不简单, 用 $event 就可以解决了



Question 3

又是一番焦头烂额, 代码改成这样

<!-- 大概看下就行, 不是完整代码 -->
<div v-for="(item, index) in lists" :key="item.key">
	<el-upload :before-upload="handleBeforeUpload($event, index)">
		<i class="el-icon-plus"></i>
	</el-upload>
	<el-button type="danger" @click="remove(item, index)">删除</el-button>
</div>
<el-button type="primary" @click="add">新建</el-button>

可这个时候, 控制台直接就报错了

[VUE WARN]:未在实例上定义属性或方法“$event”,但在呈现期间引用了该属性或方法。通过初始化属性,确保此属性是反应性的,无论是在“数据”选项中,还是对于基于类的组件。

至于啥意思呢? 老实说我不是很懂, 不过以我的经验想, “$event 出问题了”

这时候注意到 :before-upload="", before-upload 并不是触发事件的属性, 它的功能仅仅是把方法传递下去, 所以在这个位置用 $event 是不明智的



Question 4

在这山穷水尽的状况下, 我决定狠一把, 索性就用闭包的思路, 把 el-upload 封装成一个组件来用

<!-- 参考文末的完整 demo -->

思路大概是这样的

  1. 把 el-upload 封装成一个新组件 MyUpload
  2. 把 el-upload 需要的参数, 方法全部传给 MyUpload
  3. 把 index 也传给 MyUpload
  4. 以 before-upload 为例, 给 el-upload 设置 :before-upload=“imgBeforeUpload”
  5. 当 el-upload 触发 before-upload 时, 执行 imgBeforeUpload
  6. imgBeforeUpload 里接受到 file, 这时候只需要用 arguments 和 uploadId 把数据传入 MyUpload 继承下来的 beforeUpload 方法
  7. 最后在父组件中就能取到需要的 file 数据和对应的 index 数据了


总结

  • v-for 要加上 key
  • 如 before-upload, 不能使用 $event, 如果传入数据, 就会把原始数据覆盖
  • 将 el-upload 组件再次封装, 成为新组件, 这样才能既保存原始数据, 又传入自定义数据


完整 demo

<!DOCTYPE html>
<html>

	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
		<meta charset="utf-8">
		<meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
		<title>当 el-upload 遇上 v-for 时应该注意的问题</title>
		<link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/element-ui/2.14.1/theme-chalk/index.min.css">
		<!--<link rel="stylesheet" href="./jvw/element-ui/[email protected]/element-ui.2.4.0.css">-->
	</head>

	<body>

		<div id="swq">
			<App></App>
		</div>

		<script type="text/x-template" id="App-template">
			<div>
				<div v-for="(item, index) in lists" :key="item.key">
					<my-upload :action="ossSign.host" :uploadId="index" :param="param" :fileList="item.faceList" :before-upload="handleBeforeUpload" :onSuccess="handleOnSuccess" :onError="handleOnError"></my-upload>
					<el-button type="danger" @click="remove(item, index)">删除</el-button>
				</div>
				<el-button type="primary" @click="add">新建</el-button>
			</div>
		</script>

		<script type="text/x-template" id="MyUpload-template">
			<div>
				<el-upload :action="action" :limit="1" :data="param" :file-list="fileList" :before-upload="imgBeforeUpload" :on-success="imgSuccess" :on-error="imgError" list-type="picture-card">
					<i class="el-icon-plus"></i>
				</el-upload>
			</div>
		</script>

		<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.5.6/vue.min.js"></script>
		<script src="https://cdn.bootcdn.net/ajax/libs/element-ui/2.14.1/index.js"></script>
		<!--<script src="./jvw/vue/vue.2.5.16.js"></script>-->
		<!--<script src="./jvw/element-ui/[email protected]/element-ui.2.4.0.js"></script>-->

		<script type="text/javascript">
			var MyUpload = {
     
     
				template: "#MyUpload-template",
				props: {
     
     
					action: null,
					param: {
     
     },
					uploadId: null, //接收到的自定义的参数
					beforeUpload: Function,
					onSuccess: Function,
					onError: Function,
					onRemove: Function,
					fileList: null,
				},
				data: function() {
     
     
					return {
     
     }
				},
				methods: {
     
     
					imgRemove() {
     
     
						this.onRemove(...arguments, this.uploadId);
					},
					imgSuccess() {
     
     
						this.onSuccess(...arguments, this.uploadId);
					},
					imgError() {
     
     
						this.onError(...arguments, this.uploadId);
					},
					imgBeforeUpload(file) {
     
     
						this.beforeUpload(...arguments, this.uploadId);
					},
				},
			};
			var App = {
     
     
				template: "#App-template",
				data: function() {
     
     
					return {
     
     
						ossSign: {
     
     
							host: 'https://jsonplaceholder.typicode.com/posts/',
							key: ''
						},

						param: {
     
     },

						lists: [{
     
     
							faceList: [],
							key: this.getRandom(),
						}],

						dialogImageUrl: '',
						dialogVisible: false,
					}
				},
				components: {
     
     
					MyUpload,
				},
				methods: {
     
     
					handleOnSuccess(response, file, fileList, uploadId) {
     
     
						console.log("handleOnSuccess: ", response, file, fileList, uploadId)
					},

					handleOnError(response, file, fileList, uploadId) {
     
     
						console.log("handleOnError: ", response, file, fileList, uploadId)
					},

					handleBeforeUpload(file, uploadId) {
     
     
						console.log("handleBeforeUpload: ", file, uploadId)
					},

					add() {
     
     
						this.lists.push({
     
     
							faceList: [],
							key: this.getRandom(),
						})
					},

					remove(item, index) {
     
     
						this.lists.splice(this.lists.indexOf(item), 1);
					},

					getRandom() {
     
     
						// 生成个临时 key
						return ~~(Math.random() * 10000) + "" + Date.now()
					},

				},
			};
			var vu = new Vue({
     
     
				el: "#swq",
				components: {
     
     
					App: App,
				},
			})
		</script>
	</body>

</html>

//end

猜你喜欢

转载自blog.csdn.net/u013970232/article/details/111217629