Vue2.x中一些技巧及痛点问题解决方法
# 在三方组件上挂载上报事件
如何在第三方组件例如对 radio、button 等组件进行点击的上报呢?可以通过改写的形式载入代替这个过程,我们来看一下吧。
- 首先我们引入在页面中引入一个组件,并输出这个组件的值,可以看到:
import CascadeSelect from "./libs/CascadeSelect";
...
mounted() {
console.log(CascadeSelect,CascadeSelect.methods);
}
...
2
3
4
5
6
在控制台中我们可以看到我们能够拿到这个引入了的组件的对象方法等函数。有了这些,我们就可以对他进行改写了。
- 举个栗子
假设我们需要对饿了吗的 radio 组件在点击之后执行数据上报的事件,那么在这里我们可以创建一个 el-radio-button.js 文件,并使用 VUE 的 extends,它允许声明扩展另一个组件,将 ui 组件的 radio 进行混入,再加入我们定义的函数和方法进行重写,就可以完成这样的操作了。
import { RadioButton } from "element-ui";
export default {
extends: RadioButton,
props: {
IS_REPORT_LOG: [Boolean, String],
},
methods: {
// 定义handleChange包裹ui组件的handleChange事件。
handleChange() {
if (this.IS_REPORT_LOG) {
this.$report("数据上报");
}
RadioButton.methods.handleChange.call(this); // 执行真正的组件的方法。
},
},
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 原型上增加属性
这个方法可以在上面组件中调用点击事件时拿到这个触发事件的实例
Object.defineProperties(Vue.prototype, {
$report: {
get() {
const dom = this.$el;
return {
click: (action) => clickReport(action, dom)(),
};
},
},
});
2
3
4
5
6
7
8
9
10
# v-on 监听多个事件
<button v-on="{ mousedown: doThis, mouseup: doThat }"></button>
# Vue 初始化页面闪动
[v-cloak] {
display: none;
}
2
3
# 父组件监听子组件生命周期
- 通过自定义事件
// Parent.vue
<Child @mounted="doSomething"/>
// Child.vue
mounted() {
this.$emit("mounted");
}
2
3
4
5
6
- 挂载时监听
// Parent.vue
<Child @hook:mounted="doSomething" ></Child>
doSomething() {
console.log('父组件监听到 mounted 钩子函数 ...');
},
// Child.vue
mounted(){
console.log('子组件触发 mounted 钩子函数 ...');
},
// 以上输出顺序为:
// 子组件触发 mounted 钩子函数 ...
// 父组件监听到 mounted 钩子函数 ...
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# hook 处理生命周期
通过$on\once 等方法监听生命周期事件反馈执行任务。
<script>
export default {
mounted() {
const timer = setInterval(() => { ... }, 1000);
this.$once('hook:beforeDestroy', () => clearInterval(timer);)
}
};
</script>
2
3
4
5
6
7
8
# 替代右键默认事件
@contextmenu.prevent 监听 contextmenu 方法,prevent 阻止默认事件 event.preventDefault 事件向下传递,即阻止了右键菜单的发生,在此将自己制定的元素替代上去
<div
v-show="isShow"
:style="contextMenuStyle"
@contextmenu.prevent
>
<el-button
type="text"
icon="el-icon-edit"
size="medium"
@click="handleEdit"
>
设置
</el-button>
</div>
2
3
4
5
6
7
8
9
10
11
12
13
14
# 跨多组件传递 prop、event
v - bind = "$attrs";
v - on = "$listeners";
Vue.observable;
2
3
# 网页关闭事件
onbeforeunload 事件在即将离开当前页面(刷新页面、关闭页面、关闭浏览器、页面中点击超链接跳转)时触发,该事件可用于弹出对话框,提示用户是继续浏览页面还是离开当前页面。
页面触发 onbeforeunload 事件时进入
- 在页面被关闭前会进入
- 方法有返回值,才会自动弹框提示,否则不会弹框,但方法内容仍然会执行
- 方法返回的值与页面最终是否被关闭无关,即返回什么值都不会影响
window. onbeforeunload:
window.onbeforeunload = () => {
return "退出页面时弹出确认框";
};
2
3
window. addEventListener(" beforeunload":
window.addEventListener("beforeunload", function(event) {
console.log(new Date() + ":用户即将离开页面...");
event.returnValue = "...";
});
2
3
4
# 函数式创建、移除 watch 事件
使用$watch 方法来创建监听并赋值给变量,及时在满足需求条件后移除监听,减少内存占用。
//创建监听
let Watcher = this.$watch(
"count",
function() {
console.log("count 新值:" + newVal);
},
{
immediate: true, // 立即执行watch
}
);
//移除监听
Watcher();
2
3
4
5
6
7
8
9
10
11
12
# inject/provide 黑科技
在 provide 中向下传递的变量是不会触发响应式的,若想像 data 那样触发响应式,应在 provide 中传入对象类型的变量,若是想在子组件内修改这个传入的 provide,需要一并传递修改控制变量的函数,达到一对一服务的形式,并且在引入该变量的模块中的值皆会被修改。
provide() {
return {
prarams: this.params,
};
},
data() {
return {
params: {
state: '',
commit(val) {
this.state = val;
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 借用 filter 方法
在 vue 中,filter 只是一个普通的函数,没有任何 vue 的依赖,可以随意调用。 filter 方法写在 filter 中,若该方法在 methods 中也希望能够使用,则可以通过调用 vue 对象中的$options.filters 数组拿到希望使用的 filter 方法。
# 懒加载处理后端传来的 HTML 字符串中的图片
- 处理换行
content.replace(/(\\r\\n|\\n|<br\s*\/?>)/gi, "<br/>");
- 正则取出图片元素,通过分割有图片的两端
let imgs = [];
content = content.replace(
/<a.*?data-original=['|"](.*?)['|"].*?class=['|"]lazy-load-img['|"].*?>(.*?)<\/a>/g,
(fullStr, src, name) => {
imgs.push({ contentType: "img", name, src });
return "<IMG>";
}
);
2
3
4
5
6
7
8
- 分割,设立 content 数组,可以用于在 vue 中循环产生 dom
let contents = [];
let splitContent = content.split("<IMG>");
for (let t of splitContent) {
if (t)
// 这里我们去掉div元素,在之后循环中自己定义它,并且避免出现只有<div></div>这样的情况
t = t
.replace(/^\s*<\/div>/, "")
.replace(/<div>\s*$/, "")
.trim();
if (t !== "") contents.push({ contentType: "img", html: t });
}
if (imgs.length) {
//如果有图片证明有图片经历了切割,插入
contents.push({ ...imgs.splice(0, 1)[0] });
}
return contents;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- 最后再 template 中循环
div v-for xx in contents
div if xx.contentType == 'string' v-html=xx.html
LazyImage if xx.contentType == 'img' :src :name
2
3
最后相应的根据需求设计 LazyImage 组件
# vue-router 路由传参的问题
vue 的路由跳转携带参数有两种形式:
- path 配合 query 传参
<router-link :to="{path: 'List', query: { id: 10086 }}"></router-link>
path 输入路由,携带 query:/list?id=10086&token=adksja 使用 this.$route.query 获取 json 类型传入的参数,它在路由跳转的时候,只会根据 path 内输入的路由跳转。
- name 配合 params 传参
<router-link :to="{name: 'List', params: { id: 10086, name:dyf }}"></router-link>
name 输入路由。携带 params: /list/10086 params 动态路由传参,需要在 router 设置里设置好/list/:id,在 this.$route.params 里就可以获取到传入的参数,但是像上面的 name 属性,如果刷新了页面,这个参数就不存在了。
# 在当前页面中覆盖 ui 库中组件的样式
vue 中所谓的 scoped 标签,其实是为其加上了[data-xxxxxxxx]的属性,但是第三方组件内并不会附带上这个属性,如果写一个没带 scoped 的 style 又怕全局污染命名冲突,只能寄托于使用 BEM 命名形式,但是仍然无法保证其独立性。使用!import 也不是一个很好的选择,会造成层级的错乱。这个时候其实可以考虑使用样式穿透来看看是否能解决这个问题。总结:
- 使用不携带 scoped 的 style 配合 bem 命名覆盖。
- 使用!import 不推荐。
- /deep/ Or >>>
# 打包后生成很大的.map 文件问题及使用 gzip 压缩代码
项目打包后,代码都是经过压缩加密的,如果运行时报错,输出的错误信息无法准确得知是哪里的代码报错。而生成的.map 后缀的文件,就可以像未加密的代码一样,准确的输出是哪一行哪一列有错可以通过设置来不生成该类文件。但是我们在生成环境是不需要.map 文件的,设置 productionSourceMap: false,就可以不生成.map 文件。
gzip 压缩代码:productionGzip: true, // false 不开启 gizp,true 开启。在 nginx 需要设置。
# 资源加载前、中的 tips 实现
在 vue 中获取数据有两种方式:在导航完成之前/后获取。
# 在导航完成后获取
也就是最常用的在 created、mounted 中发送 ajax 请求获取数据,并根据反馈的数据分别展示不同的组件或是数据。
# 在导航完成前获取
在单页面应用中在路由的 beforeRouteEnter 钩子中请求数据,只有在数据获取成功之后才会跳转导航页面。
beforeRouteEnter (to, from, next) {
api.getList(to.query.id).then(res=> {
next(vm => {
vm.info = res.data;
vm.loadFinish = true
})
})
}
2
3
4
5
6
7
8
# 在 vue 中下载二进制文件
- 使用 window.open(url)链接下载 由后台提供已经转好的下载 url,通过弹出直接下载即可
- 前端请求接口后转码发起下载流程
这里需要用到 Blob。Blob 对象表示一个不可变、原始数据的类文件对象。Blob 表示的不一定是 JavaScript 原生格式的数据
axios({
method: "post",
url: "/export",
responseType: "arraybuffer", // 返回需要是arraybuffer类型
}).then((res) => {
// 假设 data 是返回来的二进制数据
const data = res.data;
const url = window.URL.createObjectURL(
new Blob([data], {
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
})
);
const link = document.createElement("a");
link.style.display = "none";
link.href = url;
link.setAttribute("download", "excel.xlsx");
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
在此基础上需要根据后端返回值的判断来决定是否需要下载文件。首先下载我们设置返回的响应类型为 arraybuffer 类型,所以我们需要在接受到数据时将其转换为 string 类型,判断内部是否有后端协定好的无需下载的字段,从而决定是否展开下载。这里需要用到解码方法:TextDecoder
TextDecoder 接口表示一个文本解码器,一个解码器只支持一种特定文本编码,例如 utf-8、iso-8859-2、koi8、cp1261,gbk 等等。解码器将字节流作为输入,并提供代码点流作为输出。
axios.interceptors.response.use(
(response) => {
const res = response.data;
// 判断响应数据类型是否 ArrayBuffer,true 则是下载文件接口,false 则是正常接口
if (res instanceof ArrayBuffer) {
const utf8decoder = new TextDecoder();
const u8arr = new Uint8Array(res);
// 将二进制数据转为字符串
const temp = utf8decoder.decode(u8arr);
if (temp.includes("{code:-1")) {
Message({
// 字符串转为 JSON 对象
message: JSON.parse(temp).msg,
type: "error",
duration: 5000,
});
return Promise.reject();
}
}
return res;
},
(error) => {
return Promise.reject(error);
}
);
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
# 部分强依赖 moment 的组件打包占比大
- 若是自身使用到 moment 可以根据项目使用的轻重选择是否更换为 day.js
- 不需要国际化的话,位于组件内等使用可以单独拉 zh-cn 的中文包,可以使用 webpack 去打包压缩它
// vue.config.js
module.exports = {
chainWebpack: (config) => {
config
.plugin("ContextReplacementPlugin")
.use(webpack.ContextReplacementPlugin, [/moment[/\\]locale$/, /zh-cn/]);
},
};
2
3
4
5
6
7
8