标签 vue 下的文章

这个自定义指令本不应该存在。

正常工作中,要显示一个图片,为了性能考虑,肯定是显示它的低分辨率版本。可最近在做的一个项目中,服务器直接提供了原图用来显示,即使只用来当做一个小图标。这导致一个网页消耗的流量达到了近 100MB,并严重拖累页面性能,连鼠标的 hover 样式都延迟很久才会响应。

可惜奇葩的后端目前没有解决这个问题的打算,产品同学也觉得这样还是很 OK,即使解决这个问题很容易——就是在生成图片的时候多生成几个不同分辨率的版本。

下载网页资源消耗的是公司服务器和用户的流量,自然不需要操心,也不是前端同仁能够管得了的。显然用户用起这个产品来并没有感到不适,公司也不在乎服务器多消耗的那点流量。但页面性能变差,作为一个前端老师傅,这能忍?本文的自定义指令正是用来解决这个问题的,正所谓改变不了别人,还改变不了自己么?

既然图片太大拖累了页面性能,那么在图片下载完毕之后,生成一个低分辨率的版本替换它,问题应该就解决了。代码很简单,全部贴上来。因为才疏学浅,肯定有考虑不周的地方(实际上没有考虑),请不吝指教。

//新建一个 thumb.js
export default {
    mounted(el, binding) {
        el.onload = () => {
            //同是天涯沦落人
            if (el.src.startsWith('data:image')) return;

            const canvas = document.createElement('canvas');
            const ctx = canvas.getContext('2d');
            //默认宽度给50是不是有点小气了?
            const thumbWidth = binding.value ?? 50;
            //naturalHeight 和 naturalWidth 只有在 onload 之后才能获取到
            const thumbHeight = el.naturalHeight * thumbWidth / el.naturalWidth;
            canvas.width = thumbWidth;
            canvas.height = thumbHeight;
            ctx.drawImage(el, 0, 0, thumbWidth, thumbHeight);

            //将原来图片上的属性全复制过去,除了 src
            const attrs = el.attributes;
            const img = document.createElement('img');
            img.src = canvas.toDataURL();
            //如果你不喜欢 for 循环遍历 NamedNodeMap,还能用 Array.prototype.forEach.call
            for (let i = 0; i < attrs.length; i++) {
                const attr = attrs[i]; //这是一个 Attr 对象
                if (attr.name === 'src') continue;
                img.setAttribute(attr.name, attr.value);
            }

            if (el.parentNode) {
                el.parentNode.replaceChild(img, el);
            }
        };
    }
};

注意你不能直接替换原图片的 src 属性,因为这会循环触发 onload 事件。

使用这个自定义指令:

import vThumb from 'thumb.js'; //在 setup 中,以 v开头的驼峰形式命名的变量会自动注册为指令
<!-- 注意跨域的问题,如果图片不允许跨域,那么半天白忙活了 -->
<img v-thumb src="beauty.jpg" crossorigin="anonymous">
<img v-thumb="100" src="beauty.jpg" crossorigin="anonymous">

使用的时候还要注意它的局限性,例如并没有处理绑定到原图片上的事件。


End

由于 v-if 在条件为 false 时,会销毁它内部的所有内容,当条件为 true 时,重新渲染内部的内容,所以 v-if 可以用作刷新某些区域用。

例子1

例如,input[type=file] 控件,如果你选择的文件跟上次选择的是同一个,它就不会触发 changeinput 事件,除非你选择一个新的文件。即使大多数情况下,这种情形并不需要特殊处理,因为用户不可能老是选择同一个文件,但总是会遇到特殊的时候。这个时候就可以用 v-if 来销毁原来的 file 控件,并渲染一个新 file,这个新的 file 上面绑定的数据、事件都跟原来一模一样:

<!-- template -->
<input type="file" v-if="someCondition" @change="onChange">

/* Data */
data(){
    return {
        someCondition: true
    }
}

/* Methods */
//file的change事件:
onChange(e){
    //想办法让someCondition经历 false - true 的转换
    this.someCondition = false;
    ...some code
    this.someCondition = true
}

现在,input[type=file] 已经是一个全新的了,即使你选择同一个文件,它也会响应 change 事件。

例子2

element-uitree 控件不是很完善,不提供刷新方法(也许是我没有发现)。如果你修改了某一个子项,然后想要重新加载整个树,就可以使用 v-if 来达到目的。用法跟上面是一样的。

End

Vuex 方便了管理状态,但也使开发流程更加复杂。为了提高开发速度,“让你少按几次键”,Vuex 提供了诸如mapState方法,但其实帮助不大。如果你在 methods 中看到调用了一个陌生的方法,那十有八九是 Vuex 中的 Action 或者 Mutation。

要是你想使用 v-model 绑定 state 中的字段,享受双向绑定的便捷,那在享受之前,你要好好忍受一下折磨,因为要实现这个功能是很麻烦的:在定义了 state 后,还要定义对应的 mutation;导入到组件的 computed 时,不能使用 mapState,还要给导入的 state 设置 setter,在 setter 中 commit mutation。这是至关重要的一步,否则 v-model 不会正常工作,而是会警告你缺少 setter:

Computed property "xxxx" was assigned to but it has no setter

这实在是很麻烦,必须找到更快捷的方法,更愉快地使用 v-model。这里提供一个简单的方法,应该会提高一点点开发速度。

1、定义通用的 mutation

如果给每一个 state 都定义相应的 mutation,那代码就要写到天荒地老。不妨定义一个通用的 mutation,只需传入需要修改的 state 的字段名和新值:

updateState(state, payload){
    state[payload.key] = payload.val;
}

这样就方便多了。至于一些需要特殊处理的 state,再单独给它们定义 mutation。

2、生成带 setter 的 computed

能用程序生成的,怎么能复制粘贴修改呢?computed 是一个对象,咱们就生成一个对象,再用...操作符合并到组件的 computed 中。

首先在你喜欢的地方新建一个 .js 文件。因为提交 mutation 需要用到 store 实例,store 实例又在 Vue 实例上,咱们就从 main.js 或任何你能想到的地方导出 Vue 实例,在刚才新建的 .js 文件中导入:

import vm from '@/main'

接着,定义一个数组,元素是 state 的字段名:

let stateKeys = ['name', 'age', 'sex'];

接着,循环这个数组,生成计算属性:

let _state = {};
stateKeys.map(key =>{
    _state[key] = {
        get(){
            //如果你的state在模块中,记得在`state`后加上.模块名,下同
            return vm.$store.state[key];
        },
        set(val){
            vm.$store.commit('updateState', {
                key,
                val
            });
        }
    }
})

export const state = _state;

之后,在组件中引入刚刚的 state:

import state from '刚刚的js路径和名称'

最后,在组件的 computed 属性中导入 state:

computed: {
    ...state
}

现在,就能在模板中正常使用 state 了:

<input v-model="name">

最后

一个人不喜欢刷碗不一定是个懒人,他可能只是不喜欢干类似的活。程序员一般都不喜欢干复制粘贴这样的重复性工作 :)

End

我是菜鸟,今天才发现这个问题。

Vue计算属性看似是一个函数,但实际上跟 data 更像。如果计算属性返回的是一个对象,并且在模板中读取了这个对象的属性,那就要保证开始时这个对象的属性一定要存在,下面是有问题的代码:

<header>{{ pageInfo.title }}</header>

...
computed: {
    pageInfo(){
        const mapPageInfo = {
            'index': {
                title: 'index page',
                subTitle: 'index subtitle'
            },
            'list': {
                title: 'list page',
                subTitle: 'list subtitle'
            }
        };

        return mapPageInfo[this.$route.path]
    }
}
....

上面的代码看起来没有问题,运行起来也是正常的,但实际上在控制台报错了:

cannot read property title of undefined...

原因就是 pageInfo 在刚开始的时候没有组件中读取的 title 属性。下面是正确的做法:

...
pageInfo(){
    //**保证属性一定是存在的**
    let pageInfo = {
        title: '',
        subTitle: ''
    };
    const mapPageInfo = {
        ...
    }
    ....
    return Object.assign(pageInfo, mapPageInfo[this.$route.path]);
}
...

End

一、基本使用

Swiper是一个非常好用的滑动插件,可以用来实现轮播图、代替浏览器默认的滚动条。中文主页:

https://www.swiper.com.cn/

在Vue中,Swiper对应的是Vue-Awesome-Swiper,NPM的地址:

https://www.npmjs.com/package/vue-awesome-swiper

基本的使用比较简单,首先安装Vue-Awesome-Swiper

npm install vue-awesome-swiper --save

- 阅读剩余部分 -

大部分时间,Vue都用来开发单页面应用程序(single page web application,SPA),但有时也需要开发多页面应用,这就要配置Vue CLI。有两种方式来配置Vue CLI,一是在项目的package.json中添加一个vue字段,在这个字段中配置相关的功能,二是在项目根目录新建一个vue.config.js,这个文件会被Vue CLI合并到webpack的配置中。这里以vue.config.js为例,来配置一个多页面的应用。

- 阅读剩余部分 -

Vue官方文档:https://cn.vuejs.org/v2/guide/custom-directive.html

什么是Vue自定义指令?

Vue自定义指令用起来就像Vue内置的v-modelv-on...之类,只不过实现了一些自己想要的功能。因为Vue自定义指令的钩子函数会传入一个el,它是自定义指令绑定到的那个DOM元素,所以Vue自定义指令可以用来直接和DOM打交道。

如何注册一个指令?

自定义指令分为全局和组件内的局部指令。全局自定义指令注册方法:

Vue.directive('myDirective', {
    // inserted是内置的钩子函数
    inserted: function (el) {
        // 一些操作...
    },
    // 指令的其他内容和钩子函数,如果需要的话...
})

- 阅读剩余部分 -