vue.js,这个货,就是尤大,写的这个,前端框架,网上文章,一大堆,什么教程都有,所以我就不讲那些教学的东西了,我只是举一些例子,让你更容易的去理解一些重点,而不是只会照葫芦画瓢,若举一而反不了三,那还是没有理解其中的一些原理。
vue.js 文档 https://cn.vuejs.org/v2/guide/ 写的是很容易上手,且不管你有没有看懂第一章,请把所有的内容都过一遍。当你把所有的内容过完了,你在上手的时候,遇到疑问,你就有印象:“这个在文档里好像有,我去瞜一眼”,“这我在文档的某一章节见到过,去确认一下”。换成其他的新上手的技术也是一样,文档先通读,不用管有没有懂,只要有印象,再回看的时候,能够迅速定位到就行。这货,就那点儿东西,首先,别把请求的东西算进来,那跟它没关系,请求就是请求。基本语法没啥好说的,至于组件,props,$emit,就俩东西,这俩盘好了,就是组件。props 你这么理解,你用js也好,用php也好,用java也好,你在定义一个类的时候,给这个类定义了一些成员变量,props 就好比这些成员变量,这些成员变量可以定义类型,可以给它默认值,通过构造函数给他们初始化赋值,当你实例化这个类的时候,需要有的成员变量必须传值,有的不需要。想一想你看的文档中或者你的实践中,props 是不是这个样子,可以定义默认值,可以定义类型,可以定义是否必须。当调用组件的时候,给props传值。是的,你就当这个组件是一个类,props 是就是成员变量,那 $emit 是什么呢?$emit 是钩子,是事件,是回调,我还没有找到一个很合适的词来形容。这个怎么理解呢?我们就举一个上传图片的例子来讲。
/** * uploader.vue * 上传图片组件 */<template> <input type="file" @change="uploadImage" />template><script> export default { methods: { // 上传方法 uploadImage() { // 开始上传事件 ... this.$emit('start') // 上传成功事件 ... this.$emit('success', data) // 上传失败事件 ... this.$emit('failed', error) } } }script>/** * 调用组件 */<template><uploader @start="uploadStart" @success="uploadSuccess" @failed="uploadFailed" />template><script> export default { methods: { // 开始上传事件 uploadStart(){}, // 上传成功事件 uploadSuccess(data){}, // 上传失败事件 uploadFailed(error){} } }script>
/**
* uploader.vue
* 上传图片组件
*/
<template>
<input type="file" @change="uploadImage" />
template>
<script>
export default {
methods: {
// 上传方法
uploadImage() {
// 开始上传事件
...
this.$emit('start')
// 上传成功事件
...
this.$emit('success', data)
// 上传失败事件
...
this.$emit('failed', error)
}
}
}
script>
/**
* 调用组件
*/
<template>
<uploader @start="uploadStart" @success="uploadSuccess" @failed="uploadFailed" />
template>
<script>
export default {
methods: {
// 开始上传事件
uploadStart(){},
// 上传成功事件
uploadSuccess(data){},
// 上传失败事件
uploadFailed(error){}
}
}
script>
就像上面这段伪代码一样,你在组件里定义一个 $emit('foo'),在调用组件的时候,就 @foo="funForFoo"。$emit 组件里某个事件发生的时候,将会通知外面(父组件),父组件接收事件进行处理。就是这样,没有很麻烦。当然,组件不止这些,但其他的部分还比较好理解,这两个好好理解了,你再去看看别人写的各种轮子,其实没啥,你也可以。再就是路由router和数据可持久vuex这些,路由没啥好说的,你就记住是拿h5的history的state那几接口来实现的。而数据持久化,vuex,我曾在我的群里给小伙伴们说过,就是类的思想嘛,state 成员变量,mutations 是 set 函数,getters 是 get 函数,actions 是调用 set 函数的其他函数,是吧,好理解吧。如果你有通读完 vuex 的文档,你会发现我上面举的类的例子来解释 vuex,非常的相似,就是那么回事儿,找到这个关系了,前端数据持久化,你自己也可以搞。接着说一下请求,这个呢,跟 vue.js 没关系,它就是请求,你用 xml http request 行不行?可以,你用 axios 行不行?可以,你用 fetch 行不行?可以,你用 jq ajax 也可以,它不存在跟 vue.js 有什么绑定关系,不要迷糊。最后甩一段代码,当 Vue 单页应用,一个页面请求未完全结束就被调走到其他路由的时候,需要把前一个页面的未完成的请求,全部干掉,不影响当前路由的请求,axios cancel token 配置。
/** * 关于 axios cancel token 的配置 * 当 Vue 单页应用,一个页面请求未完全结束就被调走到其他路由的时候 * 需要把前一个页面的未完成的请求,全部干掉,不影响当前路由的请求 */import Vue from 'vue'import App from './App.vue'import router from './router'// Axios configurationwindow.axios = require('axios')// ... other config// Axios request interceptors// 定义 cancel tokenlet requestSource = { token: null, cancel: null }const CancelToken = window.axios.CancelToken// 打断 axios 请求,当 config.cancelToken 有值的时候,请求会被 cancel 掉window.axios.interceptors.request.use(config => { config.cancelToken = requestSource.token return config}, error => { return Promise.reject(error)})// Router Before Each Configuration// 在进入每个新路由之前,给 axios 的 cancel token 赋值router.beforeEach((to, from, next) => { requestSource.cancel && requestSource.cancel() requestSource = CancelToken.source() next()})new Vue({ router, render: h => h(App)}).$mount('#app')
我再甩一段,更好的辅助你理解上面说的组件 props 和 $emit。是一段 gist, 我比较懒,懒得封成轮子,下面的是单文件上传。当然还有多文件上传,有需要可以关注我,找我要。下面这段是封的基于 ElementUI,单次-单文件-分片-直传-阿里云OSS的组件。
:class="'w-' + size + ' h-' + size" class="upload-card hover:border-blue-300 hover:shadow" action="" name="file" accept="images/*" :show-file-list="false" :before-upload="beforeUpload" :http-request="onUpload" :on-progress="onProgress" :on-success="onSuccess" :on-error="onError" > "circle" :percentage="percent" v-if="uploading"> "contain" :src="img ? oss + img : image" v-else-if="image || img"> class="'w-' + size + ' h-' + size" class="upload-plus el-icon-plus" v-else></i> export default { name: "ImageUpload", data() { return { oss: process.env.MIX_ALIYUN_OSS_URL, uploading: false, percent: 0, object: null, image: null, }; }, props: { size: { type: Number, default: 32 }, path: { type: String, default: 'image' }, img: { type: String, default: null, } }, methods: { beforeUpload(file) { const isIMG = file.type.substr(0, 6) === "image/"; const isLt4M = file.size / 1024 / 1024 < 4; if (!isIMG) { this.$message.error("上传只能是图片格式!"); } if (!isLt4M) { this.$message.error("上传大小不能超过 4MB!"); } this.uploading = true; return isIMG && isLt4M; }, async onUpload(option) { let file = option.file; try { let object = this.generateObject(file.name); await ossClient.multipartUpload(object, file, { progress: (p, checkpoint) => { option.file.percent = Math.floor(p * 100); option.onProgress(option.file); } }); option.file.response = { object: object }; return option.file.response; } catch (err) { option.file.err = err; return option.file.err; } }, generateObject(fileName) { let i = fileName.lastIndexOf("."); let suffix = fileName.substr(i + 1); let name = _.generateKey(16); return `/${this.path}/${name}.${suffix}`; }, onProgress(event, file, fileList) { this.percent = event.percent; }, onSuccess(response, file, fileList) { this.object = response.object; this.image = URL.createObjectURL(file.raw); this.uploading = false; this.$message.success(`${file.name} 上传成功`); this.$emit("uploadSuccess", this.object); }, onError(err, file, fileList) { this.image = null; this.object = null; this.uploading = false; this.$message.error(`${file.name} 上传失败`); this.$emit("uploadError"); } }};>.upload-card { @apply flex items-center justify-center overflow-hidden rounded-lg bg-white cursor-pointer border border-dashed border-gray-300;}.upload-plus { @apply flex items-center justify-center text-4xl text-gray-500;}.upload-card .el-upload { display: flex !important; align-items: center !important; justify-content: center !important;}
:class="'w-' + size + ' h-' + size"
class="upload-card hover:border-blue-300 hover:shadow"
action=""
name="file"
accept="images/*"
:show-file-list="false"
:before-upload="beforeUpload"
:http-request="onUpload"
:on-progress="onProgress"
:on-success="onSuccess"
:on-error="onError"
>
"circle" :percentage="percent" v-if="uploading">
"contain" :src="img ? oss + img : image" v-else-if="image || img">
class="'w-' + size + ' h-' + size" class="upload-plus el-icon-plus" v-else></i>
export default {
name: "ImageUpload",
data() {
return {
oss: process.env.MIX_ALIYUN_OSS_URL,
uploading: false,
percent: 0,
object: null,
image: null,
};
},
props: {
size: {
type: Number,
default: 32
},
path: {
type: String,
default: 'image'
},
img: {
type: String,
default: null,
}
},
methods: {
beforeUpload(file) {
const isIMG = file.type.substr(0, 6) === "image/";
const isLt4M = file.size / 1024 / 1024 < 4;
if (!isIMG) {
this.$message.error("上传只能是图片格式!");
}
if (!isLt4M) {
this.$message.error("上传大小不能超过 4MB!");
}
this.uploading = true;
return isIMG && isLt4M;
},
async onUpload(option) {
let file = option.file;
try {
let object = this.generateObject(file.name);
await ossClient.multipartUpload(object, file, {
progress: (p, checkpoint) => {
option.file.percent = Math.floor(p * 100);
option.onProgress(option.file);
}
});
option.file.response = { object: object };
return option.file.response;
} catch (err) {
option.file.err = err;
return option.file.err;
}
},
generateObject(fileName) {
let i = fileName.lastIndexOf(".");
let suffix = fileName.substr(i + 1);
let name = _.generateKey(16);
return `/${this.path}/${name}.${suffix}`;
},
onProgress(event, file, fileList) {
this.percent = event.percent;
},
onSuccess(response, file, fileList) {
this.object = response.object;
this.image = URL.createObjectURL(file.raw);
this.uploading = false;
this.$message.success(`${file.name} 上传成功`);
this.$emit("uploadSuccess", this.object);
},
onError(err, file, fileList) {
this.image = null;
this.object = null;
this.uploading = false;
this.$message.error(`${file.name} 上传失败`);
this.$emit("uploadError");
}
}
};
>
.upload-card {
@apply flex items-center justify-center overflow-hidden rounded-lg bg-white cursor-pointer border border-dashed border-gray-300;
}
.upload-plus {
@apply flex items-center justify-center text-4xl text-gray-500;
}
.upload-card .el-upload {
display: flex !important;
align-items: center !important;
justify-content: center !important;
}