1 Star 6 Fork 0

N0ts / note

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
Vue.md 82.65 KB
一键复制 编辑 原始数据 按行查看 历史
N0ts 提交于 2022-10-21 12:11 . updatew

Vue初体验

<body>
    <div id="app">
        <h1>{{message}}</h1>
        <h2>{{name}}</h2>
    </div>

    <script src="./vue.js"></script>
    <script>
        // let变量 / const常量
        // 编程范式:声明式编程
        const app = new Vue({
            el: '#app', // 挂载管理元素
            data: { // 定义数据
                message: 'wdnmd',
                name: 'test'
            }
        })
    </script>
</body>

列表展示

<body>
    <div id="app">
        <h1>{{name}}</h1>
        <ul>
            <li v-for="item in list">{{item}}</li>
        </ul>
    </div>

    <script src="./vue.js"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                name: '电影列表',
                list: ['战狼', '战狼2', '战狼3', '战狼4']
            }
        })
    </script>
</body>

Vue 实例传入的 options

  • options 需要掌握的选项

    1. el:

      类型:string | HTMLElement

      作用:决定 Vue 实例管理哪一个 DOM 对象

    2. data:

      类型:Object | Fuction (组件当中 data 必须是一个函数)

      作用:Vue 实例对应的数据对象

    3. methods:

      类型:{[key:string] : Fuction}

      作用:定义属于 Vue 的一些方法,可以在其他地方调用,也可以在指令中使用

Mustache

Mustache 语法也就是 {{}},将数据插入到 HTML 页面中需要

v-once

表示元素或者组件只渲染一次,不会随着数据改版而改变

<body>
    <div id="app">
        <h1>{{name}}</h1>
        <h1 v-once>{{name}}</h1>
    </div>

    <script src="./vue.js"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                name: '小明'
            }
        })

        app.name = '小红';
    </script>
</body>

结果:

小红

小明

v-html

解析 HTML 内容并渲染

<body>
    <div id="app">
        <h1>{{html}}</h1>
        <h1 v-html="html"></h1>
    </div>

    <script src="./vue.js"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                html: '<a href="https://baidu.com">test</a>'
            }
        })
    </script>
</body>

v-text

将数据显示在页面中,接收的值为 string 类型

<body>
    <div id="app">
        <h1>{{text}},Vue</h1>
        <h1 v-text="text"></h1>
    </div>

    <script src="./vue.js"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                text: '你好'
            }
        })
    </script>
</body>

v-pre

跳过元素的编译过程,显示原本内容

<body>
    <div id="app">
        <h1>{{text}}</h1>
        <h1 v-pre>{{text}}</h1>
    </div>

    <script src="./vue.js"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                text: '你好'
            }
        })
    </script>
</body>

结果:

你好

{{text}}

v-cloak

解析之前不显示元素内容

<head>
    <style>
        [v-cloak] {
            display: none;
        }
    </style>
</head>

<body>
    <div id="app" v-cloak>
        <h1>{{text}}</h1>
    </div>

    <script src="./vue.js"></script>
    <script>
        setTimeout(function () {
            const app = new Vue({
                el: '#app',
                data: {
                    text: '你好'
                }
            })
        }, 1000);
    </script>
</body>

v-on

  • 缩写:@

  • 预期:Function | Inline Statement | Object

  • 参数:Event

  • 修饰符:

    • .stop:调用 event.stopPropagation()
    • .prevent:调用 event.preventDefault()
    • .capture:添加事件侦听器时使用 capture 模式
    • .self:只当事件从侦听器绑定的元素本身触发时才触发回调
    • .{KeyAlias}:仅当事件从特定键触发时才触发回调
    • .once:只触发一次回调
    • .left:点击左键触发
    • .right:点击右键触发
    • .middle:点击中键触发
    • .passive:{.passive: true } 模式添加侦听器
  • 用法:绑定事件侦听

<body>
    <div id="app">
        <h2>当前数为:{{num}}</h2>
        <button @click="add()">+</button>
        <button @click="pdd()">-</button>
    </div>

    <script src="./vue.js"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                num: 0
            },
            methods: {
                add() {
                    this.num++;
                },
                pdd() {
                    this.num--;
                }
            },
        })
    </script>
</body>

stop

可以阻止冒泡,当父元素与子元素同时存在某一属性,出发子元素属性不触发父元素属性

<body>
    <div id="app" @click="click1">
        <p>wdnmd</p>
        <button @click.stop="click2">button</button>
    </div>

    <script src="./vue.js"></script>
    <script>
        const app = new Vue({
            el: '#app',
            methods: {
                click1() {
                    console.log("click1");
                },
                click2() {
                    console.log("click2");
                }
            },
        })
    </script>
</body>

prevent

阻止默认事件 ,进入自定义事件

<body>
    <form action="/wdnmd">
        <input type="submit" value="提交" @click.prevent="click1">
    </form>

    <script src="./vue.js"></script>
    <script>
        const app = new Vue({
            el: 'form',
            methods: {
                click1() {
                    console.log("click1");
                },
            },
        })
    </script>
</body>

keyup

按键触发

<body>
    <input type="text" @keyup.enter="click1">

    <script src="./vue.js"></script>
    <script>
        const app = new Vue({
            el: 'input',
            methods: {
                click1() {
                    console.log("click1");
                },
            },
        })
    </script>
</body>

once

只触发一次

<body>
    <input type="text" @keyup.enter.once="click1">

    <script src="./vue.js"></script>
    <script>
        const app = new Vue({
            el: 'input',
            methods: {
                click1() {
                    console.log("click1");
                },
            },
        })
    </script>
</body>

v-bind

动态修改标签的属性

  • a 链接的 href 属性
  • img 的 src 属性

语法糖可直接写成 :

<body>
    <div id="app">
        <img v-bind:src="img" alt="">
        <a v-bind:href="url">{{text}}</a>
        <!-- 语法糖写法 -->
        <img :src="img" alt="">
        <a :href="url">{{text}}</a>
    </div>

    <script src="./vue.js"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                img: './lanye (51).jpg',
                url: 'https://baidu.com',
                text: 'wdnmd'
            }
        })
    </script>
</body>

v-if / v-else-if / v-else

<body>
    <div id="app">
        <p v-if="score>=90"></p>
        <p v-else-if="score>=70"></p>
        <p v-else-if="score>=60"></p>
        <p v-else></p>
    </div>

    <script src="./vue.js"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                score: 80,
            },
        })
    </script>
</body>

v-show

通过 bool 变量来决定是否隐藏或显示元素,不过隐藏方式是 display 改为 none

<body>
    <div id="app">
        <span v-show="show">wdnmd</span>
    </div>

    <script src="./vue.js"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                show: true
            },
        })
    </script>
</body>

v-for

for 循环遍历

数组可获取 index 下标

对象可获取 key 与 value

<body>
    <div id="app">
        <p v-for="item in text1">{{item}}</p>
        <hr>
        <p v-for="(item, index) in text1">{{index + 1}}.{{item}}</p>
        <hr>
        <p v-for="(value, key) in text2">{{key}}-{{value}}</p>
    </div>

    <script src="./vue.js"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                text1: ['小红', '小明', '小黄', '小狗'],
                text2: {
                    name1: '小红',
                    name2: '小明',
                    name3: '小黄',
                    name4: '小狗'
                }
            },
        })
    </script>
</body>

key

官方推荐我们在使用 v-for 时,给对应的元素或组件添加上一个key属性

key的作用主要是为了高效的更新虚拟DOM

<body>
    <div id="app">
        <p v-for="(item, index) in text1" :key="item">{{item}}</p>
    </div>

    <script src="./vue.js"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                text1: ['小红', '小明', '小黄', '小狗'],
            },
        })
    </script>
</body>

VNode

  • VNode 的全程是 Virtual Node,也就是虚拟节点
  • 无论是组件还是元素,它们最终在 Vue 中表示出来的都是一个个 Vnode
  • VNode 本质上是一个 Javascript 的对象

image-20211220111942036

虚拟 DOM

如果不光有一个简单的 div,而是有一大堆元素,那么它们应该会形成一个 VNode Tree

image-20211220112244075

动态绑定

<head>
    <style>
        .color {
            color: red;
        }

        .size {
            font-size: 1.5rem;
        }
    </style>
</head>

<body>
    <div id="app">
        <p v-bind:class="{color: wdnmd, size: wdnmd2}">wdnmd</p>
        <button v-on:click="haha()">点老子</button>
    </div>

    <script src="./vue.js"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                wdnmd: true,
                wdnmd2: true,
            },
            methods: {
                haha() {
                    app.wdnmd = !app.wdnmd;
                    app.wdnmd2 = !app.wdnmd2;
                }
            },
        })
    </script>
</body>

对象语法

  • 绑定class
<body>
    <div id="app">
        <p v-bind:class="getclass()">wdnmd</p>
        <button v-on:click="haha()">点老子</button>
    </div>

    <script src="./vue.js"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                wdnmd: true,
                wdnmd2: true,
            },
            methods: {
                haha() {
                    app.wdnmd = !app.wdnmd;
                    app.wdnmd2 = !app.wdnmd2;
                },
                getclass: function() {
                    return {color: this.wdnmd, size: this.wdnmd2};
                }
            },
        })
    </script>
</body>
  • 绑定style
<body>
    <div id="app">
        <p :style="getclass()">wdnmd</p>
    </div>

    <script src="./vue.js"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {

            },
            methods: {
                getclass: function() {
                    return {color: 'red', 'font-size': '1.5rem'};
                }
            },
        })
    </script>
</body>

数组语法

  • 绑定style
<body>
    <div id="app">
        <p :style="[sz]">wdnmd</p>
    </div>

    <script src="./vue.js"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                sz: {backgroundColor: 'red', fontSize: '100px'}
            },
        })
    </script>
</body>

计算属性 computed

  • 直接当做普通属性调用不加括号
  • 任何 data 中数据变化立即重新计算
  • 计算属性会缓存
  • 含有 set , get 方法
<body>
    <div id="app">
        <p>{{wdnmd}}</p>
    </div>

    <script src="./vue.js"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                firstName: 'Kobe',
                lastName: 'Bryant'
            },
            computed: {
                wdnmd: {
                    set: function (str) {
                        console.log('set');
                        this.firstName = str;
                    },
                    get: function () {
                        console.log('get');
                        return this.firstName + ' ' + this.lastName;
                    }
                }
            },
        })
    </script>
</body>

与 methods 对比

methods 属于方法,每次调用都是重复执行

computed 可在 Vue 中生成缓存,结果一样则调用缓存数据,减少消耗

<body>
    <div id="app">
        <p>{{wdnmd1()}}</p>
        <p>{{wdnmd1()}}</p>
        <p>{{wdnmd1()}}</p>
        <p>{{wdnmd1()}}</p>
        <p>{{wdnmd1()}}</p>
        <p>{{wdnmd2}}</p>
        <p>{{wdnmd2}}</p>
        <p>{{wdnmd2}}</p>
        <p>{{wdnmd2}}</p>
        <p>{{wdnmd2}}</p>
    </div>

    <script src="./vue.js"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                firstName: 'Kobe',
                lastName: 'Bryant'
            },
            methods: {
                wdnmd1: function () {
                    console.log('methods');
                    return this.firstName + ' ' + this.lastName;
                }
            },
            computed: {
                wdnmd2: function () {
                    console.log('computed');
                    return this.firstName + ' ' + this.lastName;
                }
            },
        })
    </script>
</body>

复用解决

当在同一个父元素的 input ,切换的时候数据会复用

使用 key 给 input 赋值上不同的 key 即可解决复用问题

<body>
    <div id="app">
        <span v-if="show">
            <label for="username">用户名:</label>
            <input type="text" id="username" placeholder="请填写用户名" key="username">
        </span>
        <span v-else>
            <label for="email">邮 箱:</label>
            <input type="text" id="email" placeholder="请填写邮箱" key="email">
        </span>
        <button @click="show = !show">切换</button>
    </div>

    <script src="./vue.js"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                show: true
            },
        })
    </script>
</body>

响应式修改数组

使用这些方法可以响应式的去修改数组中的数据

  • unshift():数组最前面添加一个数据
  • push():数组最后面添加一个数据
  • shift():删除数组中第一个数据
  • pop():删除数组中最后一个数据
  • splice():删除/插入/替换 元素
  • sort():正序
  • reverse():倒序
  • Vue.set()修改
<body>
    <div id="app">
        <ul>
            <li v-for="(item, index) in text1">{{item}}</li>
            <button @click="click">button</button>
        </ul>
    </div>

    <script src="./vue.js"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                text1: ['小红', '小明', '小黄', '小狗'],
            },
            methods: {
                click() {
                    // unshift() 数组最前面添加一个数据
                    this.text1.unshift("unshift");

                    // push() 数组最后面添加一个数据
                    this.text1.push("push");

                    // shift() 删除数组中第一个数据
                    this.text1.shift();

                    // pop() 删除数组中最后一个数据
                    this.text1.pop();

                    // splice() 删除/插入/替换 元素
                    // 删除:指定从哪开始,然后删除多少个元素
                    // 插入:指定从哪插入,第二个为0,第三个依次填入数据
                    // 替换:指定从哪开始替换,第二个为替换多少个数据,第三个依次为替换后的数据
                    this.text1.splice(1, 2, 'aaa', 'bbb')

                    // sort() 正序
                    this.text1.sort();

                    // reverse() 倒序
                    this.text1.reverse();

                    // Vue.set()修改
                    Vue.set(this.text1, 0, "aaa");
                }
            },
        })
    </script>
</body>

案例-点击变色

<head>
    <style>
        .active {
            color: red;
        }
    </style>
</head>

<body>
    <div id="app">
        <ul>
            <li v-for="(item, index) in movies" :class="{active: index === currentIndex}" @click="click(index)">{{index}}.{{item}}</li>
        </ul>
    </div>

    <script src="./vue.js"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                movies: ['wdnmd1', 'wdnmd2', 'wdnmd3', 'wdnmd4'],
                currentIndex: 0,
            },
            methods: {
                click(index) {
                    this.currentIndex = index;
                }
            },
        })
    </script>
</body>

过滤器使用

定义过滤器

filters: {
    price(price) {
        return '' + price.toFixed(2);
    }
}

调用过滤器

使用符号 | 分隔

<h1>总价格:{{countPrice | price}}</h1>

购物车案例

<body>
    <div id="app">
        <div v-if="books.length">
            <table>
                <thead>
                    <tr>
                        <th></th>
                        <th>书籍名称</th>
                        <th>出版日期</th>
                        <th>价格</th>
                        <th>购买数量</th>
                        <th>操作</th>
                    </tr>
                </thead>
                <tbody>
                    <tr v-for="(item, index) in books" :key="index">
                        <td>{{item.id}}</td>
                        <td>{{item.name}}</td>
                        <td>{{item.date}}</td>
                        <td>{{item.price | price}}</td>
                        <td>
                            <button @click="del(index)" :disabled="item.count <= 1">-</button>
                            {{item.count}}
                            <button @click="add(index)">+</button>
                        </td>
                        <td>
                            <button @click="remove(index)">删除</button>
                        </td>
                    </tr>
                </tbody>
            </table>
        </div>
        <h2 v-else>购物车为空</h2>
        <h1>总价格:{{countPrice | price}}</h1>
    </div>

    <script src="./vue.js"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                books: [{
                        id: 1,
                        name: 'wdnmd',
                        date: '2001-5-6',
                        price: 50.5,
                        count: 1
                    },
                    {
                        id: 2,
                        name: 'wdnmd',
                        date: '2001-5-6',
                        price: 50.5,
                        count: 1
                    }, {
                        id: 3,
                        name: 'wdnmd',
                        date: '2001-5-6',
                        price: 50.5,
                        count: 1
                    }, {
                        id: 4,
                        name: 'wdnmd',
                        date: '2001-5-6',
                        price: 50.5,
                        count: 1
                    }, {
                        id: 5,
                        name: 'wdnmd',
                        date: '2001-5-6',
                        price: 50.5,
                        count: 1
                    },
                ]
            },
            methods: {
                add(index) {
                    this.books[index].count++;
                },
                del(index) {
                    this.books[index].count--;
                },
                remove(index) {
                    this.books.splice(index, 1);
                }
            },
            computed: {
                countPrice() {
                    let count = 0;
                    for(let i = 0; i < this.books.length; i++) {
                        count += this.books[i].price * this.books[i].count;
                    }
                    return count;
                }
            },
            filters: {
                price(price) {
                    return '' + price.toFixed(2);
                }
            }
        })
    </script>
</body>

高阶函数

filter

自定义过滤规则,返回 bool 类型

let num = [10, 20, 30, 40, 50]
let numA = num.filter(function(n) {
    return n < 30;
})
console.log(numA);

Map

操作数据

let num = [10, 20, 30, 40, 50];
let numA = num.map(function(n) {
    return n + 30;
})
console.log(numA);

reduce

对数组中所有的内容进行汇总

preValue:数组前一次返回的值

let numA = [1, 2, 3, 4, 5];
let numb = numA.reduce(function(value, n) {
    return value + n;
}, 0)
console.log(numb);

v-model

v-model 会与 value 属性进行绑定

当 data 里面的值被修改同时会修改 v-model 绑定的值,同理 v-model 值被修改 data 里面的值也会修改

<body>
    <div id="app">
        <input type="text" v-model="msg">
        {{msg}}
    </div>
    <script src="./vue.js"></script>
    <script>
        const app = new Vue({
            el: "#app",
            data: {
                msg: "wdnmd"
            }
        })
    </script>
</body>

与 radio 单选

<body>
    <div id="app">
        <input type="radio" v-model="sex" value="男">
        <input type="radio" v-model="sex" value="女">
        {{sex}}
    </div>
    <script src="./vue.js"></script>
    <script>
        const app = new Vue({
            el: "#app",
            data: {
                sex: ""
            }
        })
    </script>
</body>

与 checkbox 多选

<body>
    <div id="app">
        <input type="checkbox" value="篮球" v-model="ball">篮球
        <input type="checkbox" value="足球" v-model="ball">足球
        <input type="checkbox" value="羽毛球" v-model="ball">羽毛球
        <input type="checkbox" value="乒乓球" v-model="ball">乒乓球
        {{ball}}
    </div>
    <script src="./vue.js"></script>
    <script>
        const app = new Vue({
            el: "#app",
            data: {
                ball: []
            }
        })
    </script>
</body>

与 select 单多选

<body>
    <div id="app">
        <select v-model="select">
            <option value="苹果">苹果</option>
            <option value="香蕉">香蕉</option>
            <option value="橙子">橙子</option>
        </select>
        {{select}}
        <br />
        <select v-model="select2" multiple>
            <option value="苹果">苹果</option>
            <option value="香蕉">香蕉</option>
            <option value="橙子">橙子</option>
        </select>
        {{select2}}
    </div>
    <script src="./vue.js"></script>
    <script>
        const app = new Vue({
            el: "#app",
            data: {
                select: "",
                select2: []
            }
        })
    </script>
</body>

v-model 修饰符

lazy

v-model.lazy:失去焦点或者按下回车才更新数据

<input type="text" v-model.lazy="msg">

number

v-model.number:数字格式

<input type="number" v-model.number="msg">

trim

v-model.trim:去掉左右空格

<input type="text" v-model.trim="msg">

组件化开发

  1. Vue.extend 创建组件
  2. template 为组件 DOM 内容
  3. Vue.component 来注册全局组件;局部组件为 components:{标签名, 组件名}
  4. 注册后如 html 标签一样使用

组件就是一个vue实例,vue实例的属性,组件也可以有,例如data、methods、computed等。

组件生命周期

  • 生命周期钩子 详细
    beforeCreate 在实例初始化之后,数据观测(data observer) 和 event/watcher 事件配置之前被调用。
    created 实例已经创建完成之后被调用。在这一步,实例已完成以下的配置:数据观测(data observer),属性和方法的运算, watch/event 事件回调。然而,挂载阶段还没开始,$el 属性目前不可见。
    beforeMount 在挂载开始之前被调用:相关的 render 函数首次被调用。
    mounted el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。如果 root 实例挂载了一个文档内元素,当 mounted 被调用时 vm.$el 也在文档内。
    beforeUpdate 数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。你可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程。
    updated 由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。
    activated keep-alive 组件激活时调用。
    deactivated keep-alive 组件停用时调用。
    beforeDestroy 实例销毁之前调用。在这一步,实例仍然完全可用。
    destroyed Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。

全局组件

可以在多个 vue 实例中使用,类似于全局变量

写法:Vue.component(标签名, 组件名)

<body>
    <div id="app">
        <wdnmd></wdnmd>
    </div>

    <script src="./vue.js"></script>
    <script>
        // 创建组件构造器对象
        const test = Vue.extend({
            template: "<h1>wdnmd</h1><h2>wdnmd</h2>"
        })

        // 注册组件(全局)
        Vue.component('wdnmd', test)

        const app = new Vue({
            el: "#app",
        })
    </script>
</body>

局部组件

只能在当前已挂载的 vue 对象中使用

写法:components:{标签名, 组件名}

<body>
    <div id="app">
        <wdnmd></wdnmd>
    </div>

    <script src="./vue.js"></script>
    <script>
        // 创建组件构造器对象
        const test = Vue.extend({
            template: "<h1>wdnmd</h1><h2>wdnmd</h2>"
        })

        const app = new Vue({
            el: "#app",
            // 注册组件(局部)
            components: {
                wdnmd: test
            }
        })
    </script>
</body>

父子组件

下方定义了 test1 与 test2 两个组件,test2 里面注册了 test1组件,然后局部注册了 test2 组件;当调用 test2 组件时,同时会调用 test1 组件,test2 与 test1 组件此时为父子组件

使用子父组件 template 代码需要 div 嵌套起来才能运行

<body>
    <div id="app">
        <wdnmd2></wdnmd2>
    </div>

    <script src="./vue.js"></script>
    <script>
        const test1 = Vue.extend({
            template: "<h1>test1</h1>"
        })
        const test2 = Vue.extend({
            template: "<div><h1>test2</h1><wdnmd1></wdnmd1></div>",
            components: {
                wdnmd1: test1
            }
        })
        const app = new Vue({
            el: "#app",
            components: {
                wdnmd2: test2
            }
        })
    </script>
</body>

语法糖写法

不需要直接实例化对象,直接在注册的时候进行实例化;{} 就是一个组件对象

<body>
    <div id="app">
        <wdnmd1></wdnmd1>
        <wdnmd2></wdnmd2>
    </div>

    <script src="./vue.js"></script>
    <script>
        Vue.component("wdnmd1", {
            template: "<h1>全局组件</h1>"
        })

        const app = new Vue({
            el: "#app",
            components: {
                wdnmd2: {
                    template: "<h1>局部组件</h1>",
                }
            }
        })
    </script>
</body>

组件模板抽离

使用 <script> 或者 <template> 标签都可以做到组件模板抽离

<script> 需要在 type 属性里面加上 text/x-template,两者都必须指定 id 属性才能使用

组件模板必须有一个根(root)

<body>
    <div id="app">
        <wdnmd1></wdnmd1>
        <wdnmd2></wdnmd2>
    </div>

    <template id="wdnmd2">
        <div>
            <h1>template:组件模板抽离</h1>
        </div>
    </template>

    <script src="./vue.js"></script>
    <script type="text/x-template" id="wdnmd1">
        <div>
            <h1>script:组件模板抽离</h1>
        </div>
    </script>
    <script>
        const app = new Vue({
            el: "#app",
            components: {
                wdnmd1: {
                    template: "#wdnmd1",
                },
                wdnmd2: {
                    template: "#wdnmd2"
                }
            }
        })
    </script>
</body>

组件数据存放

vue 组件也是 vue 实例,可以使用 data 属性来存放数据

数据必须是函数

<body>
    <div id="app">
        <wdnmd></wdnmd>
    </div>

    <script>
        const app = new Vue({
            el: "#app",
            components: {
                wdnmd: {
                    template: "<h1>{{title}}</h1>",
                    data() {
                        return {
                            title: "这是标题"
                        }
                    },
                }
            }
        })
    </script>
</body>

父子组件通信

  • 父 -> 子:props 传递数据
  • 子 -> 父:自定义事件传递数据

props 父 -> 子

props 支持数据类型有:

  1. String
  2. Number
  3. Boolean
  4. Array
  5. Object
  6. Date
  7. Fuction
  8. Symbol
  • 定义父组件数据,并且在 component 内指定子组件名(子组件名与标签名一样则填一个即可)

    const app = new Vue({
        el: "#app",
        data: {
            movies: ['a', 'b', 'c']
        },
        components: {
            wdnmd
        }
    })
  • 使用 template 标签创建模板,遍历子组件 props 的自定义变量名

    <template id="wdnmd">
        <ul>
            <li v-for="(item, index) in cmovies">{{item}}</li>
        </ul>
    </template>
  • 创建子组件,template 填入 template 标签设定的 id,props 填入自定义数据名

    const wdnmd = {
        template: "#wdnmd",
        props: ["cmovies"]
    }
  • html 标签中使用模板,并且用 v-bind 来指定子组件与父组件数据对应

    <div id="app">
        <wdnmd :cmovies="movies"></wdnmd>
    </div>

props 类型限制

可以指定数据的类型,不满足则报错

  • 基础类型检查

    const wdnmd = {
        template: "#wdnmd",
        props: {
            cmovies: Array
        }
    }
  • 多个类型限制

    [type1, type2, ...]

    const wdnmd = {
        template: "#wdnmd",
        props: {
            cmovies: [String, Number]
        }
    }
  • 必须提供数据

    required: true

    const wdnmd = {
        template: "#wdnmd",
        props: {
            cmovies: {
                type: Array,
                required: true
            }
        }
    }
  • 如果没传入数据则显示设定的默认数据

    default() {return ...}

    const wdnmd = {
        template: "#wdnmd",
        props: {
            cmovies: {
                type: Array,
                default() {
                    return ["真就没数据呗"]
                }
            }
        }
    }

驼峰标识

如果子组件的变量使用驼峰命名,则在用 v-bind 指定时需要把驼峰位置使用符号 - 代替,并且将大写改成小写

<body>
    <div id="app">
        <!-- 子对象变量需要在驼峰位置使用 - 代替 -->
        <wdnmd :c-movies="movies"></wdnmd>
    </div>

    <template id="wdnmd">
        <ul>
            <!-- 模板也支持子对象变量大写 -->
            <li v-for="(item, index) in cMovies">{{item}}</li>
        </ul>
    </template>

    <script>
        const wdnmd = {
            template: "#wdnmd",
            // 子对象变量大写
            props: ["cMovies"]
        }

        const app = new Vue({
            el: "#app",
            data: {
                movies: ['a', 'b', 'c']
            },
            components: {
                wdnmd
            }
        })
    </script>
</body>

$emit 子 -> 父

子组件中使用 $emit 来触发事件;父组件则使用 v-on 来监听事件

  • 子组件定义需要传到父组件的数据,并定义方法

    this.$emit('自定义事件名', 需要传入的参数)

    const wdnmd = {
        template: "#wdnmd",
        data() {
            return {
                list: [
                    {id: "1", name: "家用电器"},
                    {id: "2", name: "日用护肤"},
                    {id: "3", name: "带你们打"},
                ]
            }
        },
        methods: {
            btn(item) {
                this.$emit('itemclick', item)
            }
        },
    }
  • 父模板定义 v-on 监听事件,方法不需要填入括号

    <div id="app">
        <wdnmd @itemclick="itemclick"></wdnmd>
    </div>
  • 父组件定义方法

    const app = new Vue({
        el: "#app",
        data: {
            msg: ['a', 'b', 'c']
        },
        components: {
            wdnmd
        },
        methods: {
            itemclick(item) {
                console.log(item.name);
            }
        }
    })

父访问子

  1. $children(不常用)

    在父组件下使用 this.$children 来获取子组件,可通过数组取下标来调用子组件方法

    <body>
        <div id="app">
            <wdnmd></wdnmd>
            <button @click="click()">点我</button>
        </div>
    
        <template id="wdnmd1">
            <div>子组件</div>
        </template>
    
        <script src="./vue.js"></script>
        <script>
            const app = new Vue({
                el: "#app",
                components: {
                    wdnmd: {
                        template: "#wdnmd1",
                        data() {
                            return {
                                name: "子组件name"
                            }
                        },
                    }
                },
                methods: {
                    click() {
                        console.log(this.$children[0].name);
                    }
                },
            })
        </script>
    </body>
  2. $refs

    在父组件下使用 $refs 来获取子组件,并且通过 ref 属性在父模板定义子组件的标签

    指定名字后,可通过 this.$refs.ref自定义名.方法名 来获取子组件方法

    <body>
        <div id="app">
            <wdnmd></wdnmd>
            <wdnmd></wdnmd>
            <wdnmd ref="wdnmd1"></wdnmd>
            <button @click="click()">点我</button>
        </div>
    
        <template id="wdnmd1">
            <div>子组件</div>
        </template>
    
        <script src="./vue.js"></script>
        <script>
            const app = new Vue({
                el: "#app",
                components: {
                    wdnmd: {
                        template: "#wdnmd1",
                        data() {
                            return {
                                name: "子组件name"
                            }
                        },
                    }
                },
                methods: {
                    click() {
                        console.log(this.$refs.wdnmd1.name);
                    }
                },
            })
        </script>
    </body>

子访问父

  1. $parent

    子组件通过 $parent 来访问父组件

    <body>
        <div id="app">
            <wdnmd></wdnmd>
        </div>
    
        <template id="wdnmd">
            <div>
                <h1>我是子组件</h1>
                <button @click="btnClick">按钮</button>
            </div>
        </template>
        <script src="./vue.js"></script>
        <script>
            const app = new Vue({
                el: "#app",
                data() {
                    return {
                        name: "name"
                    }
                },
                components: {
                    wdnmd: {
                        template: "#wdnmd",
                        methods: {
                            btnClick() {
                                console.log(this.$parent.name);
                            }
                        },
                    }
                }
            })
        </script>
    </body>
  2. $root

    子组件通过 $root 来获取根组件

    <body>
        <div id="app">
            <wdnmd></wdnmd>
        </div>
    
        <template id="wdnmd">
            <div>
                <h1>我是子组件1</h1>
                <button @click="btnClick">按钮</button>
                <wdnmd1></wdnmd1>
            </div>
        </template>
        <template id="wdnmd1">
            <div>
                <h1>我是子组件2</h1>
                <button @click="btnClick1">按钮</button>
            </div>
        </template>
        <script src="./vue.js"></script>
        <script>
            const app = new Vue({
                el: "#app",
                data() {
                    return {
                        name: "name"
                    }
                },
                components: {
                    wdnmd: {
                        template: "#wdnmd",
                        methods: {
                            btnClick() {
                                console.log(this.$parent.name);
                            }
                        },
                        components: {
                            wdnmd1: {
                                template: "#wdnmd1",
                                methods: {
                                    btnClick1() {
                                        console.log(this.$root.name);
                                    }
                                },
                            }
                        }
                    }
                }
            })
        </script>
    </body>

slot 插槽

组件的插槽是为了让组件更具有拓展性,让使用者决定组件内部展示内容

  • 模板内写入插槽标签 <slot></slot>

    <template id="wdnmd">
        <div>
            <h1>我是子组件1</h1>
            <button @click="btnClick">按钮</button>
            <slot></slot>
        </div>
    </template>
  • 调用在 slot 标签内填入内容

    <div id="app">
        <wdnmd><button>wdnmd</button></wdnmd>
        <wdnmd></wdnmd>
        <wdnmd></wdnmd>
    </div>
  • 插槽可以写入默认值,如果在调用时没有给 slot 内容那么会显示默认值

    <template id="wdnmd">
        <div>
            <h1>我是子组件1</h1>
            <button @click="btnClick">按钮</button>
            <slot>这是slot默认值</slot>
        </div>
    </template>
  • slot 标签也可以填入多个标签,一起作为替换元素

    <div id="app">
        <wdnmd><button>wdnmd</button><button>wdnmd1</button></wdnmd>
        <wdnmd></wdnmd>
        <wdnmd></wdnmd>
    </div>

具名 slot

当有多个 slot 插槽时就需要使用 name 属性来命名

<div id="app">
    <wdnmd><button slot="slot1">按钮</button></wdnmd>
    <wdnmd></wdnmd>
    <wdnmd><b slot="slot3">加粗</b></wdnmd>
</div>

<template id="wdnmd">
    <div>
        <h1>我是子组件1</h1>
        <button @click="btnClick">按钮</button>
        <slot name="slot1"></slot>
        <slot name="slot2"></slot>
        <slot name="slot3"></slot>
    </div>
</template>

作用域插槽

当需要使用 slot 插槽并且调用数据的话,就需要用到

现在 data 中定义好数据,然后在模板的 slot 中使用属性 :data="数据名"(data为自定义名) 来指定数据,最后调用 slot 标签中使用 slot-scope="slot" (slot为自定义名)来调用数据,并且使用 slot.data 来取出数据

<body>
    <div id="app">
        <wdnmd></wdnmd>
        <wdnmd>
            <slot slot-scope="slot">
                <span v-for="(item, index) in slot.data">{{item}} - </span>
            </slot>
        </wdnmd>
    </div>

    <template id="wdnmd">
        <div>
            <slot :data="text">
                <ul>
                    <li v-for="(item, index) in text">{{item}}</li>
                </ul>
            </slot>
        </div>
    </template>
    <script src="./vue.js"></script>
    <script>
        const app = new Vue({
            el: "#app",
            components: {
                wdnmd: {
                    template: "#wdnmd",
                    data() {
                        return {
                            text: ['1', '2', '3', '4', '5']
                        }
                    },
                }
            }
        })
    </script>
</body>

ES 模块化导入导出

解决变量命名冲突问题

  1. 导入 js 时,给上 type 属性,并且填入 module

    <script src="./aaa.js" type="module"></script>
    <script src="./bbb.js" type="module"></script>
    <script src="./ccc.js" type="module"></script>
  2. 使用 export 导出变量与方法

    let name = '小明'
    let age = 18
    let flag = true
    
    function sum(num1, num2) {
        return num1 + num2
    }
    
    export {sum, flag}
  3. 在需要使用的地方使用 import 接收变量与方法

    import {sum, flag} from './aaa.js'
    
    console.log(sum(10, 20));
    
    if(flag) {
        console.log("aaa");
    }
  4. 其他导出方式

    // 导出变量
    export let name = 'xiaohong'
    export let age = 18
    
    // 导出方法
    export function num() {
        return 'aaa'
    }
    
    // 导出类
    export class list {
        listA(name, age) {
            this.name = name;
            this.age = age;
        }
    }

他不允许本地直接路径调用,否则会出现跨域错误!需要搭建服务器访问解决

export default

导出一个不需要命名,可以让使用者自己命名的模块

在同一个模块中只能存在一个 export default

  1. 导出

    export default function(hello) {
        console.log(hello);
    }
  2. 调用时自定义名字

    import wdnmd from './bbb.js'
    wdnmd('hello');

统一全部导入

使用通配符 * 来导入,并且使用 as 来指定一个名字,作为调用名使用

import * as aaa from './aaa.js'
if(aaa.flag) {
    console.log("aaa flag");
}

Vue CLI

安装脚手架

npm install -g webpack
npm install -g @vue/cli
npm install -g @vue/cli-init

// CLI2 初始化
vue init webpack my-project

// CLI3 初始化
vue create my-project

初始化配置解释

CLI2

  • Project name:默认项目名
  • Project description:项目描述
  • Author:作者
  • Vue build:vue 构建方式
  • Install vue-router?:是否安装 vue 路由
  • Use ESLint to lint your code?:是否使用 ESLint 来限制代码规范
  • Set up unit tests:单元测试
  • Setup e2e tests with Nightwatch?:自动化测试

npm run dev 启动 vue 项目

箭头函数

无参无返

const test = () => {
    console.log("wdnmd");
}
test()

有参有返

一个参数时可以省略括号

// 两个参数
const test = (sum1, sum2) => {
    return sum1 + sum2
}
console.log(test(10, 20));

// 一个参数
const test2 = num => {
    return num * num
}
console.log(test2(20));

代码块只有一行

可以省略掉方法体括号以及 return,自动将一行代码结果 return

const test3 = num => num * num
console.log(test3(20));

Vue.prototype 全局定义

如果需要设置全局变量,在main.js中,Vue实例化的代码里添加。

Vue.prototype.$appName = My App

这样 $appName 就在所有的 Vue 实例中可用了,甚至在实例被创建之前就可以。如果我们运行:

new Vue({
    beforeCreate: function () {
        console.log(this.$appName)
    }
})

则控制台会打印出 My App

Vue-Router 路由

路由决定了数据包来源到目的地的路径

将输入端的数据转到合适的输出端

安装

cnpm install vue-router --save

前端改变 url 不刷新

直接去路由映射寻找需要的组件,并返回前端显示

所有导向过程都会放入栈结构,而返回操作则是取出栈结构

hash

向栈结构存入

location.hash = 'wdnmd'

history

向栈结构存入

history.pushState({}, '', 'wdnmd')

从栈结构取出,url 返回上一级

history.back()

从栈结构放回返回的东西,url 前进后一级

history.forward()

直接替换 url,不存入栈结构

history.replaceState({}, '', 'wdnmd')

直接跳转到某一栈的位置

// 基于当前位置返回栈 2 个位置
history.go(-1)
history.go(-2)
// 基于当前位置前进栈 2 个位置
history.go(1)
history.go(2)

创建路由配置

// 配置路由相关信息
import Vue from 'vue'
import VueRouter from 'vue-router'

// 通过 Vue.use 来安装插件
Vue.use(VueRouter)

// 创建 VueRoutes 对象
const routes = [

]

const router = new VueRouter({
    routes
})

// 将 router 对象传入到 Vue 实例中
export default router

在 main.js 中挂载

import Vue from 'vue'
import App from './App.vue'
// 挂载
import router from './router'

Vue.config.productionTip = false

new Vue({
    // 引用
    router,
    render: h => h(App)
}).$mount('#app')

映射路由器配置

创建路由组件

<template>
    <div>
        <h1>我是hello</h1>
    </div>
</template>

<script>
    export default {
        name: "hello"
    }
</script>

配置路由映射

// 导入 vue 模块
import home from '../components/hello'
import about from '../components/about'

// 通过 Vue.use 来安装插件
Vue.use(VueRouter)

// 创建 VueRoutes 对象
const routes = [
  // 配置 vue 模块映射配置
  {
    path: '/hello',
    component: home
  },
  {
    path: '/about',
    component: about
  }
]

通过 router-link 与 router-view 来显示

<template>
  <div id="app">
    <router-link to="/hello">hello</router-link>
    <br />
    <router-link to="/about">about</router-link>
    <router-view></router-view>
  </div>
</template>

router-link 最终会渲染成 a 标签,router-view 会根据当前路径渲染出不同的组件

路由默认路径

网站一进入默认载入显示组件

path 设置为 / 默认网站根目录,然后通过 redirect 重定向到首页

const routes = [
    //默认重定向
    {
        path: '/',
        redirect: '/hello'
    },
    // 配置 vue 模块映射配置
    {
        path: '/hello',
        component: home
    },
    {
        path: '/about',
        component: about
    }
]

修改为 history 模式

路由配置的 router 函数里面添加一项 mode

const router = new VueRouter({
  routes,
  mode: 'history'
})

router-link 其他属性

  • tag:可以修改默认渲染标签

    <router-link to="/hello" tag="button">hello</router-link>
    <router-link to="/about" tag="button">about</router-link>
  • replace:使用 history.replaceState 来跳转组件

    <router-link to="/hello" tag="button" replace>hello</router-link>
    <router-link to="/about" tag="button" replace>about</router-link>
  • linkActiveClass:当前选择的组件 class 名字自定义

    const router = new VueRouter({
      routes,
      mode: 'history',
      linkActiveClass: 'active'
    })

路由代码跳转

通过 this.$router.push 来实现

<template>
    <div id="app">
        <button @click="homeClick">home</button>
        <button @click="aboutClick">about</button>
        <router-view></router-view>
    </div>
</template>

<script>
    export default {
        name: 'App',
        methods: {
            homeClick() {
                this.$router.push('/hello')
            },
            aboutClick() {
                this.$router.push('/about')
            }
        },
    }
</script>

<style>
</style>

动态路由

访问路由映射的组件时传递一些数据

  1. 配置路由映射,添加指定参数名字

    const routes = [
        {
            path: '',
            redirect: '/hello'
        },
        {
            path: '/hello',
            component: home
        },
        {
            // 添加参数名
            path: '/about/:name',
            component: about
        }
    ]
  2. 跳转时需要带上参数

    <router-link :to="'/about/' + name" tag="button">about</router-link>
  3. 通过 $route 取出当前活跃组件的数据

    <template>
        <div>
            <h1>我是关于{{username}}</h1>
        </div>
    </template>
    
    <script>
        export default {
            name: 'about',
            computed: {
                username() {
                    return this.$route.params.name
                }
            },
        }
    </script>

路由懒加载

将对应的 js 打包成一个个的 js 块,只有在路由被访问时才加载响应组件

路由映射组件方式改为调用组件则映射

// 导入 vue 模块
const home = () => import('../components/hello')
const about = () => import('../components/about')

路由嵌套实现

定义两个子组件,然后在路由中导入

const aboutA = () => import('../components/aboutA')
const aboutB = () => import('../components/aboutB')

使用 children 来配置嵌套路由映射

CLI3 中 嵌套路由映射的 path 不需要加上 /

const routes = [
  {
    path: '',
    redirect: '/hello'
  },
  {
    path: '/hello',
    component: home
  },
  {
    path: '/about',
    component: about,
    // 嵌套路由映射
    children: [
      {
        path: 'aboutA',
        component: aboutA
      },
      {
        path: 'aboutB',
        component: aboutB
      }
    ]
  }
]

然后需要在父组件中使用 router-linkrouter-view 来显示子组件

to 属性需要指定子组件绝对路径

<router-link to="/about/aboutA">aboutA</router-link>
<router-link to="/about/aboutB">aboutB</router-link>
<router-view></router-view>

当然,路由嵌套也可以实现默认重定向

const routes = [
    {
        path: '',
        redirect: '/hello'
    },
    {
        path: '/hello',
        component: home
    },
    {
        path: '/about',
        component: about,
        // 嵌套路由映射
        children: [
            // 默认重定向
            {
                path: '',
                redirect: 'aboutA'
            },
            {
                path: 'aboutA',
                component: aboutA
            },
            {
                path: 'aboutB',
                component: aboutB
            }
        ]
    }
]

vue-router 参数传递

通过 query 传递参数

  1. 使用 v-bind 传递一个对象,对象包含 path 与 query

    <template>
        <div id="app">
            <router-link to="/hello" tag="button">hello</router-link>
            <router-link to="/about" tag="button">about</router-link>
            <router-link :to="user" tag="button">user</router-link>
            <router-view></router-view>
        </div>
    </template>
    
    <script>
        export default {
            name: 'App',
            data() {
                return {
                    user: {
                        path: '/user',
                        query: {
                            name: 'xiaoming',
                            age: 18,
                            height: 188
                        }
                    }
                }
            },
        }
    </script>
  2. 对应模块中使用 $route.query 来调用参数

    <template>
        <div>
            <h1>user</h1>
            <p>{{$route.query.name}}</p>
            <p>{{$route.query.age}}</p>
            <p>{{$route.query.height}}</p>
        </div>
    </template>

代码实现参数传递

<template>
    <div id="app">
        <button @click="userClick">buttonClickUser</button>
        <router-view></router-view>
    </div>
</template>

<script>
    export default {
        name: 'App',
        methods: {
            userClick() {
                this.$router.push({
                    path: '/user',
                    query: {
                        name: 'xiaohong',
                        age: 88,
                        height: 22
                    }
                })
            }
        },
    }
</script>

<style>
</style>

全局导航守卫

使用 router.beforeEach 检测组件创建,修改 document 标题

前置钩子 router.beforeEach

  1. 使用 meta 设置每个组件的 title 变量

    const routes = [
        {
            path: '',
            redirect: '/hello'
        },
        {
            path: '/hello',
            component: home,
            meta: {
                title: '首页'
            }
        },
        {
            path: '/about',
            component: about,
            meta: {
                title: '关于'
            },
            children: [{
                path: '',
                redirect: 'aboutA'
            },
                       {
                           path: 'aboutA',
                           component: aboutA
                       },
                       {
                           path: 'aboutB',
                           component: aboutB
                       }
                      ]
        },
        {
            path: '/user',
            component: user,
            meta: {
                title: 'user'
            }
        }
    ]
  2. router.beforeEach 监听组件激活

    router.beforeEach((to, from, next) => {
      document.title = to.matched[0].meta.title
      next()
    })
  • to:即将进入的目标路由对象
  • from:当前导航正要离开的路由
  • next():进入下一个钩子
    • next(false):中断导航,如果 url 改变则重置到 from 路由对应地址
    • next('/')或者next({ path: '/'}):跳转到不同的地址,中断当前导航并进行新的导航。
    • next(error):(2.4.0+) 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调。

后置钩子 router.afterEach

router.afterEach((to, from) => {
  console.log("afterEach");
})

组件内的守卫

  • beforeRouteEnter:组件加载前
  • beforeRouteUpdate:当路由改变,组件复用时调用(/user/:id 中来回调用 /user/1 + /user/2 时调用)
  • beforeRouteLeave:导航离开该组件前调用

keep-alive

阻止组件被销毁,与频繁的创建

router-view 标签使用 keep-alive 包裹住即可

<keep-alive>
    <router-view />
</keep-alive>

使用这个标签后即可使用 activateddeactivated 两个函数

  • activated :创建组件时
  • deactivated :销毁组件后

keep-alive 属性

  • include:字符串或正则表达式,选中的组件都不会被缓存
  • exclude:字符串或正则表达式,选中的组件都会被缓存

router-view 如果直接被包含在 keep-alive ,则所有组件都会被缓存

选择多个组件用 , 分割,不能添加空格

<keep-alive include="user,about">
    <router-view />
</keep-alive>

Promise 异步构造函数

  • 它表示代码会一步一步进行操作,也就是异步操作
  • Promise 有两个函数 resolve(成功后回调函数) 与 reject(失败后回调函数)
  • 成功后进入 then 方法,失败则进入 catch 方法

异步操作使用 Promise ,拿到结果后通过 resolve 来进行下一步的 then 处理;如果没拿到结果则调用 reject 来进行下一步的 catch 处理

new Promise((resolve, reject) => {
    let data = 1
    resolve(data)
}).then((data) => {
    console.log(data++);
    return new Promise((resolve, reject) => {
        resolve(data)
    })
}).then((data) => {
    console.log(data++);
    return new Promise((resolve, reject) => {
        resolve(data)
    })
}).then((data) => {
    console.log(data++);
    return new Promise((resolve, reject) => {
        reject("error")
    })
}).catch((err) => {
    console.log(err);
})

then 与 catch 的另一种写法

new Promise((resolve, reject) => {
    // resolve("1")
    reject("error")
}).then((data) => {
    console.log(data);
}, (error) => {
    console.log(error);
})

Promise.all

多个请求的使用

Promise.all([
    new Promise((resolve, reject) => {
        resolve("1")
    }),
    new Promise((resolve, reject) => {
        resolve("2")
    })
]).then((results) => {
    console.log(results);
})

Vuex

全局可获取或者修改的变量,支持响应式

安装

cnpm install vuex --save
  1. src 目录下新建目录 store,并且创建 index.js 文件

    import Vue from 'vue'
    import Vuex from 'vuex'
    
    // 安装插件
    Vue.use(Vuex)
    
    // 创建对象
    const store = new Vuex.Store({
    
    })
    
    // 导出对象
    export default store
  2. main.js 引入 store 对象

    import Vue from 'vue'
    import App from './App.vue'
    import router from './router'
    import store from './store'
    
    Vue.config.productionTip = false
    
    new Vue({
        store,
        router,
        render: h => h(App)
    }).$mount('#app')

多界面状态管理

store 对象添加 state 属性,并且写入数据

// 创建对象
const store = new Vuex.Store({
    state: {
        num: 100
    }
})

组件调用

<h1>{{$store.state.num}}</h1>

mutations

mutations 属性下定义需要的方法

// 创建对象
const store = new Vuex.Store({
    state: {
        num: 100
    },
    mutations: {
        jia(state) {
            state.num++
        },
        jian(state) {
            state.num--
        }
    },
})

使用 $store.commit 调用 store 方法

export default {
    name: 'app',
    methods: {
        jia() {
            this.$store.commit('jia')
        },
        jian() {
            this.$store.commit('jian')
        }
    },
}

传递对象提交

使用对象传递,type 为 mutations 中的方法名;使用 payload 接收

export default {
    name: 'app',
    methods: {
        addStu() {
            this.$store.commit({
                type: 'addStu',
                a: {
                    name: 'xiaohong',
                    age: 20
                }
            })
        }
    },
}
const store = new Vuex.Store({
    state: {
        num: 100,
        student: [
            {
                name: 'xiaoming',
                age: 18
            },
        ]
    },
    mutations: {
        addStu(state, payload) {
            state.student.push(payload)
        }
    }
})

响应规则

  • 提前在 store 中初始化好所需的属性
  • 当给 state 中的对象添加属性时,使用下面的方式
    • Vue.set(obj, newProp, 123)
    • 用新的对象重新赋值

类型常量

store 目录下新建类型常量文件

并且将需要定义为常量的方法导出

export const jia = 'jia'

需要调用方法的时候接收一下

import {
    jia
} from './mutations'

方法实现

const store = new Vuex.Store({
    state: {
        num: 100,
        student: [
            {
                name: 'xiaoming',
                age: 18
            },
        ]
    },
    mutations: {
        [jia](state) {
            state.num++
        }
    }
})

方法调用

import {
    jia
} from './store/mutations'

export default {
    name: 'app',
    methods: {
        jia() {
            this.$store.commit(jia)
        }
    }
}

actions 同步函数

  • Mutations 中的方法必须是同步方法
    • devtools 在同步的情况下可以捕获到快照
    • 异步操作则无法捕获
  1. store 目录下 index.js 写入 actions 对象

    const store = new Vuex.Store({
        state: {
            student: [
                {
                    name: 'xiaoming',
                    age: 18
                },
            ]
        },
        mutations: {
            // 修改
            replace(state) {
                state.student[0].name = "wdnmd"
            }
        },
        actions: {
            // 实现异步操作
            replacee(context) {
                setTimeout(() => {
                    context.commit('replace')
                }, 1000);
            }
        }
    })
  2. 调用 action 方法

    export default {
        name: 'app',
        methods: {
            replace() {
                this.$store.dispatch('replacee')
            }
        },
    }

getters

当一个数据需要经过一系列操作后得到的

// 创建对象
const store = new Vuex.Store({
    state: {
        num: 100
    },
    getters: {
        // 直接返回 state 数据
        cheng(state) {
            return state.num * state.num
        },
        // 返回 getters 其他方法的数据
        cheng1(getters) {
            return getters.cheng
        },
        // 返回有参数据
        cheng2(state) {
            return a => {
                return state.num + a
            }
        }
    }
})
<h2>{{$store.getters.getText}}</h2>

Module

Vue 属于单一状态树,许多状态会交给 Vuex 来管理;应用过于复杂时 story 则非常臃肿。

此时就可以使用 Module 抽离出来新的模块方便管理

module 的对象写法必须写在 story 对象前面才能运行!否则会出现 初始化前无法访问 的报错

const moduleA = {
    state: {
        text: 'aModule'
    }
}

const moduleB = {
    state: {
        text: 'bModule'
    }
}

const store = new Vuex.Store({
    state: {
        user: [
            {
                name: 'xiaoming',
                age: 18
            }
        ]
    },
    modules: {
        a: moduleA,
        b: moduleB
    }
})

命名不允许冲突

在子模块的方法之类的是不允许与 story 对象的方法名字冲突

如果当 commit 调用方法时,会先去 store 对象寻找,未找到则会去子模块寻找

所以命名不会出现冲突

axios

特点

  • 浏览器发送 XMLHttpRequests 请求
  • 在 node.js 中发送 http 请求
  • 支持 Promise API
  • 拦截请求和响应
  • 转换请求和相应数据
  • ...

安装

cnpm install axios --save

axiox 基本使用

  • main.js 导入

    import axiox from 'axios'
  • 请求 api 并 log 输出

    let serverInterface = 'api地址'
    
    axiox({
        url: serverInterface
    }).then(res => {
        console.log(res);
    })

全局配置

每一个组件可通过 $http 来发起 axios 请求

Vue.prototype.$http = axios

get 请求参数拼接

axiox({
    url: serverInterface,
    params: {
        type: 'test'
    }
}).then(res => {
    console.log(res);
})

axiox.all 多个请求

当多个请求有结果执行 then

axiox.all([
    axiox({
        url: serverInterface
    }),
    axiox({
        url: serverInterface
    })
]).then(a => {
    console.log(a[0]);
    console.log(a[1]);
})

常用配置信息

  • url:'/user':请求地址
  • method:'get':请求类型
  • baseURL:'':请求根路径
  • transformRequest:[fuction(data){}]:请求前的数据处理
  • transformResponse:[fuction(data){}]:请求前的数据处理
  • headers:{'x-Requested-With':'XMLHttpRequest'}:自定义请求头
  • params:{id:12}:URL 查询对象
  • paramsSerializer:fuction(params){}:查询对象序列化函数
  • data:{key:'aa'}:request body
  • timeout:1000:超时设置
  • withCredentials:false:跨域是否带 token
  • adapter:fuction(resolve, reject, config){}:自定义请求处理
  • auth:{uname:'', pwd:'12'}:身份验证信息
  • responseType:'json':响应的数据格式,支持 / json / blob / document / arraybuffer / text / stream

axios 实例和模块封装

在 src 中 network 文件夹的 request.js 文件进行封装配置

import axios from 'axios'

export function request(config) {
    const test = axios.create({
        baseURL: 'https://gitee.com/api/v5/repos/nutssss/cdn/issues?access_token=0d4656bf06d57b8da4cee1fadba777e6&state=open&sort=created&direction=desc&page=1&per_page=20',
        timeout: 5000
    })
    return test(config)
}

导入 request,进行调用使用

import {request} from './network/request'

request({
  url: ''
}).then(res => {
  console.log(res);
}).catch(err => {
  console.log(err);
})

axios 拦截器

请求拦截

拦截掉当前请求,处理后需要 return 请求进行后续其他处理

  • config 中一些信息不符合要求
  • 每次网络请求需要做的事情
  • token 携带的一些信息
// 添加请求拦截器
axios.interceptors.request.use(function (config) {
    // 在发送请求之前做些什么
    return config;
}, function (error) {
    // 对请求错误做些什么
    return Promise.reject(error);
});

// 添加响应拦截器
axios.interceptors.response.use(function (response) {
    // 对响应数据做点什么
    return response;
}, function (error) {
    // 对响应错误做点什么
    return Promise.reject(error);
});

在请求头上添加 token

API 要求:需要授权的 API ,必须在请求头中使用 Authorization 字段提供 token 令牌

// 拦截请求,在头部增加 token 信息
initialize.interceptors.request.use(res => {
    // 获取 token
    res.headers.Authorization = window.sessionStorage.getItem('token')
    return res;
})

配置整合

import axios from 'axios'

export function request(config) {
    // 创建 axios 实例
    const test = axios.create({
        baseURL: 'url',
        timeout: 5000
    })

    // 请求拦截器
    test.interceptors.request.use(res => {
        console.log(res);
        return res
    }, err => {
        console.log(err);
    })

    // 响应拦截器
    test.interceptors.response.use(res => {
        return res.data;
    }, err => {
        console.log(err);
    })

    // 发送网络请求
    return test(config)
}

路径别名配置

src 根目录新建配置文件 vue.config.js

const path = require('path');

function resolve(dir) {
    return path.join(__dirname, dir);
}
module.exports = {
    lintOnSave: true,
    chainWebpack: (config) => {
        config.resolve.alias
            .set('@', resolve('src'))
            .set('assets',resolve('src/assets'))
            .set('components',resolve('src/components'))
            .set('network',resolve('src/network'))
            .set('views',resolve('src/views'))
    }
};

html 标签内引用需要加上 ~号,JavaScript 里面引用则不需要

Token

存储与取出

  1. 存储

    window.sessionStorage.setItem('token', tokenString);
  2. 取出

    window.sessionStorage.getItem('token');
  3. 清空

    window.sessionStorage.clear()

全局路由守卫判断 token

防止越权访问

// 全局路由防止越权访问
router.beforeEach((to, from, next) => {
    // 访问 login 登陆则放行
    if(to.path === "/login") return next();
    // 判断是否存在 token
    if(window.sessionStorage.getItem('token')) {
        console.log("存在token 放行");
        return next()
    } else {
        console.log("不存在token,爬");
        return next("/login")
    }
})

axios 上传文件

fileUpload() {
    // 获取文件信息
    let faceFile = document.querySelector(".inputFile").files[0];
    // 创建 form 表单
    let formData = new FormData();
    // 文件写入
    formData.append("faceFile", faceFile, faceFile.name);
    // 设置头部
    let config = {
        headers: {
            'Content-Type': 'multipart/form-data',
            'headerUserToken': this.$stor.session.get("userInfo").token
        }
    };
    // 请求发送
    this.$http.post(this.API.uploadFace + "/" + this.$stor.session.get("userInfo").userId, formData, config).then(res => {
        if (res.status == 200) {
            Toast({
                message: '修改成功'
            });
        }
    }, err => {
        console.log(err);
    })
}

监听滚动条事件

export default {
    data () {
        return {
            // 滚动条的高度
            scrollTop: 0
        }
    },

    methods: {
        // 保存滚动值,这是兼容的写法
        handleScroll () {
            this.scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop
        },

        // 滚动条回到顶部
        backTop () {
            if (this.scrollTop > 10) {
                document.documentElement.scrollTop = 0
            }
        }
    },

    mounted () {
        window.addEventListener('scroll', this.handleScroll, true)
    },

    destroyed () {
        // 离开该页面需要移除这个监听的事件,不然会报错
        window.removeEventListener('scroll', this.handleScroll)
    }
}

个人常用 npm

font-awesome

图标库

npm 安装

npm install font-awesome --save

main.js 引入

import "font-awesome/css/font-awesome.min.css";

使用即可

scrollreveal

动画重复播放

npm 安装

npm install scrollreveal --save

封装

// 重复动画
import scrollReveal from 'scrollreveal';

// 启用动画方法
export default {
    Reset: (dom) => {
        let a = scrollReveal();
        dom.forEach(item => {
            a.reveal(item, {
                // 动画的时长
                duration: 500,
                // 延迟时间
                delay: 200,
                // 动画开始的位置,'bottom', 'left', 'top', 'right'
                origin: "bottom",
                // 回滚的时候是否再次触发动画
                reset: true,
                // 在移动端是否使用动画
                mobile: false,
                // 滚动的距离,单位可以用%,rem等
                distance: "0",
                // 其他可用的动画效果
                opacity: 0.001,
                easing: "cubic-bezier(.06,-0.14,.42,1.5)",
                scale: 0.8,
            });
        });
    },
    NoReset: (dom) => {
        let a = scrollReveal();
        dom.forEach(item => {
            a.reveal(item, {
                // 动画的时长
                duration: 500,
                // 延迟时间
                delay: 300,
                // 动画开始的位置,'bottom', 'left', 'top', 'right'
                origin: "bottom",
                // 在移动端是否使用动画
                mobile: false,
                // 滚动的距离,单位可以用%,rem等
                distance: "0",
                // 其他可用的动画效果
                opacity: 0.001,
                easing: "cubic-bezier(.06,-0.14,.42,1.5)",
                scale: 0.8,
            });
        });
    },
}

main.js 引入

// 重复动画
import scrollReveal from './assets/js/scrollReveal';
Vue.prototype.scrollAni = scrollReveal;

使用

// 动画
this.scrollAni.NoReset([".haha", ".TitleBox", ".footer"]);

lazy-load

图片懒加载

[vue-lazyload - npm (npmjs.com)](https://www.npmjs.com/package/vue-lazyload)

npm 安装

npm install vue-lazyload --save-dev

main.js 引入

// 图片懒加载
import VueLazyload from 'vue-lazyload';
Vue.use(VueLazyload, {
    preLoad: 1.3,
    loading: require("./assets/images/loading.gif"),
    error: require('./assets/images/error.jpg'),
    attempt: 1
});
Vue.use(VueLazyload);

使用

<img v-lazy="img" :key="img" />

wangEditor

富文本编辑器

Introduction · wangEditor 用户文档

npm 安装

npm i wangeditor --save

main.js 引入

// 富文本编辑器
import E from 'wangeditor'
Vue.prototype.E = E;

使用

// 富文本配置
createEditor() {
    // 媒体上传oos配置
    // region以杭州为例(oss-cn-hangzhou),其他region按实际情况填写。
    // 阿里云主账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM账号进行API访问或日常运维,请登录RAM控制台创建RAM账号。
    let client = new this.oos({
        region: "xxx",
        accessKeyId: "xxx",
        accessKeySecret: "xxx",
        bucket: "tucao-haha",
    });
    // 富文本编辑器启用
    let ed = new this.E(".edMenu", ".ed");
    //  菜单配置
    ed.config.menus = [
        "head",
        "bold",
        // "fontSize",
        // "fontName",
        "italic",
        "underline",
        "strikeThrough",
        // "indent",
        // "lineHeight",
        // "foreColor",
        //"backColor",
        "link",
        "list",
        // "todo",
        "justify",
        "quote",
        // "emoticon",
        "image",
        "video",
        "table",
        // "code",
        "splitLine",
        "undo",
        "redo",
    ];
    // 关闭粘贴样式
    ed.config.pasteFilterStyle = false;
    // 代码高亮
    ed.highlight = this.hljs;
    // 提示文本
    ed.config.placeholder = "开始您的吐槽吧!";

    // 图片上传
    // resultFiles 是 input 中选中的文件列表
    // insertImgFn 是获取图片 url 后,插入到编辑器的方法
    ed.config.customUploadImg = (resultFiles, insertImgFn) => {
        let vm = this;
        vm.loadStart();
        resultFiles.forEach(item => {
            let info = item.name.split(".");
            client
                .put(`${info[0]}-${Date.parse(new Date())}.${info[1]}`, item)
                .then(function (res) {
                // 上传图片,返回结果,将图片插入到编辑器中
                insertImgFn(res.url);
                vm.imgBack();
            })
                .catch(function (err) {
                console.log(err);
            });
        });
    };
    // 视频上传
    // resultFiles 是 input 中选中的文件列表
    // insertVideoFn 是获取视频 url 后,插入到编辑器的方法
    ed.config.customUploadVideo = (resultFiles, insertVideoFn) => {
        let vm = this;
        vm.loadStart();
        let info = resultFiles[0].name.split(".");
        client
            .put(`${info[0]}-${Date.parse(new Date())}.${info[1]}`, resultFiles[0])
            .then(function (res) {
            // 上传视频,返回结果,将视频插入到编辑器中
            insertVideoFn(res.url);
            vm.videoBack();
        })
            .catch(function (err) {
            console.log(err);
        });
    };

    // 图片上传大小
    ed.config.uploadImgMaxSize = 5 * 1024 * 1024;
    //   图片上传类型
    ed.config.uploadImgAccept = ["jpg", "jpeg", "png", "gif", "bmp"];
    // 一次最多上传 5 个图片
    ed.config.uploadImgMaxLength = 5;

    // 配置 onchange 回调函数
    ed.config.onchange = (content) => {
        this.article.articleContent = content;
    }

    // 创建
    ed.create();

    // 默认样式修改
    document.querySelector(".w-e-toolbar").style.background = "transparent";
    document.querySelector(".w-e-toolbar").style.borderWidth = "0";
    document.querySelector(".w-e-text-container").style.background =
        "transparent";
    document.querySelector(".w-e-text-container").style.border =
        "transparent";

    // 赋值内容
    ed.txt.html(this.article.articleContent);
},

Viewer

yarn add viewerjs
if (!this.Viewer) {
    return (this.Viewer = new Viewer(document.querySelector("#content")));
}
this.Viewer.update();

.prettierrc

格式化工具

  1. 使用插件本身
  2. 或者命名行配置

步骤

  1. npm i prettier -D
  2. 创建.prettierrc文件
  3. packjson 新增启动配置 "pre": "prettier --write ."
useTabs 使用tab缩进还是空格
tabWidth tab是空格情况下是几个空格
printWidth 当行字符的长度,推荐80
singleQuote 使用单引号还是双引号 true为单
trailingComma 在多行输入的尾逗号是否添加
semi 语句末尾是否要加分号,默认true
{
    "useTabs": false,
    "semi": true,
    "singleQuote": false,
    "bracketSpacing": true,
    "trailingComma": "none",
    "tabWidth": 4,
    "printWidth": 80
}
  1. 在根目录下创建.Prettierignore 忽略默些文件不使用当前格式化
/dist/*
.local
.output.js
/node_modules/**
**/*.svg
**/*.sh
.public/*
  1. 安装 Prettier - Code formatter插件
  2. Package,json在scripts 写"pre": "prettier --write ." npm一下 就可以全部文件格式化

解决eslint和prettierrc冲突

  1. vue create 时 可以选择安装这个包,没有就安装
npm i eslint-plugin-prettier eslint-config-prettier -D
  1. 在.eslintrc.js添加
  extends: [
    "plugin:vue/vue3-essential",
    "eslint:recommended",
    "@vue/typescript/recommended",
    "@vue/prettier",
    "@vue/prettier/@typescript-eslint",
    "plugin:prettier/recommended"
  ],

修改 title

vue.config.js 里面配置

module.exports = {
    // 修改或新增html-webpack-plugin的值,在index.html里面能读取htmlWebpackPlugin.options.title
    chainWebpack: config =>{
        config.plugin('html')
            .tap(args => {
            args[0].title = "宝贝商城";
            return args;
        })
    }
};

然后修改掉 dist 中 index.html 的 title 为

<title><%= htmlWebpackPlugin.options.title %></title>
1
https://gitee.com/n0ts/note.git
git@gitee.com:n0ts/note.git
n0ts
note
note
master

搜索帮助