Vue2全家桶

参考文档及链接

Vue基础参考文档

Vue脚手架参考文档

推荐视频教程链接

相关命令

安装环境相关

下载nodejs并安装:https://nodejs.org/en/download/

配置淘宝镜像:npm config set registry https://registry.npm.taobao.org

全局安装脚手架:npm install -g @vue/cli
关于全局命令无法生效的解决方案:https://blog.csdn.net/qq_45955580/article/details/116745061

脚手架相关

创建Vue项目:vue create project_name

其它

查看具体的webpack配置:vue inspect > output.js

事件

组件标签 本身没有预留事件,只有常见的标签(button、input等)身上有,如果想要对组件绑定 click 事件,则需要使用自定义事件(:click="click"),再调用 $emit('click', ()=>{}) 方法。

事件类型

事件的种类,会在我不断接触的过程中,一个一个补全。

1. 单击事件:`@click`
2. 双击事件:`@dblclick`
3. 滚动事件:`@scroll`
4. 鼠标按下:`@keydown`
5. 鼠标松开:`@keyup`
6. 失去焦点:`@blur`
7. 获得焦点:`@focus`

一般事件修饰符

1. `.stop`:阻止单击事件继续传播(阻止冒泡);
2. `.prevent`:阻止默认事件的触发;
3. `.capture`:在事件正常流程开始执行之前进行捕捉,被捕捉的事件立即执行;
4. `.self`:只当在 event.target 是当前元素自身时触发处理函数;
5. `.once`:仅执行一次。

滚动条专属修饰符(:scroll)

1. `.passive`:滚动事件专属,滚动会优先于事件处理执行,即界面优先响应滚动。

.passive.prevent 不能一起使用。

鼠标按钮修饰符(:click)

1. `.left`:鼠标左键按下触发;
2. `.right`:鼠标右键按下触发;
3. `.middle`:鼠标滚动键按下触发。

普通键盘修饰键(:keyup)

1. `.enter`:按下 Enter 键触发;
2. `.tab`:按下 Tab 键触发;
3. `.delete`:按下 Delete 或 Backspace 键触发;
4. `.esc`:按下 Esc 键触发;
5. `.space`:按下空格键触发;
6. `.up`:按下[上]方向键触发;
7. `.down`:按下[下]方向键触发;
8. `.left`:按下[左]方向键触发;
9. `.right`:按下[右]方向键触发;
999. `.键名`:用 `$event.key` 获取按键的名称,如:`PageDown`,然后使用 `@keyup.page-down` 触发按键事件;

系统键盘修饰键(:keydown)

1. `.ctrl`
2. `.alt`
3. `.shift`
4. `.meta`:Win键,或Command键。

如:v-on:keyup.alt.67 表示 Alt + Enter组合键;v-on:click.ctrl 表示 Ctrl + 鼠标左键组合键。

组合按键修饰符

1. `.exact`:被修饰的按键修饰键被优先按下时才会触发事件,还可以用于限制仅有一个按键按下(没有其他任何按键按下)时触发。

数据联动

v-model

v-model 只对具有 输入 属性的页面元素有效,如:input

使用 v-model 时,触发对象是 输入元素,连带触发对象是 使用该绑定变量的所有页面元素

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Demo</title>
</head>
<body>
    <div id="app">
        <input type="text" v-model="content" @focus="content = ''">
        <h1>{{ content }}</h1>
        <h2>{{ content }}</h2>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
    <script>
        new Vue({
            el: '#app',
            data() {
                return {
                    content: '正在等待输入...'
                }
            },
        })
    </script>
</body>
</html>

watch

监听属性,被监听的是 变化的值,主动监听必须要有一个载体,可以是一个变量,也可以是持久存储方法。 本例中使用 result 作为监听变化的载体。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Demo</title>
</head>
<body>
    <div id="app">
        <input type="text" v-model.float="x"> <span>* 2 + 1 =</span> <span>{{ result }}</span>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
    <script>
        const vm = new Vue({
            el: '#app',
            data() {
                return {
                    x: '',
                    result: '' // 作为监听变化的载体
                }
            },
            watch: {
                x: {
                    deep: false, // 深度监听,默认 false
                    immediate: false, // 立即触发回调,默认 false
                    handler(val){
                        this.result = (val * 2 + 1).toString()
                    }
                }

                // 当不使用 deep 和 immediate 两个属性时,可以简写成
                // x(val){
                //     this.result = (parseFloat(val) * 2 + 1).toString()
                // }
            }
        })

        // 或者使用 $watch 添加监听
        // const unwatch = vm.$watch('x', function(val){
        //     this.result = (val * 2 + 1).toString()
        // }, {deep: false, immediate: false}) // 第三个参数可以省略
        // unwatch() // 取消监听
    </script>
</body>
</html>

computed

计算属性(计算方法)不需要预先定义存储对象(不需要预先在 data 属性中声明),它是作为构建方法名调用的。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Demo</title>
</head>
<body>
    <div id="app">
        <input type="text" v-model.float="x"> <span>* 2 + 1 =</span> <span>{{ result }}</span>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
    <script>
        const vm = new Vue({
            el: '#app',
            data() {
                return {
                    x: '',
                }
            },
            computed: {
                result: {
                    get(){
                        return (this.x * 2 + 1).toString()
                    },
                    set(val){
                        // 设置函数,略
                    }
                }

                // 当仅使用 get() 方法时,等价于(简写)
                // result(){
                //     return (this.x * 2 + 1).toString()
                // }
            }
        })
    </script>
</body>
</html>

组件间通信

这里假设您对组件脚手架已经有了初步理解。若您无组件基础,此笔记对您无效。

父对子传

方法一:props

父组件【./App.vue】

父组件 App 通过引用子组件 DemoHeader,并在HTML元素中实例化子组件 <DemoHeader/>,实现对子组件的使用。 通过向子组件标签传递参数 :title="title" 完成对子组件的传值。此处的 "title" 为数据绑定传值。

<template>
    <div id="app">
        <DemoHeader :title="title"/>
    </div>
</template>

<script>
import DemoHeader from './components/DemoHeader.vue'

export default {
    name: 'App',
    components: {
        DemoHeader,
    },
    data(){
        return {
            title: "Demo-Title"
        }
    }
}
</script>

子组件【./components/DemoHeader.vue】

子组件 DemoHeaderprops 属性接收来自父组件(通过标签属性)传递的值。
此处的 title 为父组件传参的参数名称(即父组件标签传值的 :title 中的 title)。

<template>
    <div>
        <h1>{{ title }}</h1>
    </div>
</template>

<script>
    export default {
        name: 'DemoHeader',
        props: ['title']
    }
</script>

子对父传&任意传

子对父传,不可能直接传,只能间接传。

(Vue2.0数据操作原则:数据在哪个组件,操作就在哪个组件里)

方法一:props

可以通过向 子组件 传递一个函数,然后 子组件 通过调用此函数,并向此函数传递参数的方式 ,实现 子组件父组件 传递数据的目的。

父组件【./App.vue】

向子组件传递 changeTitle 方法。

<template>
    <div id="app">
        <DemoHeader :title="title" :changeTitle="changeTitle"/>
    </div>
</template>

<script>
    import DemoHeader from './components/DemoHeader.vue'

    export default {
        name: 'App',
        components: {
            DemoHeader,
        },
        data(){
            return {
                title: "Demo-Title"
            }
        },
        methods:{
            changeTitle(msg){
                this.title = msg
            }
        }
    }
</script>

子组件【./components/DemoHeader.vue】

通过 props 属性,接收父组件传递过来的函数 changeTitle,并通过新的方法 changeHeaderTitle 完成参数调用。

<template>
    <div>
        <h1>{{ title }}</h1>
        <button @click="changeHeaderTitle('你好,父组件!')">Change Title</button>
    </div>
</template>

<script>
    export default {
        name: 'DemoHeader',
        props: ['title', 'changeTitle'],
        methods: {
            changeHeaderTitle(msg){
                this.changeTitle(msg)
            }
        }
    }
</script>

方法二:ref

父组件【./App.vue】

通过绑定 ref 标签属性,再使用 this.$refs 直接调用子组件实例对象的 $on 方法,完成子组件方法的绑定( 注意:数据在哪个组件里,方法就在哪个组件里)

<template>
    <div id="app">
        <DemoHeader :title="title" ref="demoHeader"/>
    </div>
</template>

<script>
    import DemoHeader from './components/DemoHeader.vue'

    export default {
        name: 'App',
        components: {
            DemoHeader,
        },
        data(){
            return {
                title: "Demo-Title"
            }
        },
        mounted(){
            this.$refs.demoHeader.$on('changeTitle', (msg)=>{
                this.title = msg
            })
        }
    }
</script>

子组件【./components/DemoHeader.vue】

子组件通过 this.$emit 方法,触发在父组件中 间接注册 的方法(直接绑定在子组件实例上),传入参数完成子组件对父组件的数据传递。

<template>
    <div>
        <h1>{{ title }}</h1>
        <button @click="changeHeaderTitle('你好,父组件!')">Change Title</button>
    </div>
</template>

<script>
    export default {
        name: 'DemoHeader',
        props: ['title'],
        methods: {
            changeHeaderTitle(msg){
                this.$emit('changeTitle', msg)
            }
        }
    }
</script>

方法三:作用域插槽

子组件【./components/DemoHeader.vue】

<template>
    <div>
        <slot name="demo" :students="students">
            <h3>插槽默认元素</h3>
        </slot>
    </div>
</template>

<script>

export default {
    name: 'DemoHeader',
    data() {
        return {
            students: [
                {
                    id: "001",
                    name: "小明",
                    age: 111,
                },
                {
                    id: "002",
                    name: "张三",
                    age: 11,
                },
            ]
        }
    },
}
</script>

父组件【./App.vue】

通过 slot-scope 或者 scope 为作用域命名。比如此处的 datas

如果只需要 students 变量,则可以简写成 slot-scope="{ students }"

<template>
    <div id="app">
        <DemoHeader>
            <template slot-scope="datas" slot="demo">
                <div>
                <span v-for="stu in datas.students" :key="stu.id">
                    【{{ stu.name }}/{{stu.age}}】<br>
                </span>
                </div>
            </template>
        </DemoHeader>
    </div>
</template>

<script>
    import DemoHeader from './components/DemoHeader.vue'

    export default {
        name: 'App',
        components: {
            DemoHeader,
        },
    }
</script>

方法四(任意传):原型链

原理:组件实例 VueComponent 原型链在指向 Object 之前会先指向 Vue 实例对象,因此绑定在 Vue 实例对象上的属性, 均能够被生命周期内的所有的 VueComponent 获取到。

入口函数【./main.js】

import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

// 方法一:不通过 `beforeCreate` 绑定 `Vue` 实例对象,而是构造一个中间的 `VueComponent` 实例对象。
// const bus = Vue.extend({})
// Vue.prototype.$bus = new bus()

new Vue({
    render: h => h(App),

    // 方法二:直接绑定 `Vue` 实例对象
    beforeCreate(){
        Vue.prototype.$bus = this
    }
}).$mount('#app')

父组件【./App.vue】

通过调用 Vue 实例对象上的 $on 完成事件的绑定。

<template>
    <div id="app">
        <DemoHeader :title="title"/>
    </div>
</template>

<script>
    import DemoHeader from './components/DemoHeader.vue'

    export default {
        name: 'App',
        components: {
            DemoHeader,
        },
        data(){
            return {
                title: "Demo-Title"
            }
        },
        mounted(){
            this.$bus.$on('changeTitle', (msg)=>{
                this.title = msg
            })
        }
    }
</script>

子组件【./components/DemoHeader.vue】

通过调用 Vue 实例对象上的 $emit 完成数据的传递与方法的调用。

注意:务必在子组件销毁之前,调用 $off 解绑具体的方法。

<template>
    <div>
        <h1>{{ title }}</h1>
        <button @click="changeHeaderTitle('你好,父组件!')">Change Title</button>
    </div>
</template>

<script>
    export default {
        name: 'DemoHeader',
        props: ['title'],
        methods: {
            changeHeaderTitle(msg){
                this.$bus.$emit('changeTitle', msg)
                this.$destroy()
            }
        },
        beforeDestroy(){
            this.$bus.$off('changeTitle')
        }
    }
</script>

方法五(任意传):订阅与发布

首先,安装自己需要的消息订阅与发布的三方库,此处以 pubsub-js 为例。

安装:npm i pubsub-js

其他的参考 原型链 方式,使用几乎一模一样。

父组件(订阅)【./App.vue】

<template>
    <div id="app">
        <DemoHeader :title="title"/>
    </div>
</template>

<script>
    import DemoHeader from './components/DemoHeader.vue'
    import pubsub from 'pubsub-js'

    export default {
        name: 'App',
        components: {
            DemoHeader,
        },
        data(){
            return {
                title: "Demo-Title"
            }
        },
        mounted(){
            this.pubId = pubsub.subscribe('changeTitle', (msgName, msg)=>{
                this.title = msg
            })
        },
        beforeDestroy(){
            pubsub.unsubscribe(this.pubId)
        }
    }
</script>

子组件(发布)【./components/DemoHeader.vue】

<template>
    <div>
        <h1>{{ title }}</h1>
        <button @click="changeHeaderTitle('你好,父组件!')">Change Title</button>
    </div>
</template>

<script>
    import pubsub from 'pubsub-js'

    export default {
        name: 'DemoHeader',
        props: ['title'],
        methods: {
            changeHeaderTitle(msg){
                pubsub.publish('changeTitle', msg)
            }
        },
    }
</script>

方法六(任意传):Vuex

Vuex 有着特殊的应用场景,当不同的组件共用一些变量的时候(增、删、改、查),就考虑使用 Vuex

表单

【暂不整理】

样式

【暂不整理】

过渡与动画

三方库集成:npm i animate.css

简单动画

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Demo</title>
    <style>
        /* transition 无 name 属性,默认 .v-enter-active */
        .demo-enter-active{
            animation: demo 0.5s linear;
        }
        /* 默认 .v-leave-active */
        .demo-leave-active{
            animation: demo 1s linear reverse;
        }
        @keyframes demo {
            from{
                transform: translateX(-100%);
            }
            to{
                transform: translateX(0px);
            }
        }
    </style>
</head>
<body>
    <div id="app">
        <button @click="clickShow">{{ btn_name }}</button>
        <transition name='demo' :appear="true">
            <h1 v-show="isShow">动画演示</h1>
        </transition>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
    <script>
        new Vue({
            el: '#app',
            data: {
                isShow: true,
                btn_name: '隐藏'
            },
            methods: {
                clickShow(){
                    this.isShow = !this.isShow
                    if (this.isShow){
                        this.btn_name = '隐藏'
                    }else{
                        this.btn_name = '显示'
                    }
                }
            }
        })
    </script>
</body>
</html>

UUID

nanoid

import {nanoid} from 'nanoid'

// 使用
uuid = nanoid()

DOM相关

DOM更新之后执行事件

...
this.$nextTick(function(){
    // DOM刷新之后执行的操作
})
...

浏览器本地存储

持久性【localStorage】

// 存值
localStorage.setItem('key1', 'value') # 存字符串
localStorage.setItem('key2', JSON.stringify({'name':'kity'})) # 存JSON对象

// 取值(取一个非法的键值时,返回 null )
const value = localStorage.getItem('key2')
console.log(JSON.parse(value)) # 解析对象

// 删除
localStorage.removeItem('key1')

// 清空
localStorage.clear()

一次性【sessionStorage】

API 和 localStorage 一模一样,不同的是 sessionStorage 只在会话期间临时存储,关闭浏览器后存储内容自动删除。

异步请求

axios

npm i axios

<script>
    import axios from 'axios'

    export default {
        methods: {
            testApi(){
                axios.get('url').then(
                    response => {
                        // 请求成功
                        // 取数据:response.data
                    },
                    error => {
                        // 请求失败
                        // 去错误信息:error.message
                    }
                )
            }
        }
    }
</script>

fetch

【暂不整理】

vue-cli常用配置

具体配置请移步官网教程:vue-cli

package.json 同级的目录下新增文件 vue.config.js

关闭语法检查

vue.config.js 文件中新增如下内容:

module.exports = {
    // 关闭语法检查
    lintOnSave: false
}

跨域问题

配置单代理模式

vue.config.js 文件中新增如下内容:

单代理模式,不支持多代理模式(且控制不了代理的路径)

module.exports = {
    // 开启代理服务器
    // 假设 axios 所在服务器端口为 8080,接口所在的端口为 4000
    // 则此处需要配置 4000 的代理端口
    // 请求接口由原来的 http://localhost:4000/xxx
    // 改为 http://localhost:8080/xxx
    devServer: {
        proxy: 'http://localhost:4000'
    }
}

配置多代理模式

添加请求前缀的方式,详细区分 本域请求 还是 跨域请求。

module.exports = {
  devServer: {
    proxy: {
      '/api': {
        // 原来的 http://localhost:4000/xxx
        // 改为 http://localhost:8080/api/xxx
        target: 'http://localhost:4000',
        pathRewrite: {'^/api':''}, // 路由中忽略开头的 'api' 中间路径。
        ws: true, // 用于支持 WebSocket。默认 true
        changeOrigin: true // 为true时,伪造端口,与接口端口一致。默认 true
      },
      '/foo': {
        target: '<other_url>'
      }
    }
  }
}