Vue

2022/3/6 Vueui

vue2+3笔记

# Vue2

# Vue安装

  1. CDN引入

    <!-- 开发环境版本,包含了有帮助的命令行和警告-->
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
    
    
    <!-- 生产环境版本,进行了压缩,优化了尺寸和速度-->
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14"></script>
    
    1
    2
    3
    4
    5
    6
  2. 下载引用

    开发环境版本
    https://cn.vuejs.org/js/vue.js
    
    生产环境版本
    https://cn.vuejs.org/js/vue.min.js
    
    1
    2
    3
    4
    5
  3. npm安装

    # 最新稳定版
    npm install -g @vue/cli
    
    1
    2

vite构建vue项目

# 创建工程
npm init vite-app <project-name>
# 进入工程项目
cd <project-name>
# 安装依赖
npm install
# 运行
npm run dev
1
2
3
4
5
6
7
8

webpack构建vue项目

# 安装webpack
npm install webpack -g
# 安装vue-cli
npm install vue-cli -g
# 创建vue项目 webpack(模板名称)
vue init webpack <project-name>
# 进入工程项目
cd <project-name>
# 安装模块
npm install vue-router vue-resource --save
# 启动项目
npm run dev
1
2
3
4
5
6
7
8
9
10
11
12

# 基础

# 插值语法

{{}}

双括号获取vue对象中data的数据

==容器与实例是一对一的关系== 一个实例只能绑定一个容器

<body>
    <div id="app">{{message}}</div>
</body>
<script>
    // let(变量)/const(常量)
    // 编程范式:声明式编程
    const app = new Vue({
        // 用于挂载要管理的元素
        el: '#app',
        data: {
            //定义数据
            message: '你好,世界'
        }
    })

    // 原始js的做法(编程范式:命令式编程)
    /* 
         1.创建div元素,设置id属性
         2.定义一个变量叫message
         3.将message变量放在前面的div元素中显示
         4.修改message的数据:hello world
         5.修改数据就行无需修改页面
    */
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

image-20211023105204847

修改元素数据直接修改data中的数据

image-20211023105241164

image-20220211154111395

括号里只能写js表达式

==什么是响应式==

数据修改后页面自动改变

# 事件绑定

v-bind 单向数据绑定

<h1><a v-bind:href="url">百度</a></h1>
<!-- 简写: v-bind:href  :href -->

<script>
    new Vue({
        el: '#root',
        data: {
            message: 'abc',
            url:"https://www.baidu.com"
        }
    });
</script>
1
2
3
4
5
6
7
8
9
10
11
12

image-20220212162701571

<div id="root">
    单向数据绑定:<input type="text" :value="name">
    <br>
    双向数据绑定:<input type="text" v-model="name">  <!--v-model:value="name"-->
</div>

<script>
    new Vue({
        el:"#root",
        data: {
            name: "123"
        }
    });
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14

==v-model只能用在表单控件(输入类元素上)==

# 多层data

<div id="root">
    <h1>你好,{{name}}</h1>
    <br>
    <a :href="school.url">点我去{{school.name}}</a>
</div>

<script>
    Vue.config.productionTip = false;

    const c = new Vue({
        el: '#root',
        data: {
            name: "jack",
            school:{
                name: "lisa",
                url: "http://www.baidu.com"
            }
        }
    });
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 事件

配置项:methods

绑定:@click是v-on:click 语法(简写)

image-20220213174859839

<div id="root">
    <!-- 简写 @click="showInfo1"  showInfo1($event,66)使event 占位并可以传数字 -->
    <button v-on:click="showInfo1($event,66)">button1</button>
    <button @click="showInfo2">button2</button>
</div>

<script>
    new Vue({
        el: '#root',
        methods: {
            showInfo1(event, number) { //接收传过来的event和数字
                console.log(number);
                alert("1")
            },
            showInfo2() {
                alert("2")
            }
        },

    });
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<body>
    <div id="app">
        <h2>当期计数:{{counter}}</h2>
        <!-- <button v-on:click="counter++">+</button> -->
        <!-- <button v-on:click="counter--">-</button> -->
        <button @click="add">+</button>
        <button @click="sub">-</button>
    </div>
</body>
<script src="../js/vue.js"></script>
<script>
    const app = new Vue({
        el: '#app',
        data: {
            counter: 0
        },
        methods: {
            add: function () {
                this.counter++;
                console.log('add被执行');
            },
            sub: function () {
                this.counter--;
                console.log('sub被执行');
            }
        },
    })
</script>
1
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
27
28

鼠标事件

@click 单击事件

@mousedown 按下事件

@mouseup 抬起事件

@dblclick 双击事件

@mousemove 移动事件

@mouseleave 离开事件

@mouseout 移出事件

@mouseenter进入事件

@mouseover 在里面

键盘事件

@keyup 键弹起

@keydown 键按下

常用按键别名

为了在必要的情况下支持旧浏览器,Vue 提供了绝大多数常用的按键码的别名:

.enter
.tab(必须配合 @keydown 使用)
.delete (捕获“删除”和“退格”键)
.esc
.space
.up
.down
.left
.right
1
2
3
4
5
6
7
8
9
10
11
<div id="root">
    请输入:<input type="text" @keyup="showInfo">
</div>
<script>
    new Vue({
        el: '#root',
        methods: {
            showInfo(e) {
                //输入的不是回车就不输出
                //也可以在控件上输入 @keyup.enter="showInfo"
                if (e.key != "Enter") {return;} 
                console.log(e.target.value);}
    });
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14

事件修饰符

  1. prevent:阻止默认事件(常用)
  2. stop:阻止事件冒泡(常用)
  3. once:事件只触发一次(常用)
  4. capture:使用事件的捕获模式
  5. self:只有event.target是当前操作的元素才触发事件
  6. passive:事件的默认行为立即执行,无需等待事件的回调执行

==可以连续写修饰符@click.prevent.stop==

<div id="root">
    <!-- 阻止事件 本来的事件   e.preventDefault();-->
    <a href="https://www.baidu.com" @click.prevent="showInfo1">百度</a><br>
    <!-- 阻止默认行为然后阻止冒泡 -->
    <a href="https://www.baidu.com" @click.prevent.stop="showInfo1">百度</a><br>
    <!-- 组织事件冒泡          e.stopPropagation(); -->
    <div @click="showInfo1">
        <button @click.stop="showInfo2">button2</button>
    </div>
    <!-- 事件只触发一次 -->
    <button @click.once="showInfo1">一次</button>
</div>
1
2
3
4
5
6
7
8
9
10
11
12

# el与data两种写法

image-20220212170857749

<script>
    const v =  new Vue({
        // el: '#root',

        data: {
            name: "123"
        }

    });
    v.$mount('#root'); //第二种写法
</script>

<script>
    new Vue({
        el: '#root',

        /*  data: {
                 name: '123'
             }*/
        // data: function () {
        data() {
            return {
                name: '123456'
            }
        }

    });
</script>
1
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
27
28

# MVVM双向绑定

Vue的MVVM

image-20211023124846880

数据跟视图双向绑定

  1. 数据发生改变视图改变
  2. 视图响应对应的数据改变

# Vue生命周期

image-20220221160606349

每个 Vue 实例在被创建时都要经过一系列的初始化过程——例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会。

lifecycle

image-20220224164342651

//此时数据代理、监视还没生成
beforeCreate() {
    console.log('beforeCreate');

},
    //可以通过 vm 访问 data 中的数据,methods方法
    created() {
        console.log('created');

    },
        //页面显示的是未经vue编译的DOM结构  所有对DOM的操作都不奏效
        beforeMount() {
            console.log('beforeMount');

        },
            //尽可能避免对DOM进行操作
            //挂载,vue完成模板的解析并把真实DOM放入页面后(挂载完毕)调用mounted
            mounted() {
                console.log('mounted');
            },
                //此时数据是新的,页面是旧的   页面尚未和数据保持同步
                beforeUpdate() {
                    console.log('beforeUpdate');  
                },
                    //数据和页面都是新的  页面和数据保持同步
                    updated() {
                        console.log('updated');
                    },
1
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
27
28
<div id="root">
    <h1 :style="{opacity}">hello</h1>
    <button @click="stop">停止渐变</button>
</div>
<script>
    const vm = new Vue({
        el: '#root',

        data: {
            opacity: 1
        },
        methods: {
            stop(){
                this.$destroy()
            }
        },
        //页面加载完后开始变换透明度
        mounted() {
            this.timer = setInterval(() => {
                this.opacity -= 0.01;
                if (this.opacity <= 0) this.opacity = 1;
            }, 16)

        },
        //不能在方法中停止定时器,无法得知定时器何时停止
        beforeDestroy() {
            console.log('destroy');
            clearInterval(this.timer);
        },

    });
</script>
1
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
27
28
29
30
31
32

# 数据代理

什么是数据代理:通过一个对象代理对另外一个对象属性的操作 读/写

image-20220212172754083

Object.defineProperty

<script>
        let person = {
            name: '张三',
            sex: '男'
        }
		//使用 defineProperty 添加的属性默认不可枚举(遍历)
        Object.defineProperty(person, "age", {
            value: 18,
            enumerable:true //可遍历
        })
        console.log(person);
    </script>
1
2
3
4
5
6
7
8
9
10
11
12

枚举 :遍历

enumerable:true 控制属性是否可用枚举,默认false

writable:true 制属性是否可用被修改,默认false

configurable:true 控制属性是否可用被删除,默认false

get函数

let number = 18
let person = {
    name: '张三',
    sex: '男'
}
Object.defineProperty(person, "age", {
    //当有人读取person的age属性时,get函数就会被调用,且返回值就是age的值
    //get:function(){return number}
    get() {
        return number
    },
    //当有人修改person的age属性时,set函数就会被调用,且收到修改的具体值
    set(value) {
        number = value
    }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

image-20211027192059193 image-20211027193124328

let obj = {x:100}
let obj2 = {y:200}
//obj2 代理 o
Object.defineProperty(obj2,'x',{
    //读取obj的x
    get(){
        return obj.x;
    },
    //修改obj 的 x
    set(value){
        obj.x = value;
    }
})
1
2
3
4
5
6
7
8
9
10
11
12
13

vue对象data就是通过Object.defineProperty进行数据代理,含有get\set对象

image-20220213172707600

image-20211028092715954

# 计算属性

组件模板中的属性代码应该尽可能简单

模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护。例如:

<div id="example">
  {{ message.split('').reverse().join('') }}
</div>
1
2
3

在这个地方,模板不再是简单的声明式逻辑。你必须看一段时间才能意识到,这里是想要显示变量 message 的翻转字符串。当你想要在模板中的多处包含此翻转字符串时,就会更加难以处理。

所以,对于任何复杂逻辑,你都应当使用计算属性

vue提供了一个computed可选项来计算属性

image-20220214173231782

==要用的属性不存在,要通过已有的属性计算得来==

<div id="app">
    性:<input type="text" v-model="firstName"><br />
    名:<input type="text" v-model="lastName"><br />
    全名:<span>{{fullName}}</span>
</div>

<script>
    const vm=  new Vue({
        el: '#root',

        data: {
            firstName : "张",
            lastName : "三"
        },
        computed:{
            fullName:{
                //当有人读取fullName时get会被调用,且返回值就作为fullName的值
                //初次读取fullName , 所依赖的数据发生变化
                get(){
                    return this.firstName + "-" + this.lastName;
                },
                set(value){
                    const attr= value.split();
                    this.firstName = attr[0];
                    this.lastName = attr[1];
                }
            }
        }
    });
</script>
1
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
27
28
29
30

计算属性简写

computed: {
    fullName() {
        return this.firstName + "-" + this.lastName;
    }
}
1
2
3
4
5
<div id="root">
    姓:<input type="text" v-model="firstName"><br>
    名:<input type="text" v-model="lastName"><br>
    <!--插值方式-->
    <!-- 全名 <span>{{firstName}}-{{lastName}}</span> -->
    <!-- method方式  通过调用方法 返回值 -->
    全名 <span>{{fullName()}}</span>
</div>
<script>
    new Vue({
        el: '#root',

        data: {
            firstName : "张",
            lastName : "三"
        },
        methods: {
            fullName(){
                return this.firstName + "-" + this.lastName
            }
        },

    });
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 侦听属性

Watch:虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。这就是为什么 Vue 通过 watch 选项提供了一个更通用的方法,来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。

image-20220214180658801

==computed计算属性也可以被侦听==

watch: {
    isHot: {
        //初始化时让handler调用一下
        immediate: true,
        //当isHost发生改变时执行  修改后的值,修改前的值
        handler(newValue, oldValue) {
            console.log(newValue, oldValue);
        }
    }
}
//第二种写法 当不明确监听哪个属性时使用
vm.$watch('status',{
    handler(newvalue,oldvalue){
        console.log(newvalue,oldvalue)
    }
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

deep:深度侦听

computed和watch的区别

  1. computed能完成的功能,watch都可以完成。
  2. watch能完成的功能,computed不一定能完成,例如: watch可以进行异步操作。

两个重要的原则:

  • 所有被Vue管理的函数,最好写成普通函数,这样this的指向才是vm或组件实例对象。
  • 所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数等),最好写成箭头函数,这样this的指向才是vm或组件实例对象。

深度监听

image-20220214182558191

<h1>a:{{numbers.a}}</h1>
<button @click="numbers.a++">123</button>
data: {
    numbers:{
       a:1,
       b:1
    }
},
//监视多级结构的(某个)属性变化
'numbers.a':{
    handler(){
        console.log('a改变了')
    }
},

    //监视多级结构的(所有)属性变化
    numbers:{
        deep:true, //需要开启深度监视 才可以监视 numbers的所有属性是否 改变了
            handler(){
            console.log('numbers改变了')
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

监听简写

watch: {
    isHot(newValue, oldValue){
         console.log(newValue, oldValue);
    }
}

vm.$watch('status',function(newValue, oldValue){
    console.log(newValue, oldValue);
});
1
2
3
4
5
6
7
8
9

# 样式绑定

操作元素的 class 列表和内联样式是数据绑定的一个常见需求。因为它们都是 attribute,所以我们可以用 v-bind 处理它们:只需要通过表达式计算出字符串结果即可。不过,字符串拼接麻烦且易错。因此,在将 v-bind 用于 classstyle 时,Vue.js 做了专门的增强。表达式结果的类型除了字符串之外,还可以是对象或数组。

v-class绑定class样式:简写:class

image-20220215155717223

<div id="app">
    <!-- 绑定样式 绑定的样式类名不确定-->
    <div class="default" :class="mood" @click="changeMood">
        <h2>绑定样式</h2>
    </div>
    <hr>
    <!-- 对象绑定切换样式,还可以绑定一个计算属性   个数名字确定,但要决定用不用-->
    <div class="default" :class="classObject" @click="objectClass">
        <h2>对象绑定</h2>
    </div>
    <hr>
    <!-- 数组绑定切换样式,传递一个数组给:class 应用数组样式 -->
    <!-- 
          绑定的样式的个数不确定,名字不确定
-->
    <div class="default" :class="styleArr" @click="changeArr">
        <h2>数组绑定</h2>
    </div>
</div>

<script>
    //阻止vue启动时生成提示
    Vue.config.productionTip = false
    const vm = new Vue({
        el: "#app",
        data: {
            mood: "sad",
            classObject: {
                sad: true,
                happy: false
            },
            styleArr: ["style1", "style2", "style3"],
            arrCount: 0
        },
        methods: {
            changeMood() {
                const arr = ["sad", "happy", "normal"]
                const random = Math.floor(Math.random() * 3)
                this.mood = arr[random];
                console.log("mod:" + this.mod)
            },
            objectClass() {
                this.classObject.sad = !this.classObject.sad
                this.classObject.happy = !this.classObject.happy
                console.log("sad:" + this.classObject.sad + ",happy:" + this.classObject.happy);
            },
        },
</script>
1
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

# 条件渲染

image-20220215161043211

v-show

添加一个行内样式:display:none ==结构还在==

<div v-show="true">test</div>
1

image-20211112102005273

v-if

直接把dom元素删除了

image-20211112102014500

变化很高的时候经量使用v-show,不需要一直渲染元素

v-else-if

<!-- v-if直接删除dom元素 -->
<h2 v-if="num == 1">a</h2>
<h2 v-else-if="num == 2">b</h2>
<h2 v-else-if="num == 3">c</h2>
<button @click="num++;">num+1</button>
1
2
3
4
5

会一个个往下执行条件,一个满足了就不会继续了

v-else-if必须连续执行

<template> 元素上使用 v-if 条件渲染分组

因为 v-if 是一个指令,所以必须将它添加到一个元素上。但是如果想切换多个元素呢?此时可以把一个 <template> 元素当做不可见的包裹元素,并在上面使用 v-if。最终的渲染结果将不包含 <template> 元素。

template元素将不会被渲染,只能配合v-if,不能配合v-show

==template不影响结构==

<template v-if="ok">
  <h1>Title</h1>
  <p>Paragraph 1</p>
  <p>Paragraph 2</p>
</template>
1
2
3
4
5

# 列表渲染

遍历语法

<tr v-for="(item, index) in object" ::key="item.id">
1
<table border="1">
    <tr>
        <th>id</th>
        <th>姓名</th>
        <th>年龄</th>
    </tr>
	<!-- 比较多使用 -->
    <!-- 遍历数组获取的两个元素,item是元素,index是索引 -->
    <tr v-for="(item, index) in persons" ::key="item.id">
        <td>{{item.id}}</td>
        <td>{{item.name}}</td>
        <td>{{item.age}}</td>
    </tr>
</table>

<!-- 遍历的是一个对象,两个参数一个就是value,一个是key -->
<ul v-for="(value, key) in car">
    <li>{{key}}:{{value}}</li>
</ul>

<!-- 比较少使用 -->
<!-- 遍历的是一个字符串,两个参数一个是item,一个是index -->
<ul v-for="(item, index) in str">
    <li>{{item}}——{{index}}</li>
</ul>

<!-- 遍历的指定次数,两个参数一个是item,一个是index -->
<ul v-for="(item, index) in 6">
    <li>{{item}}——{{index}}</li>
</ul>

persons: [
    { id: '001', name: 'Du', age: '20' },
    { id: '002', name: '张三', age: '21' },
    { id: '003', name: '李四', age: '22' },
],
car: {
    name: '奥迪R8',
    price: '200万',
    color: '黑色'
},
str: 'hello',
1
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

可以遍历数组,对象,字符串,指定次数

# 列表过滤

watch实现

<!-- :key="person.id" 绑定主键  :key="index" 指定为遍历的索引值  (p,index)in persons -->
<input type="text" v-model="keyword">
<li v-for="person in filterPersons" :key="person.id">{{person.name}}-{{person.age}}-{{person.sex}} </li>
<script>
    new Vue({
        el: '#root',
        data: {
            keyword: '',
            persons: [
                { id: '001', name: '马冬梅', age: 12, sex: '女' },
                { id: '002', name: '周冬雨', age: 13, sex: '女' },
                { id: '003', name: '周杰伦', age: 14, sex: '男' },
                { id: '004', name: '温兆伦', age: 14, sex: '男' },
            ],
            filterPersons: []  //定义一个新的数组 显示 原数组要保存原数据
        },
        watch: {
            keyword: {
                immediate: true, //立即执行 初始化的时候显示整个数组
                handler(value) {
                    this.filterPersons = this.persons.filter((p) => {
                        return p.name.indexOf(value) != -1;
                    })
                }
            }
        }

    });
</script>
1
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
27
28
29

computed实现

<!-- :key="person.id" 绑定主键  :key="index" 指定为遍历的索引值  (p,index)in persons -->
<input type="text" v-model="keyword">
<li v-for="person in filterPersons" :key="person.id">{{person.name}}-{{person.age}}-{{person.sex}} </li>
<script> 
    new Vue({
        el: '#root',
        data: {
            keyword: '',
            persons: [
                { id: '001', name: '马冬梅', age: 12, sex: '女' },
                { id: '002', name: '周冬雨', age: 13, sex: '女' },
                { id: '003', name: '周杰伦', age: 14, sex: '男' },
                { id: '004', name: '温兆伦', age: 14, sex: '男' },
            ]
        },
        computed: {
            filterPersons() {
                // 返回 新的数组  依赖keyword 只要keyword 改变 就执行这个方法
                return this.persons.filter((p) => {
                    return p.name.indexOf(this.keyword) != -1;
                })
            }
        }
    })
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

列表排序

<ul>
    <!-- :key="person.id" 绑定主键  :key="index" 指定为遍历的索引值  (p,index)in persons -->
    <input type="text" v-model="keyword">
    <li v-for="person in filterPersons" :key="person.id">{{person.name}}-{{person.age}}-{{person.sex}} </li>
    <button @click="sorttype = 2">升序</button>
    <button @click="sorttype = 1">降序</button>
    <button @click="sorttype = 0">原顺序</button>

</ul>

<script>

    new Vue({
        el: '#root',
        data: {
            keyword: '',
            persons: [
                { id: '001', name: '马冬梅', age: 30, sex: '女' },
                { id: '002', name: '周冬雨', age: 31, sex: '女' },
                { id: '003', name: '周杰伦', age: 18, sex: '男' },
                { id: '004', name: '温兆伦', age: 15, sex: '男' },
            ],
            sorttype: 0
        },
        computed: {
            filterPersons() {
                // 返回 新的数组  依赖keyword 只要keyword 改变 就执行这个方法
                const arr = this.persons.filter((p) => {
                    return p.name.indexOf(this.keyword) != -1;
                })

                if (this.sorttype) {
                    // a-b 升序  b-a降序
                    arr.sort((a, b) => {
                        return this.sorttype == 2 ? a.age - b.age : b.age - a.age;
                    })
                }
                return arr;
            }
        }
    })
</script>
1
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

# vue监视数据原理

image-20220220151024386

# vue.set()

<script>
    new Vue({
    el: '#root',

    data: {
        student:{
            name: 'name',
            age: '12',
        }
    },
    methods: {
        addSex(){
            // 目标,key值,value值
            Vue.set(this.student,'sex','男')
        }
    },

});
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 列表key的原理

(尚硅谷vue p30)

image-20220215164919030

image-20220215165303148

==如果不添加key,vue会默认用index生成一个key==

1. 虚拟DOM中key的作用:
key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】, 
随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下:

2.对比规则:
(1).旧虚拟DOM中找到了与新虚拟DOM相同的key:
①.若虚拟DOM中内容没变, 直接使用之前的真实DOM!(复用)
②.若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM。(将旧的dom的内容替换成新的内容)

(2).旧虚拟DOM中未找到与新虚拟DOM相同的key
创建新的真实DOM,随后渲染到到页面。

3. 用index作为key可能会引发的问题:
1. 若对数据进行:逆序添加、逆序删除等破坏顺序操作:
会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。

2. 如果结构中还包含输入类的DOM:
会产生错误DOM更新 ==> 界面有问题。

4. 开发中如何选择key?:
1.最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
2.如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,
使用index作为key是没有问题的。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 收集form数据

image-20220220153706323

<span>用户名:</span>
<!--去掉首尾空格-->
<input type="text" v-model.trim="username"><br>
<span>密码</span>
<!--输入数字类型-->
<input type="password" v-model.number="password"><br>
<!--失去焦点在转数据-->
<textarea rows="10" v-model.lazy="desc"></textarea><br>

<script>
    new Vue({
    el: '#root',

    data: {
        username:'',
        password:'',
        sex:'',
        likes:[],//checkbox 要写成数组
        city:'',
        desc:''

    }

});
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 过滤器

image-20220220160731554

# 指令

<!-- v-text指令 -->
<div v-text="name"></div>data:{name:'123'};
<h1 v-once>初始值:{{n}}</h1>
<button @click="n++">动态值:{{n}}</button>
1
2
3
4

image-20220220195004631

image-20220220195952198

image-20220220200217207

image-20220220200922209

# 自定义指令

image-20220221153411337

==指令名不要使用驼峰使用 - 写==

v-big-number 获取使用引号获取 'big-numbeer'

<h1>n的值{{n}}</h1>
<h1>放大10倍的n:<span v-big="n"></span></h1>
<button @click="n++">n+1</button><br>
<input type="text" v-fbind="n">
<script>
    directives: {
        //v- 后面的名字
        focus: {
            ////指令与元素成功绑定时(一上来)
            //(dom元素<span></span>,绑定对象)   binding.value 
            bind(element, binding) {
                element.value = binding.value;
            },
                //指令所在元素被插入页面时
                inserted(element, binding) {
                    element.focus()
                },
                    //指令所在的模板被重新解析时
                    update(element, binding) {
                        element.value = binding.value;
                    }
        }
    }
    //何时被调用? 指令与元素成功绑定时(一上来)  指令所在的模板重新解析时
    //使用 v-指令名称
</script>

<input type="text" v-focus>
1
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
27
28

简写

<h1>n的值{{n}}</h1>
<h1>放大10倍的n:<span v-big="n"></span></h1>
<button @click="n++">n+1</button><br>
<script>
    new Vue({
        el: '#root',

        data: {
            n:1
        },
        directives:{
            //v后面的名字
            big(element,binding){
                element.innerText = binding.value*10;
            }
        }


    });
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

全局指令

image-20220221153026470

# 组件开发

定义: 实现应用中局部功能代码和资源的集合

# 非单文件组件

定义:一个文件包含有n个文件

image-20220224172953163

image-20220224175748736

image-20220225142952485

==组件名如果是多个单词需要用横线隔开 my-school==

<div id="root">
    <school></school>
    <hr>
    <student></student>
</div>
<script>
    const school = Vue.extend({
        template: `
<div>
<h1>{{schoolName}}</h1>   
<h1>{{schoolAddress}}</h1>   
    </div>
`
        ,
        // el: "#root",  所有的组件都要被一个vm管理,由vm决定服务于哪个容器
        data() {
            return {
                schoolName: 'fjut',
                schoolAddress: 'fj'
            };
        }
    });
    const student = Vue.extend({
        template: `
<div>
<h1>{{studentName}}</h1>   
<h1>{{age}}</h1>   
    </div>
`,
        data() {
            return {
                studentName: 'zs',
                age: 18
            }
        }
    })

    new Vue({
        el: '#root',
        //注册组件
        components: {
            school: school,
            //简写 如果 变量名一样  student:student
            student
        }

    });
</script>
1
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

# 全局注册

//创建hello组件
const hello = Vue.extend({
    template: `
<div>
<h2>你好:{{name}}</h2>    
</div>
`,
    data() {
        return {
            name: 'Du',
        }
    }
})

//全局注册组件
Vue.component('hello', hello)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 父子组件

<div id="root">
</div>
<script>


    //简写  可省略Vue.extend({})
    const student = {
        name: 'student',
        template: `
<div>
<h1>学生姓名:{{name}}</h1>
<h1>学生年龄:{{age}}</h1>
    </div>
`,
        data() {
            return {
                name: 'zs',
                age: 18
            };
        }
    }
    //为 student 的父组件需要注册student
    const school = Vue.extend({
        name: 'school',
        // 需要 加 <student></student> 标签
        template: `
<div>
<h1>学校:{{name}}</h1>
<h1>学校地址:{{address}}</h1>

<student></student>
    </div>
`,
        data() {
            return {
                name: 'fjut',
                address: 'fj'
            };
        },
        components: {
            student
        }

    })

    const hello = {
        template: `
<h1>welocme to {{msg}}</h1>
`,
        data() {
            return {
                msg: 'fjut'
            };
        }
    }
	//管理所有的父组件 不需要管理子组件
    const app = Vue.extend({
        template: `
<div>
<hello></hello>
<school></school>
    </div>
`,
        components: {
            school,
            hello
        }
    })
	
    new Vue({
        template:`<app></app>`,
        el: '#root',

        components: {
            app
        }

    });
</script>
1
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79

# VueComponent

image-20220225151352419

原型

image-20220225160142074

image-20220225161602515

  1. 一个重要的内置关系:VueComponent.prototype.__proto__ === Vue.prototype
  2. 目的:==让组件实例对象(vc)可以访问Vue原型的属性和方法==

# 单文件组件

定义:一个文件只包含1个组件

创建组件

Student.vue

<template>
<div class="demo">
    <h1>{{ name }}</h1>
    <h1>{{ age }}</h1>
    <button @click="showName">showName</button>
    </div>
</template>

<script>
    export default {
        name: "Student",
        data() {
            return {
                name: "zs",
                age: 18,
            };
        },
        methods: {
            showName() {
                alert(this.name);
            },
        },
    };
</script>

<style>
    .demo {
        background-color: orange;
    }
</style>
1
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
27
28
29
30

School.vue

<template>
<div class="demo">
    <h1>{{ name }}</h1>
    <h1>{{ address }}</h1>
    </div>
</template>

<script>
    export default {
        name: "School",
        data() {
            return {
                name: "fjut",
                address: "fj",
            };
        },
    };
</script>

<style>
    .demo {
        background-color: green;
    }
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

App.vue

<template>
<div>
    <School/>
    <Student></Student>
    </div>
</template>

<script>
    //导入两个组件并注册
    import School from "./School.vue";
    import Student from "./Student.vue";
    export default {
        name: "App",
        components: { School, Student },
    };
</script>

<style>
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

main.js 创建vue


import App from './App.vue'
new Vue({
    el: '#root',
    template:`<App></App>`,
    components: { App }
});

1
2
3
4
5
6
7
8

index.html

<div id="root"></div>
<script src="../../js/vue.js"></script>
<script src="./main.js"></script>
1
2
3

# Vue脚手架笔记

# 脚手架文件结构

├── node_modules 
├── public
│   ├── favicon.ico: 页签图标
│   └── index.html: 主页面
├── src
│   ├── assets: 存放静态资源
│   │   └── logo.png
│   │── component: 存放组件
│   │   └── HelloWorld.vue
│   │── App.vue: 汇总所有组件
│   │── main.js: 入口文件
├── .gitignore: git版本管制忽略的配置
├── babel.config.js: babel的配置文件
├── package.json: 应用包配置文件 
├── README.md: 应用描述文件
├── package-lock.json:包版本控制文件

安装

vue create hello-world
vue run server
1
2

# 关于不同版本的Vue

  1. vue.js与vue.runtime.xxx.js的区别:
    1. vue.js是完整版的Vue,包含:核心功能 + 模板解析器。
    2. vue.runtime.xxx.js是运行版的Vue,只包含:核心功能;没有模板解析器。
  2. 因为vue.runtime.xxx.js没有模板解析器,所以不能使用template这个配置项,需要使用render函数接收到的createElement函数去指定具体内容。
render: (createElement) => createElement('h1', 'hello')  //简写
 render: (h) => {
     return h('h1', 'h1')
 },
1
2
3
4

# vue.config.js配置文件

  1. 使用vue inspect > output.js可以查看到Vue脚手架的默认配置。
  2. 使用vue.config.js可以对脚手架进行个性化定制,详情见:https://cli.vuejs.org/zh
module.exports = defineConfig({
    transpileDependencies: true,
    lintOnSave: false  //关闭语法检查
})
1
2
3
4

# ref属性

  1. 被用来给元素或子组件注册引用信息(id的替代者)
  2. 应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc)
  3. 使用方式:
    1. 打标识:<h1 ref="xxx">.....</h1><School ref="xxx"></School>
    2. 获取:this.$refs.xxx

# props配置项

  1. 功能:让组件接收外部传过来的数据

  2. 传递数据:<Demo name="xxx"/>

  3. 接收数据:

    1. 第一种方式(只接收):props: ["name", "sex", "age"]

    2. 第二种方式(限制类型):props:{name:String}

    3. 第三种方式(限制类型、限制必要性、指定默认值):

    备注:props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。

    Student.vue

    <template>
    <div class="school">
        <h1>{{ name }}</h1>
        <h1>{{ sex }}</h1>
        <h1>{{ age+1 }}</h1> 年龄加一
        </div>
    </template>
    
    <script>
        export default {
            name: "Student",
            props: ["name", "sex", "age"], //声明props 
            //接收的同时进行类型限制
            props:{
                name:String,
                sex:String,
                age:Number
            },
            //进行类型限制,默认值指定,必要性的限制
            props: {
                name: {
                    type: String,
                    required: true, //name是必要的
                },
                sex: {
                    type: String,
                    default: "男", //默认为男
                },
                age: {
                    type: Number,
                    required: true,
                },
            },
            
        };
    </script>
    
    1
    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
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36

    App.vue

    //使用数字需要绑定这个元素才可转换成int类型 :age="18"
    <template>
    <student  name="zs" sex="" :age="18"/>
    </template>
    
    1
    2
    3
    4

# 修改传入的数据

<script>
    data() {
        return {
            myAge: this.age,  //将props传过来的age赋给myage
        };
    },
        methods: {
            upd() {
                console.log(this.myAge);
                this.myAge++
            },
        }
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13

# mixin(混入)

  1. 功能:可以把多个组件共用的配置提取成一个混入对象

局部混合

Student.vue

<script>
    import {mixin} from '../mixin.js'  //导入mixin的js
    export default {
        data() {
            return {
                name: 'fjut',
                address: 'fj'
            }
        },
        mixins:[mixin] //局部调用
    }
</script>
1
2
3
4
5
6
7
8
9
10
11
12

mixin.js

export const mixin = {
    //生命周期钩子 Student.vue 执行mixin也会执行(mixin先)
    methods: {
        upd() {
            alert(this.name);
        },
    },
}
1
2
3
4
5
6
7
8

全局混合

main.js

// import {mixin,mixin2} from './mixin'
import {mixin} from './mixin' 

Vue.mixin(mixin) //全局注册
Vue.mixin(mixin2)
1
2
3
4
5

# 插件

  1. 功能:用于增强Vue

  2. 本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据。

  3. 定义插件:

    对象.install = function (Vue, options) {
        // 1. 添加全局过滤器
        Vue.filter(....)
    
        // 2. 添加全局指令
        Vue.directive(....)
    
        // 3. 配置全局混入(合)
        Vue.mixin(....)
    
        // 4. 添加实例方法
        Vue.prototype.$myMethod = function () {...}
        Vue.prototype.$myProperty = xxxx
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
  4. 使用插件:Vue.use()

# scoped样式

  1. 作用:让样式在局部生效,防止冲突。
  2. 写法:<style scoped>

# 总结TodoList案例

  1. 组件化编码流程:

    ​ (1).拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突。

    ​ (2).实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:

    ​ 1).一个组件在用:放在组件自身即可。

    ​ 2). 一些组件在用:放在他们共同的父组件上(状态提升)。

    ​ (3).实现交互:从绑定事件开始。

  2. props适用于:

    ​ (1).父组件 ==> 子组件 通信

    ​ (2).子组件 ==> 父组件 通信(要求父先给子一个函数)

  3. ==使用v-model时要切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改的!props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做。==

image-20220227170044027

image-20220227165913850

app.vue

<template>
<div id="root">
    <div class="todo-container">
        <div class="todo-wrap">
            <MyHeader :receive="receive" />
            <MyList
                    :todos="todos"
                    :checkToDo="checkToDo"
                    :deleteToDo="deleteToDo"
                    />
            <MyFooter :todos="todos" :checkAllToDo="checkAllToDo" :clearAllToDo="clearAllToDo"/>
    </div>
    </div>
    </div>
</template>

<script>
    import MyHeader from "./components/MyHeader.vue";
    import MyFooter from "./components/MyFooter.vue";
    import MyList from "./components/MyList.vue";
    export default {
        name: "App",
        components: { MyHeader, MyFooter, MyList },
        //将 mylist 的数据放在 app myheader 和 mylist 都能调用
        data() {
            return {
                todos: [
                    { id: "001", title: "吃饭", done: true },
                    { id: "002", title: "喝酒", done: false },
                    { id: "003", title: "睡觉", done: true },
                ],
            };
        },
        methods: {
            //给 MyHeader 一个函数 让他把创建的对象传进来
            receive(todo) {
                this.todos.unshift(todo);
            },
            //勾选或者取消勾选一个todo
            checkToDo(id) {
                this.todos.forEach((todo) => {
                    if (todo.id == id) todo.done = !todo.done;
                });
            },
            deleteToDo(id) {
                this.todos = this.todos.filter((todo) => {
                    return todo.id !== id;
                });
            },
            //勾选全部
            checkAllToDo(done) {
                this.todos.forEach((todo) => {
                    todo.done = done;
                });
            },
            //清除已完成
            clearAllToDo() {
                this.todos = this.todos.filter((todo) => {
                    return !todo.done;
                });
            },
        },
    };
</script>
1
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

MyHeader.vue

<template>
<div class="todo-header">
    <input
           type="text"
           placeholder="请输入你的任务名称,按回车键确认"
           @keyup.enter="add"
           />
    </div>
</template>

<script>
    //导入nanoid 的包
    import { nanoid } from "nanoid";

    export default {
        name: "MyHeader",
        methods: {
            // 第一种: 获取用户输入
            //   add(event){
            //       console.log(event.target.value);
            //   }
            //第二种  v-model="title"  data: title:''
            // add(){
            //     console.log(this.title);
            // }
            //添加一个对象
            add(e) {
                const todo = { id: nanoid(), title: e.target.value, done: false };
                //通知app组件添加一个对象
                this.receive(todo);
            },
        },
        props: ["receive"],
    };
</script>
1
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
27
28
29
30
31
32
33
34
35

MyList.vue

<template>
<div class="todo-header">
    <input
           type="text"
           placeholder="请输入你的任务名称,按回车键确认"
           @keyup.enter="add"
           />
    </div>
</template>

<script>
    //导入nanoid 的包
    import { nanoid } from "nanoid";

    export default {
        name: "MyHeader",
        methods: {
            // 第一种: 获取用户输入
            //   add(event){
            //       console.log(event.target.value);
            //   }
            //第二种  v-model="title"  data: title:''
            // add(){
            //     console.log(this.title);
            // }
            //添加一个对象
            add(e) {
                const todo = { id: nanoid(), title: e.target.value, done: false };
                //通知app组件添加一个对象
                this.receive(todo);
            },
        },
        props: ["receive"],
    };
</script>
1
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
27
28
29
30
31
32
33
34
35

MyItem.vue

<template>
<div class="todo-header">
    <input
           type="text"
           placeholder="请输入你的任务名称,按回车键确认"
           @keyup.enter="add"
           />
    </div>
</template>

<script>
    //导入nanoid 的包
    import { nanoid } from "nanoid";

    export default {
        name: "MyHeader",
        methods: {
            // 第一种: 获取用户输入
            //   add(event){
            //       console.log(event.target.value);
            //   }
            //第二种  v-model="title"  data: title:''
            // add(){
            //     console.log(this.title);
            // }
            //添加一个对象
            add(e) {
                const todo = { id: nanoid(), title: e.target.value, done: false };
                //通知app组件添加一个对象
                this.receive(todo);
            },
        },
        props: ["receive"],
    };
</script>
1
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
27
28
29
30
31
32
33
34
35

MyFooter.vue

<template>
<div class="todo-footer" v-if="todos.length">
    <label>
        <!-- <input type="checkbox" :checked="isAll" @click="checkAll" /> -->
        <input type="checkbox" v-model="isAll" />
    </label>
    <span>
        <span>已完成{{ checkTotal }}</span> / 全部{{ todos.length }}
    </span>
    <button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
    </div>
</template>

<script>
    export default {
        name: "MyFooter",
        props: ["todos", "checkAllToDo", "clearAllToDo"],
        computed: {
            checkTotal() {
                //统计几个完成了
                return this.todos.reduce((pre, current) => {
                    //pre默认0  如果完成了一个 pre 成1
                    return pre + (current.done ? 1 : 0);
                }, 0);
            },
            //勾选全部
            isAll: {
                get() {
                    return this.todos.length == this.checkTotal && this.checkTotal > 0;
                },
                set(value) {
                    this.checkAllToDo(value);
                },
            },
        },
        methods: {
            //清除已完成
            clearAll() {
                this.clearAllToDo();
            },
        },
    };
</script>
1
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

增加本地存储功能

app.vue

<script>
    data() {
        return {
            todos: JSON.parse(localStorage.getItem("todos")) || [], //如果todos为空会返回null 给todos赋值 【】
        };
    },
        watch: {
            todos: {
                deep: true, //深度监视 如果对象内的数据发生变化
                    handler(value) {
                    localStorage.setItem("todos", JSON.stringify(value));
                },
            },
        },
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

编辑功能

app.vue

<script>
    methods:{
        //更新 title
        updateToDo(id, title) {
            this.todos.forEach((todo) => {
                if (todo.id == id) todo.title = title;
            });
        },
    }
    mounted() {
        this.$bus.$on("updateToDo", this.updateToDo);
    },
        beforeDestroy() {
            this.$bus.$off("updateToDo");
        },
            //监视 todos 的变化并把它存进b
            watch: {
                todos: {
                    deep: true,
                        handler(value) {
                        localStorage.setItem("todos", JSON.stringify(value));
                    },
                },
            },

</script>
1
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

ListItem.vue

<template>
<div>
    <li>
        <label>
            <input type="checkbox" :checked="t.done" @click="handleCheck(t.id)" />
            <input
                   type="text"
                   :value="t.title"
                   v-show="t.isEdit"
                   @blur="handleBlur(t, $event)"
                   />
            <span v-show="!t.isEdit">{{ t.title }}</span>
    </label>
        <button class="btn btn-danger" @click="deleteById(t.id)">删除</button>
        <button  v-show="!t.isEdit" @click="handleEdit(t)">编辑</button>
    </li>
    </div>
</template>
<script>
    //编辑
    handleEdit(t) {
        <!--这个对象是否包含这个属性-->
        if (t.hasOwnProperty("isEdit")) {
            console.log('有isedit');
            t.isEdit = true;
        } else {
            console.log('没有isedit');
            this.$set(t, "isEdit", true);
        }
    },
        //光标移走
        handleBlur(t, e) {
            t.isEdit = false;
            //通知更新  id 修改后的值
            // console.log("updateToDo", t.id, e.target.value);
            this.$bus.$emit("updateToDo", t.id, e.target.value);
        },
</script>
1
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
27
28
29
30
31
32
33
34
35
36
37
38

# webStorage

  1. 存储内容大小一般支持5MB左右(不同浏览器可能还不一样)

  2. 浏览器端通过 Window.sessionStorage 和 Window.localStorage 属性来实现本地存储机制。

  3. 相关API:

    1. xxxxxStorage.setItem('key', 'value'); 该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值。

    2. xxxxxStorage.getItem('person');

      ​ 该方法接受一个键名作为参数,返回键名对应的值。

    3. xxxxxStorage.removeItem('key');

      ​ 该方法接受一个键名作为参数,并把该键名从存储中删除。

    4. xxxxxStorage.clear()

      ​ 该方法会清空存储中的所有数据。

  4. 备注:

    1. SessionStorage存储的内容会随着浏览器窗口关闭而消失。
    2. LocalStorage存储的内容,需要手动清除才会消失。
    3. xxxxxStorage.getItem(xxx)如果xxx对应的value获取不到,那么getItem的返回值是null。
    4. JSON.parse(null)的结果依然是null。
    let p = { name: 'zs', age: 12 }
    function saveData() {
        localStorage.setItem('msg', 'hello');
        localStorage.setItem('msg2', '123');
        const result = JSON.stringify(p);
        localStorage.setItem('person', result)
    }
    function readData() {
        console.log(localStorage.getItem('msg'));
        const p = localStorage.getItem('person');
        console.log(JSON.parse(p));
    }
    function deleteData() {
        localStorage.removeItem('msg');
    }
    function clearData() {
        localStorage.clear();
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18

# 组件的自定义事件

  1. 一种组件间通信的方式,适用于:子组件 ===> 父组件

  2. 使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)。

  3. 绑定自定义事件:

    1. 第一种方式,在父组件中:<Demo @atguigu="test"/><Demo v-on:atguigu="test"/>

    2. 第二种方式,在父组件中:

      <Demo ref="demo"/>
      ......
      mounted(){
         this.$refs.xxx.$on('atguigu',this.test)
      }
      
      1
      2
      3
      4
      5
    3. 若想让自定义事件只能触发一次,可以使用once修饰符,或$once方法。

  4. 触发自定义事件:this.$emit('atguigu',数据)

  5. 解绑自定义事件this.$off('atguigu')

    unbind() {
    this.$off("getStudentName"); //解绑单个自定义组件
    this.$off(["getStudentName", "xxx"]);//解绑多个组	件
    	this.$off();//解绑全部
    },
    
    1
    2
    3
    4
    5
  6. 组件上也可以绑定原生DOM事件,需要使用native修饰符。

    <Student @click.native="show"
    
    1
  7. 注意:通过this.$refs.xxx.$on('atguigu',回调)绑定自定义事件时,回调要么配置在methods中要么用箭头函数,否则this指向会出问题!

app.vue

<template>
<Student v-on:getStudentName="getStudentName" />
</template>
<script>
    methods: {
            getStudentName(name) {
                console.log("学生名:", name);
            },
</script>
1
2
3
4
5
6
7
8
9

student.vue

methods:{
    sendStudentName(){
        //自定义事件名,要传的参数
        this.$emit('getStudentName',this.name)
    }
}
1
2
3
4
5
6

# 全局事件总线(GlobalEventBus)

image-20220228163720081

  1. 一种组件间通信的方式,适用于任意组件间通信

  2. 安装全局事件总线:

    main.js,初始化Vue的时候配置

    new Vue({
    	......
    	beforeCreate() {
    		Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm
    	},
        ......
    }) 
    
    1
    2
    3
    4
    5
    6
    7
  3. 使用事件总线:

    1. 接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。

      methods: {
          getStudentName(name) {
              console.log("学生姓名:", name);
          },
      },
      mounted() {
              //接收数据
              //第一种
              // this.$bus.$on("sendName", this.getStudentName);
              //第二种
              this.$bus.$on("sendName", (name) => {
                  console.log("学生姓名", name);
              });
          },
      beforeDestroy() {
                  //解绑
                  this.$bus.$off("sendName");
              },
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
    2. 提供数据:this.$bus.$emit('xxxx',数据)

      methods: {
          //提供数据
          sendNameToSchool() {
              this.$bus.$emit("sendName", this.name);
          },
      },
      
      1
      2
      3
      4
      5
      6
  4. 最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件。

todoList 全局总线

MyItem.vue 提供者

methods: {
    handleCheck(id) {
        // this.checkToDo(id);
        this.$bus.$emit("checkToDo", id);
    },
        deleteById(id) {
            if (confirm("确定删除")) {
                //通知 app 删除
                // this.deleteToDo(id);
                this.$bus.$emit("deleteToDo", id);
            }
        },
},
1
2
3
4
5
6
7
8
9
10
11
12
13

app.vue 接收者

mounted() {
    this.$bus.$on("checkToDo", this.checkToDo);
    this.$bus.$on("deleteToDo", this.deleteToDo);

},
    beforeDestroy() {
        this.$bus.$off("checkToDo");
        this.$bus.$off("deleteToDo");

    },
1
2
3
4
5
6
7
8
9
10

# 消息订阅与发布(pubsub)

  1. 一种组件间通信的方式,适用于任意组件间通信

  2. 使用步骤:

    1. 安装pubsub:npm i pubsub-js

    2. 引入: import pubsub from 'pubsub-js'

    3. 接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。

      methods: {
          getStudentName(_,name) { // _ 占位
              console.log("学生姓名:", name);
          },
      },
          mounted() {
              //订阅消息   (消息名,消息的内容)
              /* this.pubId = pubsub.subscribe("getMsg", (msgName, data) => {
            console.log("学生发布的消息:", data);
          }); */
              this.pubId = pubsub.subscribe("getMsg", this.getStudentName);
          },
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
    4. 提供数据:pubsub.publish('xxx',数据)

      methods: {
          //提供数据
          sendNameToSchool() {
              //发布消息
              pubsub.publish('getMsg',this.name)
          },
      },
      
      1
      2
      3
      4
      5
      6
      7
    5. 最好在beforeDestroy钩子中,用PubSub.unsubscribe(pid)取消订阅。

# nextTick

  1. 语法:this.$nextTick(回调函数)
  2. 作用:在下一次 DOM 更新结束后执行其指定的回调。
  3. 什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行。
//编辑
handleEdit(t) {
    if (t.hasOwnProperty("isEdit")) {
        console.log("有isedit");
        t.isEdit = true;
    } else {
        console.log("没有isedit");
        this.$set(t, "isEdit", true);
    }
    //点击编辑之后获取焦点
	//等DOMg
    this.$nextTick(function () {
        this.$refs.inputTitle.focus();  //等input更新完之后走该行
    });
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# Vue封装的过度与动画

  1. 作用:在插入、更新或移除 DOM元素时,在合适的时候给元素添加样式类名。

  2. 图示:image-20220301175721274

  3. 写法:

    1. 准备好样式:

      • 元素进入的样式:
        1. v-enter:进入的起点
        2. v-enter-active:进入过程中
        3. v-enter-to:进入的终点
      • 元素离开的样式:
        1. v-leave:离开的起点
        2. v-leave-active:离开过程中
        3. v-leave-to:离开的终点
    2. 使用<transition>包裹要过度的元素,并配置name属性:

      <transition name="hello">
      	<h1 v-show="isShow">你好啊!</h1>
      </transition>
      
      1
      2
      3
    3. 备注:若有多个元素需要过度,则需要使用:<transition-group>,且每个元素都要指定key值。

      <transition-group name="MyT2" appear>
          <h1 v-show="isShow" key="1">hello</h1>
          <h1 v-show="isShow" key="2">hello2</h1>
      </transition-group>
      
      1
      2
      3
      4
<template>
<!-- 规定transition名 写enter和leave动画class时候使用   :appear="true"(appear)  一上来展示enter动画-->
<transition name="MyT" appear>
    <h1 v-show="isShow" class="go">hello</h1>
    </transition>
</template>
<style>
    /* vue规定进入的动画叫 v-enter-active  如果transition写了name 要 xxx-enter-active */
    .MyT-enter-active {
        animation: MyAn 1s linear;
    }
    /* 离开的动画叫 v-leave-active */
    .MyT-leave-active {
        animation: MyAn 1s linear reverse;
    }
    @keyframes MyAn {
        from {
            transform: translateX(-100px);
        }
        to {
            transform: translateX(0px);
        }
    }
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

过渡

/* vue规定进入的动画叫 v-enter-active */
/* 进入的起点 */
.MyT2-enter {
    transform: translateX(-100%);
}
/* 进入的终点 */
.MyT2-enter-to {
    transform: translateX(0);
}
/* 过渡时执行的 */
.MyT2-enter-active {
    transition: 1s linear;
}
/* 离开的动画叫 v-leave-active */
.MyT2-leave-active {
    transition: 1s linear;
}

/* 离开的起点 */
.MyT2-leave {
    transform: translateX(0);
}
/* 离开的终点 */
.MyT2-leave-to {
    transform: translateX(-100%);
}
1
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

第三方动画库

安装

npm install animate.css
1

导包

import "animate.css";

<!--要配置name属性和想要的动画效果-->
<transition-group 
                  appear 
                  name="animate__animated animate__bounce" 
                  enter-active-class="animate__wobble"
                  leave-active-class="animate__fadeOutBottomLeft">
    <h1 v-show="isShow" key="1">hello</h1>
    <h1 v-show="isShow" key="2">hello2</h1>
</transition-group>
1
2
3
4
5
6
7
8
9

# vue脚手架配置代理

跨域: 协议名、主机名、端口号有一个不一致就是跨域

image-20220301202615341

安装axios npm i axios

# 方法一

​ 在vue.config.js中添加如下配置:

devServer:{
    //开启代理服务器解决跨域
    proxy:"http://localhost:5000"
}
1
2
3
4

说明:

  1. 优点:配置简单,请求资源时直接发给前端(8080)即可。
  2. 缺点:不能配置多个代理,不能灵活的控制请求是否走代理。
  3. 工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器 (优先匹配前端资源)==public文件夹下如果存在请求的资源则不会走代理服务器==
import axios from "axios";
export default {
    name: "App",
    methods: {
        getStudent() {
            //需要写本机的路径和端口
            axios.get("http://localhost:8080/students").then(
                (response) => {
                    console.log("请求成功了", response.data);
                },
                (error) => {
                    console.log('请求失败',error.message);
                }
            );
        },
    },
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 方法二

​ 编写vue.config.js配置具体代理规则:

module.exports = {
	devServer: {
      proxy: {
      '/api1': {// 匹配所有以 '/api1'开头的请求路径  
        target: 'http://localhost:5000',// 代理目标的基础路径
        changeOrigin: true,
        pathRewrite: {'^/api1': ''} //所有有api1的路径都替换成 ‘’  
      }, //localhost:5000/api1/students  换成localhost:5000/students
          
      '/api2': {// 匹配所有以 '/api2'开头的请求路径
        target: 'http://localhost:5001',// 代理目标的基础路径
        changeOrigin: true,
        pathRewrite: {'^/api2': ''}
      }
    }
  }
}
/*
   changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
   changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:8080
   changeOrigin默认值为true
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//请求需要加上q
axios.get("http://localhost:8080/hello1/students").then(
    (response) => {
        console.log("请求成功了", response.data);
    },
    (error) => {
        console.log('请求失败',error.message);
    }
);
1
2
3
4
5
6
7
8
9

说明:

  1. 优点:可以配置多个代理,且可以灵活的控制请求是否走代理。
  2. 缺点:配置略微繁琐,请求资源时必须加前缀。

# Github搜索案例

image-20220302170639886

public/css/引入bootstrap.css文件

index引入

<link rel="stylesheet" href="<%= BASE_URL %>css/bootstrap.css">

github api

https://api.github.com/search/users?q=xx
1

search.vue

<template>
<div>
    <input
           type="text"
           placeholder="enter the name you search"
           v-model="keyword"
           />&nbsp;<button @click="search">Search</button>
    </div>
</template>
<script>
    import axios from "axios";
    export default {
        name: "Search",
        data() {
            return {
                keyword: "",
            };
        },
        methods: {
            search() {
                axios.get(`https://api.github.com/search/users?q=${this.keyword}`).then(
                    (response) => {
                        console.log("请求成功");
                        this.$bus.$emit("getUsers", response.data.items);
                    },
                    (error) => {
                        console.log("请求失败", error.message);
                    }
                );
            },
        },
    };
</script>
1
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
27
28
29
30
31
32
33

list.vue

<template>
<div class="row">
    <div class="card" v-for="(user,index) in users" :key="index"> 
        <a :href="user.html_url" target="_blank">
            <img :src="user.avatar_url" style="width: 100px" />
    </a>
        <p class="card-text">{{user.login}}</p>
    </div>
    </div>
</template>
<script>
    export default {
        name: "List",
        data() {
            return {
                users: [],
            };
        },
        mounted() {
            this.$bus.$on("getUsers", (users) => {
                console.log(users);
                this.users = users;
            });
        },
    };
</script>
1
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

# 插槽

  1. 作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式,适用于 父组件 ===> 子组件

  2. 分类:默认插槽、具名插槽、作用域插槽

  3. 使用方式:

    1. 默认插槽:

      父组件中:
              <Category>
                 <div>html结构1</div>
              </Category>
      子组件中:
              <template>
                  <div>
                     <!-- 定义插槽 -->
                     <slot>插槽默认内容...</slot>
                  </div>
              </template>
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      <template>
      <Category title="美食">
          <img src="favicon.ico" alt="" />
          </Category>
      <Category title="美食">
          <ul>
              <li v-for="(item, index) in games" :key="index">{{ item }}</li>
          </ul>
          </Category>
      <Category>
          <video controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video>
          </Category>
      </template>
      <script>
          import Category from "./components/Category.vue";
          export default {
              name: "App",
              data() {
                  return {
                      films: ["f1", "f2", "f3"],
                      games: ["g1", "g2", "g3"],
                      foods: ["f1", "f2", "f3"],
                  };
              },
              components: { Category },
          };
      </script>
      
      1
      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
      27

      子组件 Category.vue

      <template>
        <div class="category">
          <h3>{{title}}分类</h3>
            <!--定义插槽,等待父组件进行填充-->
          <slot>没有传具体结构时这句话出现</slot>
        </div>
      </template>
      
      1
      2
      3
      4
      5
      6
      7
    2. 具名插槽:

      父组件中:
              <Category>
                  <template slot="center">
                    <div>html结构1</div>
                  </template>
      			<!--v-slot 只能在有template的标签上使用-->
                  <template v-slot:footer>
                     <div>html结构2</div>
                  </template>
              </Category>
      子组件中:
              <template>
                  <div>
                     <!-- 定义插槽 -->
                     <slot name="center">插槽默认内容...</slot>
                     <slot name="footer">插槽默认内容...</slot>
                  </div>
              </template>
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18

      app.vue

      <template>
      <div class="container">
          <Category title="美食">
              <img slot="center" src="favicon.ico" alt="" />
          </Category>
          <Category title="美食">
              <ul slot="center">
                  <li v-for="(item, index) in games" :key="index">{{ item }}</li>
          </ul>
              <!--使用template包裹-->
              <template v-slot:footer>
                  <div class="foot">
                      <a href="">xxx</a>
                      <a href="">xxx</a>
                      <a href="">xxx</a>
          </div>
                  <h4>一段h4</h4>
      </template>
      </Category>
      <Category>
          <video controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video>
      </Category>
      </div></template>
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23

      category.vue

      <template>
      <div class="category">
          <h3>{{ title }}分类</h3>
          <!--为这个插槽命名-->
          <slot name="center"></slot>
          <slot name="footer"></slot>
          </div>
      </template>
      
      1
      2
      3
      4
      5
      6
      7
      8

    ​ 3. 作用域插槽:

    1. 理解:数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。(games数据在Category组件中,但使用数据所遍历出来的结构由App组件决定)

    2. 具体编码:

      父组件中:
      		<Category>
                  <!--需要使用template接收-->
      			<template scope="scopeData">
      				<!-- 生成的是ul列表 -->
      				<ul>
      					<li v-for="g in scopeData.games" :key="g">{{g}}</li>
      				</ul>
      			</template>
      		</Category>
      
      		<Category>
      			<template slot-scope="scopeData">
      				<!-- 生成的是h4标题 -->
      				<h4 v-for="g in scopeData.games" :key="g">{{g}}</h4>
      			</template>
      		</Category>
      子组件中:
              <template>
                  <div>
                      <!--将数据传回父组件-->
                      <slot :games="games"></slot>
                  </div>
              </template>
      		
              <script>
                  export default {
                      name:'Category',
                      props:['title'],
                      //数据在子组件自身
                      data() {
                          return {
                              games:['红色警戒','穿越火线','劲舞团','超级玛丽']
                          }
                      },
                  }
              </script>
      
      1
      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
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37

# Vuex

# 1.概念

​ 在Vue中实现集中式状态(数据)管理的一个Vue插件,对vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。

# 2.何时使用?

​ 多个组件需要共享数据时

image-20220303202207608

# 3.搭建vuex环境

vue2 适配 vuex3 vue3 适配vuex4

安装vuex npm i vuex@3

main.js引入

//引入store
import store from './store/index.js'
1
2
  1. 创建文件:src/store/index.js

    import Vue from 'vue'
    //引入Vuex
    import Vuex from 'vuex'  //vue 默认先加载import语句所以在main.js写会找不到vuex
    Vue.use(Vuex)
    
    //准备actions对象——响应组件中用户的动作
    const actions = {}
    //准备mutations对象——操作、修改 state(数据)
    const mutations = {}
    //准备state对象——用于存储数据
    const state = {}
    
    //创建并暴露store
    export default new Vuex.Store({
        actions,
        mutations,
        state
    })
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
  2. main.js中创建vm时传入store配置项

    ......
    //引入store
    import store from './store'
    ......
    
    //创建vm
    new Vue({
    	el:'#app',
    	render: h => h(App),
    	store
    })
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

# 4.基本使用

  1. 初始化数据、配置actions、配置mutations,操作文件store.js

    count.vue 调用vuex

    methods: {
        increament() {
            //   this.$store.dispatch("add", this.n);
            //如果没有其他操作可直接调用commit
            this.$store.commit("ADD", this.n);
        },
            decreament() {
                this.$store.dispatch("decreament", this.n);
            },
                //奇数加
                increamentOdd() {
                    this.$store.dispatch("increamentOdd", this.n);
                },
                    increamentWait() {
                        this.$store.dispatch("increamentWait", this.n);
                    },
    },
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17

    index.js

    //引入Vue核心库
    import Vue from 'vue'
    //引入Vuex
    import Vuex from 'vuex'
    //引用Vuex
    Vue.use(Vuex)
    
    const actions = {
        //this.$store.dispatch("add", this.n);
        add(context, value) {
            console.log('action add');
            context.commit('ADD', value);
        },
        decreament(context, value) {
            console.log('action decreament');
            context.commit('DECREAMENT', value);
        },
        increamentOdd(context, value) {
            console.log('action odd');
            //求奇数
            if (context.state.sum % 2) {
                context.commit('ADD', value);
            }
        },
        increamentWait(context, value) {
            setTimeout(() => {
                context.commit('ADD', value);
            }, 500);
        }
    }
    
    const mutations = {
        //we
        ADD(state, value) {
            console.log('Mutation add');
            state.sum += value;
        },
        DECREAMENT(state, value) {
            state.sum -= value;
        }
    }
    
    //初始化数据
    const state = {
        sum:0
    }
    
    //创建并暴露store
    export default new Vuex.Store({
        actions,
        mutations,
        state,
    })
    
    1
    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
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
  2. 组件中读取vuex中的数据:$store.state.sum

  3. 组件中修改vuex中的数据:$store.dispatch('action中的方法名',数据)$store.commit('mutations中的方法名',数据)

    备注:若没有网络请求或其他业务逻辑,组件中也可以越过actions,即不写dispatch,直接编写commit

# 5.getters的使用

  1. 概念:当state中的数据需要经过加工后再使用时,可以使用getters加工。

  2. store.js中追加getters配置

    ......
    
    const getters = {
    	bigSum(state){
    		return state.sum * 10
    	}
    }
    
    //创建并暴露store
    export default new Vuex.Store({
    	......
    	getters
    })
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
  3. 组件中读取数据:$store.getters.bigSum

# 6.四个map方法的使用

引入 import { mapState, mapGetters, mapMutations, mapActions } from "vuex";

  1. mapState方法:用于帮助我们映射state中的数据为计算属性

    computed: {
        //借助mapState生成计算属性:sum、school、subject(对象写法)  
         ...mapState({sum:'sum',school:'school',subject:'subject'}),
        // ...  在对象不能直接写对象 所以加 ...mapState     
        //借助mapState生成计算属性:sum、school、subject(数组写法)
        ...mapState(['sum','school','subject']),
    },
        
        <h1>当前和为:{{ sum }}</h1>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
  2. mapGetters方法:用于帮助我们映射getters中的数据为计算属性

    computed: {
        //借助mapGetters生成计算属性:bigSum(对象写法)
        ...mapGetters({bigSum:'bigSum'}),
    
        //借助mapGetters生成计算属性:bigSum(数组写法)
        ...mapGetters(['bigSum'])
    },
    
    1
    2
    3
    4
    5
    6
    7
  3. mapActions方法:用于帮助我们生成与actions对话的方法,即:包含$store.dispatch(xxx)的函数

    methods:{
        //靠mapActions生成:incrementOdd、incrementWait(对象形式)
        ...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
    
        //靠mapActions生成:incrementOdd、incrementWait(数组形式)
        ...mapActions(['jiaOdd','jiaWait'])
    }
    
    1
    2
    3
    4
    5
    6
    7
  4. mapMutations方法:用于帮助我们生成与mutations对话的方法,即:包含$store.commit(xxx)的函数

    methods:{
    //靠mapActions生成:increment、decrement(对象形式) mapMutations(方法名:commit('xxx'))  
        ...mapMutations({increment:'JIA',decrement:'JIAN'}),
        
        //靠mapMutations生成:JIA、JIAN(对象形式)  两个名字要一样
        ...mapMutations(['JIA','JIAN']),
    }
        
        decreament() {
            this.$store.dispatch("decreament", this.n);
        },   
            //需要传参数
          <button @click="increament(n)">+</button>    
         
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

备注:mapActions与mapMutations使用时,若需要传递参数需要:在模板中绑定事件时传递好参数,否则参数是事件对象。

共享数据 : ...mapState(["personList","sum"]), 将想要的属性加进mapState即可使用

# 7.模块化+命名空间

  1. 目的:让代码更好维护,让多种数据分类更加明确。

  2. 修改store.js

    const countAbout = {
      namespaced:true,//开启命名空间
      state:{x:1},
      mutations: { ... },
      actions: { ... },
      getters: {
        bigSum(state){
           return state.sum * 10
        }
      }
    }
    
    const personAbout = {
      namespaced:true,//开启命名空间
      state:{ ... },
      mutations: { ... },
      actions: { ... }
    }
    
    const store = new Vuex.Store({
      modules: {
        countAbout,
        personAbout
      }
    })
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
  3. 开启命名空间后,组件中读取state数据:

    //方式一:自己直接读取
    this.$store.state.personAbout.list
    //方式二:借助mapState读取: 从 countAbout 拿出这三个属性
    ...mapState('countAbout',['sum','school','subject']),
    
    1
    2
    3
    4
  4. 开启命名空间后,组件中读取getters数据:

    //方式一:自己直接读取
    this.$store.getters['personAbout/firstPersonName']
    //方式二:借助mapGetters读取:
    ...mapGetters('countAbout',['bigSum'])
    
    1
    2
    3
    4
  5. 开启命名空间后,组件中调用dispatch

    //方式一:自己直接dispatch
    this.$store.dispatch('personAbout/addPersonWang',person)
    //方式二:借助mapActions:
    ...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
    
    1
    2
    3
    4
  6. 开启命名空间后,组件中调用commit

    //方式一:自己直接commit 
    this.$store.commit('personAbout/ADD_PERSON',person)
    //方式二:借助mapMutations:
    ...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),
    
    1
    2
    3
    4

    添加网络action

    person.vue

    addPerson() {
        this.$store.dispatch("PersonAbout/addRPerson");
        //   mapActions('PersonAbout',['addRPerson'])
    },
    
    1
    2
    3
    4

    config.js

    actions: {
        addRPerson(context) {
            axios.get("https://api.uixsj.cn/hitokoto/get?type=social").then(
                response => {
                    console.log(response.data);
    
                    context.commit('ADD_PERSON', {id:nanoid(),name:response.data,age:19})
                },
                error => { alert(error.message) }
            )
    
    
        }
    },
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

# 路由

  1. 理解: 一个路由(route)就是一组映射关系(key - value),多个路由需要路由器(router)进行管理。
  2. 前端路由:key是路径,value是组件。

# 1.基本使用

vue2 vue-router3
vue3 vue-router4
  1. 安装vue-router,命令:npm i vue-router@3 ==在vue2中vuerouter只能装3否则报错==

  2. 应用插件: 编写main.js

    import VueRouter from 'vue-router'
    Vue.use(VueRouter)
    //导入自己编写的 route 文件
    import router from './route/index.js'
    new Vue({
        el: '#app',
        render: h => h(App),
        router: router
    }
    )
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
  3. 编写router配置项: 先创建 router/index.js

    //引入VueRouter
    import VueRouter from 'vue-router'
    //引入Luyou 组件
    import About from '../components/About'
    import Home from '../components/Home'
    
    //创建router实例对象,去管理一组一组的路由规则
    const router = new VueRouter({
    	routes:[
    		{
    			path:'/about',
    			component:About
    		},
    		{
    			path:'/home',
    			component:Home
    		}
    	]
    })
    
    //暴露router
    export default router
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
  4. 实现切换(active-class可配置高亮样式)

    <router-link active-class="active" to="/about">About</router-link>
    
    1
  5. 指定展示位置

    <router-view></router-view>
    
    1

# 2.几个注意点

  1. 路由组件通常存放在pages文件夹,一般组件通常存放在components文件夹。
  2. 通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载。
  3. 每个组件都有自己的$route属性,里面存储着自己的路由信息。
  4. 整个应用只有一个router,可以通过组件的$router属性获取到。

# 3.多级路由(多级路由)

  1. 配置路由规则,使用children配置项:

    routes:[
    	{
    		path:'/about',
    		component:About,
    	},
    	{
    		path:'/home',
    		component:Home,
    		children:[ //通过children配置子级路由
    			{
    				path:'news', //此处一定不要写:/news
    				component:News
    			},
    			{
    				path:'message',//此处一定不要写:/message
    				component:Message
    			}
    		]
    	}
    ]
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
  2. 跳转(要写完整路径):

    <router-link to="/home/news">News</router-link>
    
    1
  3. 在要插入主键的位置编写 <router-view></router-view>

# 4.路由的query参数

多级路由

routes: [
    {
        path: '/home',
        component: Home,
        children: [
            {
                path: 'message',
                component: Message,
                children: [
                    {
                        path: 'details',
                        component: Details
                    }
                ]
            },
            {
                path: 'news',
                component: News
            }
        ]
    }
]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  1. 传递参数

    <!-- 跳转并携带query参数,to的字符串写法  需要加 ``解析路径否则报错  -->
    <router-link :to="`/home/message/detail?id=666&title=你好`">跳转</router-link>
    				
    <!-- 跳转并携带query参数,to的对象写法 -->
    <router-link 
    	:to="{
    		path:'/home/message/detail',
    		query:{
    		   id:666,
                title:'你好'
    		}
    	}"
    >跳转</router-link>
    
    <router-view></router-view>
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
  2. 接收参数:

    $route.query.id
    $route.query.title
    
    1
    2

# 5.命名路由

  1. 作用:可以简化路由的跳转。

  2. 如何使用

    1. 给路由命名:

      {
      	path:'/demo',
      	component:Demo,
      	children:[
      		{
      			path:'test',
      			component:Test,
      			children:[
      				{
                            name:'hello' //给路由命名
      					path:'welcome',
      					component:Hello,
      				}
      			]
      		}
      	]
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
    2. 简化跳转:

      <!--简化前,需要写完整的路径 -->
      <router-link to="/demo/test/welcome">跳转</router-link>
      
      <!--简化后,直接通过名字跳转 需要写在对象里-->
      <router-link :to="{name:'hello'}">跳转</router-link>
      
      <!--简化写法配合传递参数 -->
      <router-link 
      	:to="{
      		name:'hello',
      		query:{
      		   id:666,
                  title:'你好'
      		}
      	}"
      >跳转</router-link>
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16

# 6.路由的params参数

  1. 配置路由,声明接收params参数

    {
    	path:'/home',
    	component:Home,
    	children:[
    		{
    			path:'news',
    			component:News
    		},
    		{
    			component:Message,
    			children:[
    				{
    					name:'xiangqing',
    					path:'detail/:id/:title', //使用占位符声明接收params参数
    					component:Detail
    				}
    			]
    		}
    	]
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
  2. 传递参数

    <!-- 跳转并携带params参数,to的字符串写法 -->
    <router-link :to="/home/message/detail/666/你好">跳转</router-link>
    				
    <!-- 跳转并携带params参数,to的对象写法 -->
    <router-link 
    	:to="{
    		name:'xiangqing',
    		params:{
    		   id:666,
                title:'你好'
    		}
    	}"
    >跳转</router-link>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

    特别注意:路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置!

  3. 接收参数:

    $route.params.id
    $route.params.title
    
    1
    2

# 7.路由的props配置

​ 作用:让路由组件更方便的收到参数

{
	name:'xiangqing',
	path:'detail/:id',
	component:Detail,

	//第一种写法:props值为对象,该对象中所有的key-value的组合最终都会通过props传给Detail组件
	// props:{a:900}

	//第二种写法:props值为布尔值,布尔值为true,则把路由收到的所有params参数通过props传给Detail组件
	// props:true    无法使用query
	
	//第三种写法:props值为函数,该函数返回的对象中每一组key-value都会通过props传给Detail组件
	props($route){
		return {
			id:route.query.id,
			title:route.query.title
		}
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  1. 作用:控制路由跳转时操作浏览器历史记录的模式
  2. 浏览器的历史记录有两种写入方式:分别为pushreplacepush是追加历史记录,replace是替换当前记录(无痕游览,无法退到上一个页面)。路由跳转时候默认为push
  3. 如何开启replace模式:<router-link replace .......>News</router-link>

# 9.编程式路由导航

  1. 作用:不借助<router-link>实现路由跳转,让路由跳转更加灵活

  2. 具体编码:

    //$router的两个API
    this.$router.push({
    	name:'xiangqing',
    		params:{
    			id:xxx,
    			title:xxx
    		}
    })
    
    this.$router.replace({
    	name:'xiangqing',
    		params:{
    			id:xxx,
    			title:xxx
    		}
    })
    this.$router.forward() //前进
    this.$router.back() //后退 
    this.$router.go(3) //可前进也可后退 正数前进n步  负数后退n步
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19

# 10.缓存路由组件

  1. 作用:让不展示的路由组件保持挂载,不被销毁。

  2. 具体编码:

    <keep-alive include="News"> <!--想要缓存的路由名字 没写include默认缓存这个view的所有组件-->
        <!--:include="['News','Message']"-->
        <router-view></router-view>
    </keep-alive>
    
    1
    2
    3
    4

# 11.两个新的生命周期钩子

  1. 作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态。 配合 <keep-alive>使用
  2. 具体名字:
    1. activated路由组件被激活(显示)时触发。
    2. deactivated路由组件失活(切走)时触发。

# 12.路由守卫

  1. 作用:对路由进行权限控制

  2. 分类:全局守卫、独享守卫、组件内守卫

  3. 全局守卫:

    //全局前置守卫:初始化时执行、每次路由切换前执行
    router.beforeEach((to,from,next)=>{
    	console.log('beforeEach',to,from)
    	if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
    		if(localStorage.getItem('school') === 'atguigu'){ //权限控制的具体规则
    			next() //放行
    		}else{
    			alert('暂无权限查看')
    			// next({name:'guanyu'})
    		}
    	}else{
    		next() //放行
    	}
    })
    
    //全局后置守卫:初始化时执行、每次路由切换后执行
    router.afterEach((to,from)=>{
    	console.log('afterEach',to,from)
    	if(to.meta.title){ 
    		document.title = to.meta.title //修改网页的title
    	}else{
    		document.title = 'vue_test'
    	}
    })
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24

    meta 在路由下写

    routes: [
            children: [
                {
                    path: 'message',
                    component: Message,
                    meta: { isAuth: true ,title='消息'}, //没写就为false	
    
    1
    2
    3
    4
    5
    6
  4. 独享守卫:(专门对某个组件做拦截)

    beforeEnter:()=>{} /
    beforeEnter(to,from,next){
    	console.log('beforeEnter',to,from)
    	if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
    		if(localStorage.getItem('school') === 'atguigu'){
    			next()
    		}else{
    			alert('暂无权限查看')
    			// next({name:'guanyu'})
    		}
    	}else{
    		next()
    	}
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
  5. 组件内守卫:

    //进入守卫:通过路由规则,进入该组件时被调用
    beforeRouteEnter (to, from, next) {
    },
    //离开守卫:通过路由规则,离开该组件时被调用
    beforeRouteLeave (to, from, next) {
    }
    
    1
    2
    3
    4
    5
    6

# 13.路由器的两种工作模式

  1. 对于一个url来说,什么是hash值?—— #及其后面的内容就是hash值。
  2. hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器。
  3. hash模式:
    1. 地址中永远带着#号,不美观 。
    2. 若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。
    3. 兼容性较好。
  4. history模式:
    1. 地址干净,美观 。
    2. 兼容性和hash模式相比略差。
    3. 应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题。

# element-ui使用

  1. 完整引入

    安装 : npm i element-ui

    编辑 main.js

    import Vue from 'vue'
    import App from './App.vue'
    import ElementUI from 'element-ui';							// 引入ElementUI组件库
    import 'element-ui/lib/theme-chalk/index.css';	// 引入ElementUI全部样式
    
    Vue.config.productionTip = false
    
    Vue.use(ElementUI)	// 使用ElementUI
    
    new Vue({
        el:"#app",
        render: h => h(App),
    })
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

    使用

    <template>
    <div>
        <br>
        <el-row>
            <el-button icon="el-icon-search" circle></el-button>
            <el-button type="primary" icon="el-icon-edit" circle></el-button>
            <el-button type="success" icon="el-icon-check" circle></el-button>
            <el-button type="info" icon="el-icon-message" circle></el-button>
            <el-button type="warning" icon="el-icon-star-off" circle></el-button>
            <el-button type="danger" icon="el-icon-delete" circle></el-button>
        </el-row>
        </div>
    </template>
    
    <script>
        export default {
            name:'App',
        }
    </script>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
  2. 按需引入

    安装 babel-plugin-component npm i babel-plugin-component -D

    修改 babel-config-js

    module.exports = {
        presets: [
            '@vue/cli-plugin-babel/preset',
            //element-ui 官网为 2015 会报错
            ["@babel/preset-env", { "modules": false }]
        ],
        plugins: [
            [
                "component",
                {        
                    "libraryName": "element-ui",
                    "styleLibraryName": "theme-chalk"
                }
            ]
        ]
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

    main.js

    import Vue from 'vue'
    import App from './App.vue'
    import { Button,Row } from 'element-ui'	// 按需引入
    
    Vue.config.productionTip = false
    //应用的时候使用的名字可自定义    ('rong-button',Button)
    Vue.component(Button.name, Button);
    Vue.component(Row.name, Row);
    /* 或写为
     * Vue.use(Button)
     * Vue.use(Row)
     */
    
    new Vue({
        el:"#app",
        render: h => h(App),
    })
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17

# Vue3快速上手

# 1.Vue3简介

# 2.Vue3带来了什么

# 1.性能的提升

  • 打包大小减少41%

  • 初次渲染快55%, 更新渲染快133%

  • 内存减少54%

    ......

# 2.源码的升级

  • 使用Proxy代替defineProperty实现响应式

  • 重写虚拟DOM的实现和Tree-Shaking

    ......

# 3.拥抱TypeScript

  • Vue3可以更好的支持TypeScript

# 4.新的特性

  1. Composition API(组合API)

    • setup配置
    • ref与reactive
    • watch与watchEffect
    • provide与inject
    • ......
  2. 新的内置组件

    • Fragment
    • Teleport
    • Suspense
  3. 其他改变

    • 新的生命周期钩子
    • data 选项应始终被声明为一个函数
    • 移除keyCode支持作为 v-on 的修饰符
    • ......

# 一、创建Vue3.0工程

# 1.使用 vue-cli 创建

官方文档:https://cli.vuejs.org/zh/guide/creating-a-project.html#vue-create

## 查看@vue/cli版本,确保@vue/cli版本在4.5.0以上
vue --version
## 安装或者升级你的@vue/cli
npm install -g @vue/cli
## 创建
vue create vue_test
## 启动
cd vue_test
npm run serve
1
2
3
4
5
6
7
8
9

# 2.使用 vite 创建

官方文档:https://v3.cn.vuejs.org/guide/installation.html#vite

vite官网:https://vitejs.cn

  • 什么是vite?—— 新一代前端构建工具。
  • 优势如下:
    • 开发环境中,无需打包操作,可快速的冷启动。
    • 轻量快速的热重载(HMR)。
    • 真正的按需编译,不再等待整个应用编译完成。
  • 传统构建 与 vite构建对比图

## 创建工程
npm init vite-app <project-name>
## 进入工程目录
cd <project-name>
## 安装依赖
npm install
## 运行
npm run dev
1
2
3
4
5
6
7
8

# 二、常用 Composition API

官方文档: https://v3.cn.vuejs.org/guide/composition-api-introduction.html

# 1.拉开序幕的setup

  1. 理解:Vue3.0中一个新的配置项,值为一个函数。
  2. setup是所有Composition API(组合API)“ 表演的舞台 ”
  3. 组件中所用到的:数据、方法等等,均要配置在setup中。
  4. setup函数的两种返回值:
    1. 若返回一个对象,则对象中的属性、方法, 在模板中均可以直接使用。(重点关注!)
    2. 若返回一个渲染函数:则可以自定义渲染内容。(了解)
  5. 注意点:
    1. 尽量不要与Vue2.x配置混用
      • Vue2.x配置(data、methos、computed...)中可以访问到setup中的属性、方法。
      • 但在setup中不能访问到Vue2.x配置(data、methos、computed...)。
      • 如果有重名, setup优先。
    2. setup不能是一个async函数,因为返回值不再是return的对象, 而是promise, 模板看不到return对象中的属性。(后期也可以返回一个Promise实例,但需要Suspense和异步组件的配合)

# 2.ref函数

  • 作用: 定义一个响应式的数据
  • 语法: const xxx = ref(initValue)
    • 创建一个包含响应式数据的引用对象(reference对象,简称ref对象)
    • JS中操作数据: xxx.value
    • 模板中读取数据: 不需要.value,直接:<div></div>
  • 备注:
    • 接收的数据可以是:基本类型、也可以是对象类型。
    • 基本类型的数据:响应式依然是靠Object.defineProperty()getset完成的。
    • 对象类型的数据:内部 “ 求助 ” 了Vue3.0中的一个新函数—— reactive函数。

# 3.reactive函数

  • 作用: 定义一个对象类型的响应式数据(基本类型不要用它,要用ref函数)
  • 语法:const 代理对象= reactive(源对象)接收一个对象(或数组),返回一个代理对象(Proxy的实例对象,简称proxy对象)
  • reactive定义的响应式数据是“深层次的”。
  • 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据进行操作。

# 4.Vue3.0中的响应式原理

# vue2.x的响应式

  • 实现原理:

    • 对象类型:通过Object.defineProperty()对属性的读取、修改进行拦截(数据劫持)。

    • 数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)。

      Object.defineProperty(data, 'count', {
          get () {}, 
          set () {}
      })
      
      1
      2
      3
      4
  • 存在问题:

    • 新增属性、删除属性, 界面不会更新。
    • 直接通过下标修改数组, 界面不会自动更新。

# Vue3.0的响应式

  • 实现原理:
    • 通过Proxy(代理): 拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除等。
    • 通过Reflect(反射): 对源对象的属性进行操作。
    • MDN文档中描述的Proxy与Reflect:
      • Proxy:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy

      • Reflect:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect

        new Proxy(data, {
        	// 拦截读取属性值
            get (target, prop) {
            	return Reflect.get(target, prop)
            },
            // 拦截设置属性值或添加新属性
            set (target, prop, value) {
            	return Reflect.set(target, prop, value)
            },
            // 拦截删除属性
            deleteProperty (target, prop) {
            	return Reflect.deleteProperty(target, prop)
            }
        })
        
        proxy.name = 'tom'   
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16

# 5.reactive对比ref

  • 从定义数据角度对比:
    • ref用来定义:基本类型数据
    • reactive用来定义:对象(或数组)类型数据
    • 备注:ref也可以用来定义对象(或数组)类型数据, 它内部会自动通过reactive转为代理对象
  • 从原理角度对比:
    • ref通过Object.defineProperty()getset来实现响应式(数据劫持)。
    • reactive通过使用Proxy来实现响应式(数据劫持), 并通过Reflect操作源对象内部的数据。
  • 从使用角度对比:
    • ref定义的数据:操作数据需要.value,读取数据时模板中直接读取不需要.value
    • reactive定义的数据:操作数据与读取数据:均不需要.value

# 6.setup的两个注意点

  • setup执行的时机

    • 在beforeCreate之前执行一次,this是undefined。
  • setup的参数

    • props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性。
    • context:上下文对象
      • attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于 this.$attrs
      • slots: 收到的插槽内容, 相当于 this.$slots
      • emit: 分发自定义事件的函数, 相当于 this.$emit

# 7.计算属性与监视

# 1.computed函数

  • 与Vue2.x中computed配置功能一致

  • 写法

    import {computed} from 'vue'
    
    setup(){
        ...
    	//计算属性——简写
        let fullName = computed(()=>{
            return person.firstName + '-' + person.lastName
        })
        //计算属性——完整
        let fullName = computed({
            get(){
                return person.firstName + '-' + person.lastName
            },
            set(value){
                const nameArr = value.split('-')
                person.firstName = nameArr[0]
                person.lastName = nameArr[1]
            }
        })
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20

# 2.watch函数

  • 与Vue2.x中watch配置功能一致

  • 两个小“坑”:

    • 监视reactive定义的响应式数据时:oldValue无法正确获取、强制开启了深度监视(deep配置失效)。
    • 监视reactive定义的响应式数据中某个属性时:deep配置有效。
    //情况一:监视ref定义的响应式数据
    watch(sum,(newValue,oldValue)=>{
    	console.log('sum变化了',newValue,oldValue)
    },{immediate:true})
    
    //情况二:监视多个ref定义的响应式数据
    watch([sum,msg],(newValue,oldValue)=>{
    	console.log('sum或msg变化了',newValue,oldValue)
    }) 
    
    /* 情况三:监视reactive定义的响应式数据
    			若watch监视的是reactive定义的响应式数据,则无法正确获得oldValue!!
    			若watch监视的是reactive定义的响应式数据,则强制开启了深度监视 
    */
    watch(person,(newValue,oldValue)=>{
    	console.log('person变化了',newValue,oldValue)
    },{immediate:true,deep:false}) //此处的deep配置不再奏效
    
    //情况四:监视reactive定义的响应式数据中的某个属性
    watch(()=>person.job,(newValue,oldValue)=>{
    	console.log('person的job变化了',newValue,oldValue)
    },{immediate:true,deep:true}) 
    
    //情况五:监视reactive定义的响应式数据中的某些属性
    watch([()=>person.job,()=>person.name],(newValue,oldValue)=>{
    	console.log('person的job变化了',newValue,oldValue)
    },{immediate:true,deep:true})
    
    //特殊情况
    watch(()=>person.job,(newValue,oldValue)=>{
        console.log('person的job变化了',newValue,oldValue)
    },{deep:true}) //此处由于监视的是reactive素定义的对象中的某个属性,所以deep配置有效
    
    1
    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
    27
    28
    29
    30
    31
    32

# 3.watchEffect函数

  • watch的套路是:既要指明监视的属性,也要指明监视的回调。

  • watchEffect的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。

  • watchEffect有点像computed:

    • 但computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值。
    • 而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值。
    //watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调。
    watchEffect(()=>{
        const x1 = sum.value
        const x2 = person.age
        console.log('watchEffect配置的回调执行了')
    })
    
    1
    2
    3
    4
    5
    6

# 8.生命周期

vue2.x的生命周期lifecycle_2
vue3.0的生命周期lifecycle_2

1

  • Vue3.0中可以继续使用Vue2.x中的生命周期钩子,但有有两个被更名:
    • beforeDestroy改名为 beforeUnmount
    • destroyed改名为 unmounted
  • Vue3.0也提供了 Composition API 形式的生命周期钩子,与Vue2.x中钩子对应关系如下:
    • beforeCreate===>setup()
    • created=======>setup()
    • beforeMount ===>onBeforeMount
    • mounted=======>onMounted
    • beforeUpdate===>onBeforeUpdate
    • updated =======>onUpdated
    • beforeUnmount ==>onBeforeUnmount
    • unmounted =====>onUnmounted

# 9.自定义hook函数

  • 什么是hook?—— 本质是一个函数,把setup函数中使用的Composition API进行了封装。

  • 类似于vue2.x中的mixin。

  • 自定义hook的优势: 复用代码, 让setup中的逻辑更清楚易懂。

# 10.toRef

  • 作用:创建一个 ref 对象,其value值指向另一个对象中的某个属性。

  • 语法:const name = toRef(person,'name')

  • 应用: 要将响应式对象中的某个属性单独提供给外部使用时。

  • 扩展:toRefstoRef功能一致,但可以批量创建多个 ref 对象,语法:toRefs(person)

# 三、其它 Composition API

# 1.shallowReactive 与 shallowRef

  • shallowReactive:只处理对象最外层属性的响应式(浅响应式)。

  • shallowRef:只处理基本数据类型的响应式, 不进行对象的响应式处理。

  • 什么时候使用?

    • 如果有一个对象数据,结构比较深, 但变化时只是外层属性变化 ===> shallowReactive。
    • 如果有一个对象数据,后续功能不会修改该对象中的属性,而是生新的对象来替换 ===> shallowRef。

# 2.readonly 与 shallowReadonly

  • readonly: 让一个响应式数据变为只读的(深只读)。
  • shallowReadonly:让一个响应式数据变为只读的(浅只读)。
  • 应用场景: 不希望数据被修改时。

# 3.toRaw 与 markRaw

  • toRaw:
    • 作用:将一个由reactive生成的响应式对象转为普通对象
    • 使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新。
  • markRaw:
    • 作用:标记一个对象,使其永远不会再成为响应式对象。
    • 应用场景:
      1. 有些值不应被设置为响应式的,例如复杂的第三方类库等。
      2. 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。

# 4.customRef

  • 作用:创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。

  • 实现防抖效果:

    <template>
    	<input type="text" v-model="keyword">
    	<h3>{{keyword}}</h3>
    </template>
    
    <script>
    	import {ref,customRef} from 'vue'
    	export default {
    		name:'Demo',
    		setup(){
    			// let keyword = ref('hello') //使用Vue准备好的内置ref
    			//自定义一个myRef
    			function myRef(value,delay){
    				let timer
    				//通过customRef去实现自定义
    				return customRef((track,trigger)=>{
    					return{
    						get(){
    							track() //告诉Vue这个value值是需要被“追踪”的
    							return value
    						},
    						set(newValue){
    							clearTimeout(timer)
    							timer = setTimeout(()=>{
    								value = newValue
    								trigger() //告诉Vue去更新界面
    							},delay)
    						}
    					}
    				})
    			}
    			let keyword = myRef('hello',500) //使用程序员自定义的ref
    			return {
    				keyword
    			}
    		}
    	}
    </script>
    
    1
    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
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38

# 5.provide 与 inject

  • 作用:实现祖与后代组件间通信

  • 套路:父组件有一个 provide 选项来提供数据,后代组件有一个 inject 选项来开始使用这些数据

  • 具体写法:

    1. 祖组件中:

      setup(){
      	......
          let car = reactive({name:'奔驰',price:'40万'})
          provide('car',car)
          ......
      }
      
      1
      2
      3
      4
      5
      6
    2. 后代组件中:

      setup(props,context){
      	......
          const car = inject('car')
          return {car}
      	......
      }
      
      1
      2
      3
      4
      5
      6

# 6.响应式数据的判断

  • isRef: 检查一个值是否为一个 ref 对象
  • isReactive: 检查一个对象是否是由 reactive 创建的响应式代理
  • isReadonly: 检查一个对象是否是由 readonly 创建的只读代理
  • isProxy: 检查一个对象是否是由 reactive 或者 readonly 方法创建的代理

# 四、Composition API 的优势

# 1.Options API 存在的问题

使用传统OptionsAPI中,新增或者修改一个需求,就需要分别在data,methods,computed里修改 。

# 2.Composition API 的优势

我们可以更加优雅的组织我们的代码,函数。让相关功能的代码更加有序的组织在一起。

# 五、新的组件

# 1.Fragment

  • 在Vue2中: 组件必须有一个根标签
  • 在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中
  • 好处: 减少标签层级, 减小内存占用

# 2.Teleport

  • 什么是Teleport?—— Teleport 是一种能够将我们的组件html结构移动到指定位置的技术。

    <teleport to="移动位置">
    	<div v-if="isShow" class="mask">
    		<div class="dialog">
    			<h3>我是一个弹窗</h3>
    			<button @click="isShow = false">关闭弹窗</button>
    		</div>
    	</div>
    </teleport>
    
    1
    2
    3
    4
    5
    6
    7
    8

# 3.Suspense

  • 等待异步组件时渲染一些额外内容,让应用有更好的用户体验

  • 使用步骤:

    • 异步引入组件

      import {defineAsyncComponent} from 'vue'
      const Child = defineAsyncComponent(()=>import('./components/Child.vue'))
      
      1
      2
    • 使用Suspense包裹组件,并配置好defaultfallback

      <template>
      	<div class="app">
      		<h3>我是App组件</h3>
      		<Suspense>
      			<template v-slot:default>
      				<Child/>
      			</template>
      			<template v-slot:fallback>
      				<h3>加载中.....</h3>
      			</template>
      		</Suspense>
      	</div>
      </template>
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13

# 六、其他

# 1.全局API的转移

  • Vue 2.x 有许多全局 API 和配置。

    • 例如:注册全局组件、注册全局指令等。

      //注册全局组件
      Vue.component('MyButton', {
        data: () => ({
          count: 0
        }),
        template: '<button @click="count++">Clicked {{ count }} times.</button>'
      })
      
      //注册全局指令
      Vue.directive('focus', {
        inserted: el => el.focus()
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
  • Vue3.0中对这些API做出了调整:

    • 将全局的API,即:Vue.xxx调整到应用实例(app)上

      2.x 全局 API(Vue 3.x 实例 API (app)
      Vue.config.xxxx app.config.xxxx
      Vue.config.productionTip 移除
      Vue.component app.component
      Vue.directive app.directive
      Vue.mixin app.mixin
      Vue.use app.use
      Vue.prototype app.config.globalProperties

# 2.其他改变

  • data选项应始终被声明为一个函数。

  • 过度类名的更改:

    • Vue2.x写法

      .v-enter,
      .v-leave-to {
        opacity: 0;
      }
      .v-leave,
      .v-enter-to {
        opacity: 1;
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
    • Vue3.x写法

      .v-enter-from,
      .v-leave-to {
        opacity: 0;
      }
      
      .v-leave-from,
      .v-enter-to {
        opacity: 1;
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
  • 移除keyCode作为 v-on 的修饰符,同时也不再支持config.keyCodes

  • 移除v-on.native修饰符

    • 父组件中绑定事件

      <my-component
        v-on:close="handleComponentEvent"
        v-on:click="handleNativeClickEvent"
      />
      
      1
      2
      3
      4
    • 子组件中声明自定义事件

      <script>
        export default {
          emits: ['close']
        }
      </script>
      
      1
      2
      3
      4
      5
  • 移除过滤器(filter)

    过滤器虽然这看起来很方便,但它需要一个自定义语法,打破大括号内表达式是 “只是 JavaScript” 的假设,这不仅有学习成本,而且有实现成本!建议用方法调用或计算属性去替换过滤器。

God's Plan
Drake