VUE学习笔记

时间:2024-10-24 11:04:19

VUE项目的创建过程

cmd在指定的目录下面使用指令:

npm init vue@latest

然后出现下面的画面:

> npx
> create-vue


Vue.js - The Progressive JavaScript Framework

√ 请输入项目名称: ... vuedemo1
√ 是否使用 TypeScript 语法? .../ 是
√ 是否启用 JSX 支持? .../ 是
√ 是否引入 Vue Router 进行单页面应用开发? .../ 是
√ 是否引入 Pinia 用于状态管理? .../ 是
√ 是否引入 Vitest 用于单元测试? .../ 是
√ 是否要引入一款端到端(End to End)测试工具? » 不需要
√ 是否引入 ESLint 用于代码质量检测? .../ 是
√ 是否引入 Vue DevTools 7 扩展用于调试? (试验阶段) .../ 是

正在初始化项目 D:\workspaces\workspace2-vs\vuedemo1...

项目初始化完成,可执行以下命令:

  cd vuedemo1
  npm install
  npm run dev

以上的内容都选择no就可以了。

接下来进入到项目中,开始安装项目。

插播一下,需要先安装cnpm,国内的镜像地址,并且设置环境变量。

设置两个文件夹,这两个文件夹的位置就是nodejs的根目录的下面

打开安装根路径

创建两个空文件夹node_cache 、node_global

然后设置环境变量。

在这里插入图片描述

更换 npm 的 registry 源为国内源

npm install -g cnpm --registry=https://registry.npmmirror.com

安装

C:\Windows\System32>npm install -g cnpm --registry=https://registry.npmmirror.com

added 1 package in 11s
....

C:\Windows\System32>cnpm -v
cnpm@9.4.0 (C:\Users\77572\AppData\Roaming\npm\node_modules\cnpm\lib\parse_argv.js)
....
registry=https://registry.npmmirror.com

cnpm的设置成功。

安装项目,执行代码:

初始化

npm init vue@latest

安装

cnpm install

运行项目:

npm run dev

访问:http://localhost:5173/

运行vscode

打开项目,注意在插件中心中安装一个插件:Volar扩展,这样可以让你的文档内容高亮显示。

项目的目录结构:

.vscode			VSCode工具的配置文件
node_moudules	Vue项目的运行依赖文件夹
public			资源文件夹
src				源码文件夹
.gitignore		git忽略文件
index.html		入口HTML文件
jsconfig.json	为项目提供更好的编译支持:智能提示、代码导航、重构
package.json	信息描述文件
README.md		注释文件
vite.config.js	vue的配置文件

遇见问题:

  1. 无法创建项目

  2. 检查网络连接:确保你的计算机可以正常访问互联网。

  3. 检查镜像源地址:确认你使用的镜像源地址是正确的,并且该镜像服务当前是可用的。

  4. 清除npm缓存:运行npm cache clean --force清除npm缓存后再尝试。

  5. 更换npm镜像源:可以尝试更换回官方npm源。运行

    1. npm config set registry https://registry.npmmirror.com
      

模板语法

  1. 清空所有的components文件夹下的内容,
  2. 在main.js当中,删除:import ‘./assets/main.css’
  3. 在App.vue中只留下template和script即可。

从空的内容开始写起来:

<template>
   <h3>模板语法</h3>
   <p>{{ msg }}</p>
</template>

<script>

export default {
  data(){
    return{
      msg:"语法1234"
    }
  }
}
</script>

记住凡是有return结果的,都可以使用两个大括号。

<template>
   <h3>模板语法</h3>
   <p>{{ msg }}</p>
   <p>{{ num +1 }}</p>
   <p>{{ ok?'yes':'no' }}</p>
   <p>{{ message.split('').reverse().join('') }}</p>
   <p>{{ rawHTML }}</p>
   <p v-html="rawHTML"></p><!-- 可以实现html原始内容输出 -->
</template>

<script>

export default {
  data(){
    return{
      msg:"语法1234",
      num:10,
      ok:false,
      message:"大家好我是张三",
      rawHTML:"<a href='http://www.baidu.com'>百度一下</a>"
    }
  }
}
</script>

属性绑定

v-bind:xxx

比如:v-bind:id=“xxx”,v-bin:class=“xxxx”

<template>
  <!-- <div class="{{ msg }}">{{ msg }}</div> -->
  <div v-bind:class="msg" v-bind:id="dymainId">{{ msg }}</div>
</template>

<script>
export default {
  data(){
    return{
      msg:"helloworld",
      dymainId:"appId"
    }
  }
}

</script>
<style>
.helloworld{
  color: red;
}
</style>

简写方案:

:bind:xxx

可以是布尔型的

<button :disabled="isButtonDis">按钮点击</button>
export default {
  data(){
    return{
      isButtonDis:true
    }
  }
}

动态的绑定多个值:

<div v-bind="objectAttrs">111</div>
export default {
  data(){
    return{
      objectAttrs: {
          class: "dtid",
          id: "dtClass"
      }
    }
  }
}

条件渲染

在vue中,提供了条件渲染,这类似于JavaScript中的条件语句

  • v-if
  • v-else
  • v-else-if
  • v-show

v-if

v-if 指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回真值时才被渲染

<template>
    <h3>条件渲染</h3>
    <div v-if="flag">您能看见我吗</div>
</template>
<script>
export default {
    data(){
        return{
            flag:true
        }
    }
}
</script>

v-else

你也可以使用 v-else 为 v-if 添加一个“else 区块”

<template>
    <h3>条件渲染</h3>
    <div v-if="flag">您能看见我吗</div>
    <div v-else="flag">那你一定能看到else</div>
</template>
<script>
export default {
    data(){
        return{
            flag:false
        }
    }
}
</script>

v-esle-if

顾名思义,V-else-if 提供的是相应于 v-if 的”else if 区块”。它可以连续多次重复使用

<template>
    <h3>条件渲染</h3>
    <div v-if="flag">您能看见我吗</div>
    <div v-else="flag">那你一定能看到else</div>
    <div v-if="type=='A'">A</div>
    <div v-else-if="type=='B'">B</div>
    <div v-else-if="type=='C'">C</div>
    <div v-else="type=='D'">not ABC</div>
</template>
<script>
export default {
    data(){
        return{
            flag:false,
            type: "B"
        }
    }
}
</script>

v-show

另一个可以用来按条件显示一个元素的指令是 v-show 。其用法基本一样

<template>
    <h3>条件渲染</h3>
    <div v-show="!flag">您能看见show吗?</div>
</template>
<script>
export default {
    data(){
        return{
            flag:false
        }
    }
}
</script> 

v-if VS v-show

vif 是“真实的”按条件染,因为它确保了在切换时,条件区块内的事件监听器和子组件都会被销毁与重建。

vif 也是情性的:如果在初次染时条件值为 false,则不会做任何事。条件区块只有当条件首次变为 true 时才被渲染。

相比之下,v-show 简单许多,元素无论初始条件如何,始终会被染,只有 CSS display 属性会被切换.

总的来说,vif 有更高的切换开销,而 show 有更高的初始染开销。因此,如果需要频繁切换,则使用show 较好;如果在运行时绑定条件很少改变,则 v-if 会更合适

列表渲染

我们可以使用 v-for 指令基于一个数组来染一个列表形式的特殊语法v-for 指令的值需要使用 item in items其中 items 是源数据的数组,而 item 是选代项的别名

<template>
    <h3>列表渲染</h3>
    <p v-for="item in names">{{ item }}</p>
</template>
<script>
export default {
    data(){
        return{
            names:["apple","boy","cat"]
        }
    }
}
</script>

复杂数据

大多数情况,我们渲染的数据源来源于网络请求,也就是 JSON 格式

<template>
    <h3>列表渲染</h3>
    <p v-for="item in names">{{ item }}</p>
    <div v-for="item in result">
        <p>{{ item.title }}</p>
        <img :src="item.avator" :alt="item.id">
    </div>
</template>
<script>
export default {
    data() {
        return {
            names: ["apple", "boy", "cat"],
            result: [{
                "id": 2261677,
                "title": "鄂尔多斯| 感受一座城市的璀璨夜景 感受一座城市,除了白日里的车水马龙,喧嚣繁华之",
                "avator": "https://pic.qyer.com/avatar/002/25/77/30/200?v=156226451",
            },
            {
                "id": 2261566,
                "title": "成都这家洞穴暗黑风咖啡厅酷毙了!早C晚A走起成都天气这么热 咖啡头人必",
                "avator": "https://pic.qyer.com/avatar/011/07/08/69/200?v=1572185180"
            },
            {
                "id": 2261662,
                "title": "[川西新龙-措卡湖] 措卡湖,意为“乱石从中的黑色海水”,神秘小众 原汁原味。",
                "avator": "https://pic.qyer.com/avatar/009/88/48/58/200?v=1507386782"
            }
            ]
        }
    }
}
</script>

v-for 也支持使用可选的第二个参数表示当前项的位置索引

<p v-for="(item,index) in names">{{ item }}-{{ index }}</p>

你也可以使用 of 作为分隔符来替代 in 这更接近JavaScript 的选代器语法

<p v-for="(item,index) of names">{{ item }} - {{ index }}</p>

v-fo 与对象

你可以使用 v-for 来遍历一个对象的所有属性

<template>
    <h3>列表渲染</h3>
    <div>
        <p v-for="(item,key,index) of userInfo">{{ item }} - {{ key }} - {{ index }}</p>
    </div>
</template>
<script>
export default {
    data() {
        return {
            userInfo:{
                name: "小明",
                age: 20,
                sex: "男"
            }
        }
    }
}
</script>

结果显示:

小明 - name - 0
20 - age - 1
男 - sex - 2

通过key管理状态

Vue 默认按照"就地更新”的策略来更新通过 or 染的元素列表。当数据项的顺序改变时,Vue 不会随之移动 DOM 元素的顺序,而是就地更新每个元素,确保它们在原本指定的索引位置上渲染。

为了给 Vue 一个提示,以便它可以跟踪每个节点的标识,从而重用和重新排序现有的元素,你需要为每个元素对应的块提供一个唯一的 key attribute:

<template>
    <h3>Key属性添加到v-for当中</h3>
    <p v-for="(item,index) in names" :key="index">{{ item }}</p>
</template>
<script>
export default {
    data(){
        return{
            names:["xiaomi","huawei","vivo","oppo"]
        }
    }
}
</script>

key的来源

请不要使用index作为key的值,我们要确保每一条数据的唯一索引不会发生变化

<template>
    <h3>Key属性添加到v-for当中</h3>
    <p v-for="(item,index) in names" :key="index">{{ item }}</p>
    <div v-for="item in result" :key="item.id">
        <p>{{ item.title }}</p>
        <img :src="item.avator">
    </div>
</template>
<script>
export default {
    data(){
        return{
            names:["xiaomi","huawei","vivo","oppo"],
            result: [{
                "id": 2261677,
                "title": "鄂尔多斯| 感受一座城市的璀璨夜景 感受一座城市,除了白日里的车水马龙,喧嚣繁华之",
                "avator": "https://pic.qyer.com/avatar/002/25/77/30/200?v=156226451",
            },
            {
                "id": 2261566,
                "title": "成都这家洞穴暗黑风咖啡厅酷毙了!早C晚A走起成都天气这么热 咖啡头人必",
                "avator": "https://pic.qyer.com/avatar/011/07/08/69/200?v=1572185180"
            },
            {
                "id": 2261662,
                "title": "[川西新龙-措卡湖] 措卡湖,意为“乱石从中的黑色海水”,神秘小众 原汁原味。",
                "avator": "https://pic.qyer.com/avatar/009/88/48/58/200?v=1507386782"
            }
            ]
        }
    }
}
</script>

事件处理

我们可以使用 v-on 指令(简写为 @)来监听 DOM 事件,并在事件触发时执行对应的 JavaScript。用法:

v-on:click=“methodName” 或 @click="handler

事件处理器的值可以是

  1. 内联事件处理器: 事件被触发时执行的内联JavaScript 语句(与 onlick 类似)

  2. 方法事件处理器:一个指向组件上定义的方法的属性名或是路径

内联事件处理器

内联事件处理器通常用于简单场景

<template>
    <h3>内联事件处理器</h3>
    <button @click="count++">Add</button>
    <p>{{ count }}</p>
</template>
<script>
export default {
    data(){
        return{
            count:0
        }
    }
}
</script>

方法事件处理器

<template>
    <h3>内联事件处理器2</h3>
    <!-- 放入的是一个事件 -->
    <button @click="addCount">Add</button>
    <p>{{ count }}</p>
</template>
<script>
export default {
    data(){
        return{
            count:0
        }
    },
    //所有的函数都放在这里
    methods:{
        addCount(){
            this.count++
        }
    }
}
</script>

事件参数

事件参数可以获取 event 对象和通过事件传递数据

获取event对象

<template>
    <h3>内联事件处理器3</h3>
    <!-- 放入的是一个事件 -->
    <button @click="addCount">Add</button>
    <p>{{ count }}</p>
</template>
<script>
export default {
    data(){
        return{
            count:0
        }
    },
    //所有的函数都放在这里
    methods:{
        addCount(e){
            //Vue中的event对象,就是原生JS的Event对象
            e.target.innerHTML="Add"+this.count;
            console.log(e.target);
            this.count++
        }
    }
}
</script>

传递参数

<template>
    <h3>事件传参</h3>
    <p @click="getNameHandler(item)" v-for="(item,index) of names" :key="index">{{ item }}</p>
</template>
<script>
export default {
    data(){
        return{
            names:["Jack","Tom","Lucy"]
        }
    },
    //所有的函数都放在这里
    methods:{
        getNameHandler(name){
            console.log(name);
        }
    }
}
</script>
<style>
    p{
        cursor: pointer;
    }
</style>

传递参数过程中获取event对象

<template>
    <h3>事件传参</h3>
    <p @click="getNameHandler(item,$event)" v-for="(item,index) of names" :key="index">{{ item }}</p>
</template>
<script>
export default {
    data(){
        return{
            names:["Jack","Tom","Lucy"]
        }
    },
    //所有的函数都放在这里
    methods:{
        getNameHandler(name,e){
            console.log(name);
            console.log(e);
        }
    }
}
</script>

可以传递event对象,但是一定是最后面。使用的语法是:$event

事件修饰符

如处理事件时调用event.prevenDefault() 或 event.stopPropagation()是很常见的。尽管我们可以直接在方法内调用,但如果方法能更专注于数据逻辑而不用去处理DOM事件的细节会更好。

为解决这一问题,Vue为v-on提供了时间修饰符,常用有以下几个:

  1. .stop
  2. .prevent
  3. .once
  4. .enter
具体参考地址:
地址:https://cn.vuejs.org/guide/essentials/event-handling#event-modifiers

阻止默认事件

@click.prevent 代替了 e.preventDefault(); 的行为操作

<template>
    <h3>事件修饰符</h3>
    <a href="http://www.baidu.com" @click.prevent="clickHandle">百度一下</a>
</template>
<script>
export default {
    data(){
        return{

        }
    },
    methods:{
        clickHandle(e){
            //阻止了默认事件
            //e.preventDefault();
            console.log("点击了");
        }
    }
}
</script>

阻止事件冒泡

<template>
    <h3>事件修饰符</h3>
    <a href="http://www.baidu.com" @click.prevent="clickHandle">百度一下</a>
    <div @click="clickDiv">
        <p @click.stop="clickP">测试冒泡</p>
    </div>
</template>
<script>
export default {
    data(){
        return{

        }
    },
    methods:{
        clickHandle(e){
            //阻止了默认事件
            //e.preventDefault();
            console.log("点击了");
        },
        clickDiv(){
            console.log("点击了DIV");
        },
        clickP(e){
            //阻止事件冒泡(点击子元素不会触发父元素)
            //e.stopPropagation();
            console.log("点击了P");
        }
    }
}
</script>

数组侦测

变更方法

Vue 能够侦听响应式数组的变更方法,并在它们被调用时触发相关的更新。这些变更方法包括:

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • short()
  • reverse()
<template>
    <h3>数组变化侦听</h3>
    <button v-on:click="addListHandle">添加数据</button>
    <ul>
        <li v-for="(item,index) of names" :key="index">{{ item }}</li>
    </ul>
</template>
<script>
export default {
    data(){
        return{
            names:["xiaomi","huawei","vivo","oppo"]
        }
    },
    methods:{
        addListHandle(){
            // 引起UI自动更新
            this.names.push("rongyao");
        }
    }
}
</script>

替换一个数组

变更方法,顾名思义,就是会对调用它们的原数组进行变更。相对地,也有一些不可变 (immutable) 方法,例如 filter(),concat0 和 sice(),这些都不会更改原数组,而总是返回一个新数组。当遇到的是非变更方法时,我们需要将旧的数组替换为新的。

<template>
    <h3>数组变化侦听</h3>
    <button v-on:click="addListHandle">添加数据</button>
    <ul>
        <li v-for="(item,index) of names" :key="index">{{ item }}</li>
    </ul>
</template>
<script>
export default {
    data(){
        return{
            names:["xiaomi","huawei","vivo","oppo"]
        }
    },
    methods:{
        addListHandle(){
            // 引起UI自动更新
            //this.names.push("rongyao");
            //不会更新UI
            this.names.concat("rongyao");
            console.log(this.names.concat("rongyao"));
            //如果想要讲UI更新的话
            this.names = this.names.concat("rongyao");
        }
    }
}
</script>
<template>
    <h3>数组变化侦听</h3>
    <button @click="countHandle">合并数组,都将数组合并到数组1当中</button>
    <h3>数组1</h3>
    <p v-for="(item,index) of numbers1" :key="index">{{ item }}</p>
    <h3>数组2</h3>
    <p v-for="(item,index) of numbers2" :key="index">{{ item }}</p>
</template>
<script>
export default {
    data(){
        return{
            numbers1:[1,2,3,4,5,6],
            numbers2:[7,8,9]
        }
    },
    methods:{
        countHandle(){
            this.numbers1 = this.numbers1.concat(this.numbers2);
        }
    }
}
</script>

计算属性

模板中的表达式虽然方便,但也只能用来做简单的操作。如果在模板中写太多逻辑,会让模板变得臃肿,难以维护。因此我们推荐使用计算属性来描述依赖响应式状态的复杂逻辑

<template>
    <h3>{{ object1.name }}</h3>
    <h3>{{ calc }}</h3>
</template>
<script>
export default {
    data(){
        return{
            object1:{
                name:"jack",
                hobby:["java","js","html","mysql"]
            }
        }
    },
    //计算属性
    computed:{
        calc(){
            return this.object1.hobby.length>0?"yes":"no"
        }
    }
}
</script>

计算属性缓存 vs 方法

<template>
    <h3>{{ object1.name }}</h3>
    <h3>{{ calc }}</h3>
    <h3>{{ calc2() }}</h3>
</template>
<script>
export default {
    data(){
        return{
            object1:{
                name:"jack",
                hobby:["java","js","html","mysql"]
            }
        }
    },
    //计算属性
    computed:{
        calc(){
            return this.object1.hobby.length>0?"yes":"no"
        }
    },
    // 使用函数完成
    methods:{
        calc2(){
            return this.object1.hobby.length>0?"yes":"no"
        }
    }
}
</script>

重点区别:

计算属性: 计算属性值会基于其响应式依赖被缓存一个计算属性仅会在其响应式依赖更新时才重新计算

方法: 方法调用总是会在重染发生时再次执行函数。

总结:将负责的属性提取到计算属性中实现,而不会直接在模板语法中实现。

Class绑定

数据绑定的一个常见需求场景是操纵元素的 CSS class 列表,因为 class 是 attribute,我们可以和其他attribute 一样使用 v-bind 将它们和动态的字符串绑定。但是,在处理比较复杂的绑定时,通过拼接生成字符串是麻烦且易出错的。因此,Vue 专门为 class 的 v-bind 用法提供了特殊的功能增强。除了字符串外,表达式的值也可以是对象或数组

<template>
    <p :class="{'c1':isActive,'c2':hasError}">Class样式的绑定</p>
    <p>Class样式的绑定</p>
</template>
<script>
export default {
    data(){
        return{
            isActive:true,
            hasError:true
        }
    }
}
</script>
<style>
.c1{
    font-size: 32px;
}
.c2{
    color:red;
}
</style>

多个对象绑定

<template>
    <p :class="{'c1':isActive,'c2':hasError}">Class样式的绑定</p>
    <p :class="classObject">Class样式的绑定</p>
</template>
<script>
export default {
    data(){
        return{
            isActive:true,
            hasError:true,
            classObject:{
                'c1':true,
                'c2':true
            }
        }
    }
}
</script>
<style>
.c1{
    font-size: 32px;
}
.c2{
    color:red;
}
</style>

数组绑定

详情见API

Class 与 Style 绑定 | Vue.js (vuejs.org)

Style绑定

数据绑定的一个常见需求场景是操纵元素的 CSS style列表,因为 style 是 attribute,我们可以和其他attribute 一样使用 bind 将它们和动态的字符串绑定。但是,在处理比较复杂的绑定时,通过拼接生成字符串是麻烦且易出错的。因此,Vue 专门为 style 的 v-bind 用法提供了特殊的功能增强。除了字符串外,表达式的值也可以是对象数组

绑定对象

<template>
    <h3>Style绑定</h3>
    <p :style="{color:activeColor,fontSize:fColor+'px'}">这是我样式所绑定的内容信息</p>
</template>
<script>
export default {
    data() {
        return {
            activeColor: 'red',
            fColor: 30
        }
    }
}
</script>
<template>
    <h3>Style绑定</h3>
    <p :style="{color:activeColor,fontSize:fColor+'px'}">这是我样式所绑定的内容信息</p>
    <p :style="styleObject">这是我样式所绑定的内容信息2</p>
</template>
<script>
export default {
    data() {
        return {
            activeColor: 'red',
            fColor: 30,
            styleObject:{
                color:"red",
                fontSize:'32px'
            }
        }
    }
}
</script>

侦听器

我们可以使用 watch 选项在每次相应式属性发生变化时触发一个函数

<template>
    <h3>侦听器</h3>
    <button @click="updateHandle">修改数据</button>
    <p>{{ message }}</p>
</template>
<script>

export default {

    data(){

        return{
            message:"hello"
        }
    },
    methods:{
        updateHandle(){
            this.message = "world"
        }
    },
    // 侦听器
    watch:{
        // newObj:改变之后的数据
        // oldObj:改变之前的数据
        // 函数名必须与侦听的数据对象保持一致
        message(newObj,oldObj){
            // 数据发生变化,自动执行的函数
            console.log(newObj,oldObj);
        }
    }
}

</script>

表单的输入绑定

在前端处理表单时,我们常常需要将表单输入框的内容同步给 JavaScript 中相应的变量。手动连接值绑定和更改事件监听器可能会很麻烦,v-model 指令帮我们简化了这一步骤

<template>
    <h3>表单输入绑定</h3>
    <form>
        <input type="text" v-model="message">
        <p>{{ message }}</p>
    </form>
</template>
<script>

export default {

    data() {
        return {
            message:""
        }
    }
}
</script>

复选框

单一的复选框,绑定布尔类型值

<template>
    <h3>表单输入绑定</h3>
    <form>
        <input type="checkbox" v-model="checked"/>是否选中
        <p>{{ checked }}</p>
    </form>
</template>
<script>

export default {
    data() {
        return {
            checked:false
        }
    }
}
</script>

修饰符

v-model 也提供了修饰符:.lazy、.number、.trim、.lazy

.number只获取数字,.trim去掉前后的空格

默认情况下,vmode 会在每次 inut 事件后更新数据。你可以添加 zy 修饰符来改为在每次 change 事件后更新数据

<template>
    <h3>表单输入绑定</h3>
    <form>
        <input type="text" v-model="message"><br>
        <input type="text" v-model.lazy="message">
        <p>{{ message }}</p>
        <input type="checkbox" id="checkbox" v-model="checked"/>是否选中
        <label for="checkbox">{{ checked }}</label>
    </form>
</template>
<script>

export default {

    data() {
        return {
            message:"",
            checked:false
        }
    }
}
</script>

模板引用

虽然 Vue 的声明性渲染模型为你抽象了大部分对 DOM 的直接操作,但在某些情况下,我们仍然需要直接访问底层 DOM 元素。要实现这一点,我们可以使用特殊的 ref attribute 挂载结束后引用都会被暴露在 this.$refs 之上

<template>
    <div ref="container" claas="container">{{content}}</div>
    <input type="text" ref="username"/>
    <button @click="getElementHandle">获取元素</button>
</template>
<script>
/**
 * 内容改变:{{ 模板语法 }}
 * 属性改变:v-bind:指令
 * 事件:v-on:click
 * 
 * 如果没有特别的需求,不要操作DOM
 */
export default {
    data() {
        return {
            content:"内容"
        }
    },
    methods:{
        getElementHandle(){
            // innerHTML:原生JS的属性
            console.log(this.$refs.container.innerHTML="newValue");
            console.log(this.$refs.username.value);
        }
    }
}

</script>

其实就是读取DOM,建议:如果没有特别的需求,不要操作DOM。

组件组成

组件最大的优势就是可复用性当使用构建步骤时,我们一般会将 Vue 组件定义在一个单独的 .vue 文件中,这被叫做单文件组件(简称 SFC)

组件组成结构

<template>
<!-- 包含所有的HTML -->
</template>
<script>
//包含所有的逻辑
export default {}
</script>
<style>
/**包含了所有的样式*/
</style>

组件的引用

<template>
    <!-- 第三步:显示组件 -->
     <MyComponent/>
</template>

<script>
// 第一步:引入组件
import MyComponent from './components/MyComponent.vue'

export default {
  // 第二步:注入组件
  components:{
    MyComponent
  }
}
</script>
<style scoped>
</style>

style scoped 这里 scoped 的作用:让当前样式只在当前组件中生效

组件嵌套关系

在这里插入图片描述

组件允许我们将UI 划分为独立的、可重用的部分,并目可以对每个部分进行单独的思考。在实际应用中,组件常常被组织成层层嵌套的树状结构这和我们嵌套 HTML 元素的方式类似,Vue 实现了自己的组件模型,使我们可以在每个组件内封装自定义内容与逻辑

创建组件及引用关系

Header
<template>
    <h3>Header</h3>
</template>
<style scoped>
h3{
    width: 100%;
    height: 100px;
    border: 5px solid #999;
    text-align: center;
    line-height: 100px;
    box-sizing: border-box;
}
</style>
Main
<template>
    <div class="main">
        <h3>Main</h3>
        <Article />
        <Article />
    </div>
</template>
<script>
import Article from './Article.vue';
export default {
    components: {
        Article
    }
}
</script>
<style scoped>
.main {
    float: left;
    width: 70%;
    height: 400px;
    border: 5px solid #999;
    box-sizing: border-box;
}
</style>
Aside
<template>
    <div class="aside">
        <h3>Aside</h3>
        <Item />
        <Item />
        <Item />
    </div>
</template>
<script>
import Item from './Item.vue';
export default {
    components: {
        Item
    }

}
</script>
<style scoped>
.aside {
    float: left;
    width: 30%;
    height: 400px;
    border: 5px solid #999;
    box-sizing: border-box;
}
</style>
Article
<template>
    <h3>Aritcle</h3>
</template>
<style scoped>
h3{
    width: 80%;
    margin: 0 auto;
    text-align: center;
    line-height: 100px;
    box-sizing: border-box;
    margin-top: 50px;
    background: #999;
}
</style>
Item
<template>
    <h3>Item</h3>
</template>
<style scoped>
h3{
   width: 80%;
   margin: 0 auto;
   text-align: center; 
   line-height: 80px;
   box-sizing: border-box;
   margin-top: 25px;
   background: #999;
}
</style>

组件的注册方式

个 Vue 组件在使用前需要先被”注册”,这样 Vue 才能在染模板时找到其对应的实现。组件注册有两种方式:全局注册和局部注册

全局注册

在最外面注册一次,在任意组件中都可以引用。

main.js 当中注册

import { createApp } from 'vue'
import App from './App.vue'
import Header from './pages/Header.vue'

const app = createApp(App)

// 在这中间写组件的注册
app.component("Header",Header);

app.mount("#app")

局部注册

意思是在某一个组件中引用和注册。

全局注册虽然很方便,但有以下几个问题:

  1. 全局注册,但并没有被使用的组件无法在生产打包时被自动移除(也叫“tree-shaking”)。如果你全局注册了一个组件,即使它并没有被实际使用,它仍然会出现在打包后的JS文件中
  2. 全局注册在大型项目中使项目的依赖关系变得不那么明确。在父组件中使用子组件时,不太容易定位子组件的实现。和使用过多的全局变量一样,这可能会影响应用长期的可维护性

局部注册需要使用 components 选项

组件传递数据 Props

组件与组件之间不是完全独立的,而是有交集的,那就是组件与组件之间是可以传递数据的传递数据的解决方案就是 props

<template>
    <h3>Parent</h3>
    <Child title="Parent的数据" testdemo="测试的文本"/>
</template>
<script>
import Child from './Child.vue';
export default {
    components:{
        Child
    }
}
</script>

子组件显示 title

<template>
    <h3>Child</h3>
    <p>{{ title }}</p>
    <p>{{ testdemo }}</p>
</template>
<script>
export default {
    props:["title","testdemo"]
}
</script>

动态数据传递

<template>
    <h3>Parent</h3>
    <Child :title="message" testdemo="测试的文本"/>
</template>
<script>
import Child from './Child.vue';
export default {
    data(){
        return{
            message:"动态的信息数据"
        }
    },
    components:{
        Child
    }
}
</script>

注意事项:

props传递数据,只能从父级传递到子级,不能反其道而行

组件传递多种数据类型

通过 props 传递数据,不仅可以传递字符串类型的数据,还可以是其他类型,例如: 数字、对象、数组等;但实际上任何类型的值都可以作为 props 的值被传递

Number

<template>
    <h3>Parent</h3>
    <Child :title="message" testdemo="测试的文本" :age="age"/>
</template>
<script>
import Child from './Child.vue';
export default {
    data(){
        return{
            message:"动态的信息数据",
            age:20
        }
    },
    components:{
        Child
    }
}
</script>

数组类型

<template>
    <h3>Parent</h3>
    <Child :title="message" testdemo="测试的文本" :age="age" :names="names"/>
</template>
<script>
import Child from './Child.vue';
export default {
    data(){
        return{
            message:"动态的信息数据",
            age:20,
            names:["huawei","rongyao","vivo","oppo"]
        }
    },
    components:{
        Child
    }
}
</script>
<template>
    <h3>Child</h3>
    <p>{{ title }}</p>
    <p>{{ testdemo }}</p>
    <p>{{ age }}</p>
    <p>{{ names }}</p>
    <ul>
        <li v-for="(item,index) of names" :key="index">{{ item }}</li>
    </ul>
</template>
<script>
export default {
    props:["title","testdemo","age","names"]
}
</script>

对象类型

<template>
    <h3>Parent</h3>
    <Child :userInfo="userInfo"/>
</template>
<script>
import Child from './Child.vue';
export default {
    data(){
        return{
            userInfo:{
                name:"zhangsan",
                age:20
            }
        }
    },
    components:{
        Child
    }
}
</script>
<template>
    <h3>Child</h3>
    <p>{{ userInfo.name }} - {{ userInfo.age }}</p>
</template>
<script>
export default {
    props:["userInfo"]
}
</script>

组件传递数据Props效验

Vue组件可以更细致地声明对传入的props的校验要求

验证的意思,具体指定什么类型

校验类型

<template>
    <h3>ComponentA</h3>
    <ComponentB :title="title"/>
</template>
<script>
import ComponentB from './ComponentB.vue';
export default {
    data(){
        return{
            title:"字符串信息123"
        }
    },
    components:{
        ComponentB
    }   
}
</script>
<template>
    <h3>ComponentB</h3>
    <p>{{ title }}</p>
</template>
<script>
export default {
    //props:["title"]
    props: {
        title: {
            //type:String
            type: [Number, String, Array, Object]
        }
    }
}
</script>

默认值

<template>
    <h3>ComponentB</h3>
    <p>{{ title }}</p>
    <p>{{ age }}</p>
    <ul>
        <li v-for="(item,index) of names">{{ item }}</li>
    </ul>
</template>
<script>
export default {
    //props:["title"]
    props: {
        title: {
            //type:String
            type: [Number, String, Array, Object]
        },
        age: {
            type: Number,
            default: 0 //默认值
        },
        // 数字和字符串可以直接default,但是如果是数组和对象,必须通过工厂函数返回默认值
        names: {
            type: Array,
            default(){
                return ["空"]
            }
        }
    }
}
</script>

必选项

<template>
    <h3>ComponentB</h3>
    <p>{{ title }}</p>
</template>
<script>
export default {
    props: {
        title: {
            type:String
            required:true
        }
    }
}
</script>

组件事件

在组件的模板表达式中,可以直接使用 $emit 方法触发自定义事件,触发自定义事件的目的是组件之间传递数据

就是子组件给父组件传值的过程

<template>
    <h3>子组件</h3>
    <button @click="clickEventHandle">子组件的按钮</button>
</template>
<script>
export default {
    data(){
        return{     
            msg:"子元素的msg"
        }
    },
    methods:{
        clickEventHandle(){
            // 自定义事件
            this.$emit("someEvent",this.msg)
        }
    }
}
</script>
<style>

</style>
<template>
    <h3>组件事件</h3>
    <Child @someEvent="getHandle"/>
    <p>父元素:{{ message }}</p>
</template>
<script>
import Child from './Child.vue';

export default {
    data(){
        return{
            message:""
        }
    },
    components:{
        Child
    },
    methods:{
        getHandle(data){
            console.log("触发了!",data);
            this.message = data
        }
    }
}
</script>
<style></style>

组件事件配合 v-model 使用

如果是用户输入,我们希望在获取数据的同时发送数据配合 v-model 来使用

在组件A中得到值,在组件B中去显示…

<template>
    <h3>Main</h3>
    <p>搜索内容为:{{ search }}</p>
    <SearchComponent @searchEvent="getSearch"/>
</template>
<script>
import SearchComponent from './SearchComponent.vue';
export default {
    data(){
        return{
            search:""
        }
    },
    components:{
        SearchComponent
    },
    methods:{
        getSearch(data){
            this.search = data
        }
    }
}
</script>
<template>
    搜索:<input type="text" v-model="search">
</template>
<script>
export default {
    data(){
        return{
            search:""
        }
    },
    // 侦听器
    watch:{
        search(newValue,oldValue){
            this.$emit("searchEvent",newValue)
        }
    }
}
</script>

组件数据传递

我们之前讲解过了组件之间的数据传递 props 自定义事件 两种方式

  1. props:父传子
  2. 自定义事件:子传父

出了上述的方案,props 也可以实现子传父

<template>
    <h3>ComponentA</h3>
    <!-- onEvent 里面放函数 -->
    <!-- 父传子可以直接传递,不需要任何出发形式 -->
    <p>父元素:{{ message }}</p>
    <ComponentB title="标题" :onEvent="getName"/>
</template>
<script>
import ComponentB from './ComponentB.vue';
export default {
    data(){
        return{
            message:""
        }
    },
    components:{
        ComponentB
    },
    methods:{
        getName(name){
            console.log(name);
            this.message = name;   
        }
    }
}
</script>
<template>
    <h3>ComponentB</h3>
    <p>{{ title }}</p>
    <p>{{ onEvent("张三") }}</p>
</template>
<script>
export default {
    data() {
        return {

        }
    },
    props: {
        title: String,
        onEvent: Function
    }
}
</script>

个人理解:

父传子,传递了一个函数,而子模块返回了一个数据给了父模块。

这样的感觉有点像:回调函数的感觉,最终有个返回信息到父元素模块。

透传 属性 Attribues

“透传 attribute”指的是传递给一个组件,却没有被该组件声明为 props 或 emits 的 attribute 或者 v-on 事件监听器。最常见的例子就是 class 、style 和 id 当一个组件以单个元素为根作渲染时,透传的 attribute 会自动被添加到根元素上

非重点,使用率很低。

插槽 Slots

我们已经了解到组件能够接收任意类型的 JavaScript 值作为 props,但组件要如何接收模板内容呢? 在某些场景中,我们可能想要为子组件传递一些模板片段,让子组件在它们的组件中渲染这些片段

<script setup>
import Base from "./components/Base.vue"
</script>

<template>
  <Base>
    <h3>标题标签</h3>
    <div>内容元素</div>
  </Base>
</template>
<template>
    <h3>插槽基础知识</h3>
    <slot></slot>
</template>

<script></script>

元素是一个插槽出口(slot outlet),标示了父元素提供的插槽内容 (slot content) 将在哪里被渲染

在这里插入图片描述

渲染作用域

插槽内容可以访问到父组件作用域,因为插槽内容本身是在父组件模板中定义的。

<template>
  <Slot2>
    <h3>{{message}}</h3>
  </Slot2>
</template>

<script>
import Base from "./components/Base.vue"
import Slot2 from "./components/Slot2.vue"
export default {
  data(){
    return{
      message:"续集来自APP父级"
    }
  },
  components:{
    Slot2
  }
}
</script>
<template>
    <h3>Slots2</h3>
    <slot></slot>
</template>
<script></script>

插槽的内容在父元素中定义即可

默认内容

在外部没有提供任何内容的情况下,可以为插槽指定默认内容

<template>
    <h3>Slots2</h3>
    <slot>插槽默认值</slot>
</template>
<script></script>

具名插槽

v-slot: 名字

<template>
  <Slot2>
    <template v-slot:header>
      <h3>{{message}}</h3>
    </template>
    <template v-slot:content>
      <h3>内容</h3>
    </template>
  </Slot2>
</template>

多个插槽可以根据名字描述,具体摆放的位置

<template>
    <h3>Slots2</h3>
    <slot name="header">插槽默认值</slot>
    <hr>
    <slot name="content">插槽默认值</slot>
</template>
<script></script>

简写:

<template>
  <Slot2>
    <template #header>
      <h3>{{message}}</h3>
    </template>
    <template #content>
      <h3>内容</h3>
    </template>
  </Slot2>
</template>

插槽中的数据传递

在某些场景下插槽的内容可能想要同时使用父组件域内和子组件域内的数据。要做到这一点,我们需要一种方法来让子组件在渲染时将一部分数据提供给插槽我们也确实有办法这么做!可以像对组件传递 props 那样,向一个插槽的出口上传递 attributes

<template>
  <Slot3 v-slot="s3">
    <h3>{{ content }} - {{ s3.msg }}</h3>
  </Slot3>
</template>

<script>
import Slot3 from "./components/Slot3.vue"
export default {
  data(){
    return{
      content:"测试内容"
    }
  },
  components:{
    Slot3
  }
}
</script>

子组件中增加 属性,比如::msg,这样都是自定义的

<template>
    <h3>子组件</h3>
    <slot :msg="childMess">
        默认值
    </slot>
    <hr>
</template>
<script>
export default {
    data(){
        return{
            childMess:"子组件数据123"
        }
    }
}
</script>

总结:

插槽中的数据是:父组件的数据和子组件的数据合并到了一起去现实的。

具名插槽添加数据

<template>
<slot3>
      <template #header="s3">
        <h3>{{ content }} - {{ s3.msg }}</h3>
      </template>
      <template #main="s4">
        <p>{{ s4.job }}</p>
      </template>
   </slot3>
</template>
<template>
    <h3>子组件</h3>
    <slot name="header" :msg="childMess"></slot>
    <slot name="main" :job="jobMess"></slot>
</template>
<script>
export default {
    data(){
        return{
            childMess:"子组件数据123",
            jobMess:"具名插槽传递参数"
        }
    }
}
</script>

组件的生命周期

每个 Vue 组件实例在创建时都需要经历一系列的初始化步骤,比如设置好 数据侦听编译模板挂载实例到 DOM,以及在数据改变时更新 DOM。在此过程中,它也会运行被称为生命周期钩子的函数,让开发者有机会在特定阶段运行自己的代码

生命周期示意图

在这里插入图片描述

组件准备被创建:Renderer encounters component (渲染器遇见组件)

  1. 组件创建前:beforeCreate
  2. 组件创建后:created
  3. 组件渲染前:beforeMount
  4. 组件渲染后:mounted
  5. 组件数据更新前:beforeUpdate
  6. 组件数据更新后:updated
  7. 组件卸载前:beforeUnmount
  8. 组件卸载后:unmounted

以上八个重要时刻,都要记下来

<template>
  <h3>组件的生命周期</h3>
  <p>{{ messsage }}</p>
  <button @click="updateHandle">更新数据</button>
</template>

<script>
/**
 *  生命周期函数
 *    创建期:beforeCreate  created
 *    挂载期:beforeMount   mounted
 *    更新期:beforeUpdate  updated
 *    销毁期:beforeUnmount unmounted
 */
export default {
  data() {
    return {
      messsage: "更新之前"
    }
  },
  methods:{
    updateHandle(){
      this.messsage = "更新之后"
    }
  },
  beforeCreate() {
    console.log("组件创建之前");
  },
  created() {
    console.log("组件创建之后");
  },
  beforeMount() {
    console.log("组件渲染之前");
  },
  mounted() {
    console.log("组件渲染之后");
  },
  beforeUpdate() {
    console.log("组件更新之前");
  },
  updated() {
    console.log("组件更新之后");
  },
  beforeUnmount() {
    console.log("组件销毁之前");
  },
  unmounted() {
    console.log("组件销毁之后");
  }
}
</script>

生命周期的应用

组件的生命周期会随着我们对 vue 的了解越多,也会越来越重要,这里我们先进两个堂用的应用常见:

  1. 通过 ref 获取元素DOM结构

  2. 模拟网络请求染数据

通过 ref 获取元素DOM结构

<template>
    <h3>UserComponent</h3>
    <p>组件生命周期应用</p>
    <p ref="name">名字zhangsan</p>
</template>
<script>
// 读取元素的名称,放一个按钮在点击按钮的时候读取到元素信息
export default {
    beforeMount(){ // 挂载期前(渲染前)
        console.log(this.$refs.name);//undefined
    },
    mounted(){
        console.log(this.$refs.name);
    }
}
</script>

必须在适当的时机,在生命周期当中,挂载(渲染)之前无法得到,挂在之后可以得到。

模拟网络请求渲染数据

提示:当页面的内容渲染完成后,这时候才开始加载数据。

<template>
    <h3>UserComponent</h3>
    <p>组件生命周期应用</p>
    <p ref="name">名字zhangsan</p>
    <ul>
        <li v-for="(item,index) of banner" :key="index">
            <h3>{{ item.title }}</h3>
            <p>{{ item.content }}</p>
        </li>
    </ul>
</template>
<script>
// 读取元素的名称,放一个按钮在点击按钮的时候读取到元素信息
export default {
    data(){
        return{
            banner:[]
        }
    },
    beforeMount(){ // 挂载期前(渲染前)
        console.log(this.$refs.name);//undefined
    },
    mounted(){
        console.log(this.$refs.name);
        this.banner = [
            {
                "title":"vue生命周期mounted标题1",
                "content":"文章内容1"
            },
            {
                "title":"vue生命周期mounted标题2",
                "content":"文章内容2"
            },
            {
                "title":"vue生命周期mounted标题3",
                "content":"文章内容3"
            },
            {
                "title":"vue生命周期mounted标题4",
                "content":"文章内容4"
            },
        ]
    }
}
</script>

动态组件

有些场景会需要在两个组件间来回切换,比如Tab界面

A、B 两个组件

<template>
    <h3>ComponentA</h3>
</template>
<template>
  <component :is="tabComponent"></component>
  <button @click="changeHandle">切换组件</button>
</template>

<script>
import ComponentA from "./components/ComponentA.vue";
import ComponentB from "./components/ComponentB.vue";
export default {
  data() {
    return {
      //以字符串的形式赋值
      tabComponent:"ComponentA" 
    }
  },
  methods: {
    changeHandle(){
      this.tabComponent = this.tabComponent == "ComponentA" ? "ComponentB":"ComponentA"
    }
  },
  components: {
    ComponentA,
    ComponentB
  }
}
</script>

总结:

这里 :is=“ ” 相当于 v-bind:is=“ ” ,后面的双引号里面放的是一个变量名称,这个变量名称需要在data中定义一下,然后在下面函数中对这个变量进行判断和再次的赋值操作。

组件保持存活

当使用 来在多个组件间作切换时,被切换掉的组件会被卸载。我们可以通过组件强制被切换掉的组件仍然保持”存活“的状态

组件被卸载

<template>
    <h3>ComponentA</h3>
</template>
<script>
export default {
    beforeUnmount(){
        console.log("组件卸载之前")
    },
    unmounted(){
        console.log("组件卸载之后")
    }
}
</script>

保持存活

<template>
  <KeepAlive>
    <component :is="tabComponent"></component>
  </KeepAlive>
  <button @click="changeHandle">切换组件</button>
</template>

异步组件

在大型项目中,我们可能需要拆分应用为更小的块,并仅在需要时再从服务器加载相关组件。Vue 提供了 defineAsyncComponent 方法来实现比功能

<script>
import UserComponent from "./components/UserComponent.vue"
import ComponentA from "./components/ComponentA.vue";
// import ComponentB from "./components/ComponentB.vue";
// 异步加载组件
import { defineAsyncComponent } from 'vue'
const ComponentB = defineAsyncComponent(() => 
  import("./components/ComponentB.vue")
)
//...
</script>

总结:

异步组件需要的步骤是:

  1. 导入:import { defineAsyncComponent } from ‘vue’
  2. 定义:defineAsyncComponent( () => import(“…”) )

依赖注入

通常情况下,当我们需要从父组件向子组件传递数据时,会使用 props 。想象一下这样的结构: 有一些多层级嵌套的组件,形成了一颗巨大的组件树,而某个深层的子组件需要一个较远的祖先组件中的部分数据。在这种情况下,如果仅使用 props 则必须将其沿着组件链逐级传递下去,这会非常麻烦

在这里插入图片描述

这一问题被称为 ”prop 逐级透传” 一个父组件相对于其所有的后代组件,会作为依赖提供者。任何provide 和 inject 可以帮助我们解决这一问题。后代的组件树,无论层级有多深,都可以注入由父组件提供给整条链路的依赖

在这里插入图片描述

Provide(提供)

要为组件后代提供数据,需要使用 provide 选项

<template>
  <h3>祖宗App</h3>
  <Parent title="祖宗的财产"/>
</template>
<script>
import Parent from './components/Parent.vue';
export default {
  provide:{
    message: "hello!"
  },
  components:{
    Parent
  }
}
</script>

在孙子组件当中接收信息,使用:inject:[“属性”]

<template>
    <h3>Child</h3>
    <p>{{ title }}</p>
    <p>{{ message }}</p>
</template>
<script>
export default {
    inject: ["message"],
    props:{
        title:{
            type:String
        }
    }
}
</script>

也可以读取 data 中的数据

<script>
export default {
  data(){
    return{
      message:"hello123!"
    }
  },
  // provide:{
  //   message: "爷爷的财产"
  // },
  provide(){
    return{
      message : this.message
    }
  },
  components:{
    Parent
  }
}
</script>

注入会在组件自身的状态**之前 **被解析,因此你可以在 data() 中访问注入的属性

<template>
    <h3>Child</h3>
    <p>{{ fullmessage }}</p>
</template>
<script>
export default {
    inject: ["message"],
    data(){
        return {
            // 基于注入值的初始数据
            fullmessage: this.message
        }
    }
}
</script>

注入全局数据

在main.js 当中挂载

const app = createApp(App)
app.provide("golabData","我是全局数据")
app.mount('#app')

提示:

provide 和 inject 只能由上到下的传递,不能反向传递

vue应用

应用实例

每个Vue应用都是通过 createApp 函数创建一个新的应用实例

应用实例:vue的实例对象

import { createApp } from 'vue'

const app = createApp({
	/* 根组件选项 */
}}

根组件

我们传入 createApp 的对象实际上是一个组件,每个应用都需要一个根组件”,其他组件将作为其子组件。

import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)

挂载应用

应用实例必须在调用了 mount0 方法后才会染出来。该方法接收一个“容器”参数,可以是一个实际的 DOM 元素或是一个 CSS 选择器字符串

app.mount('#app')
<div id="app"></div>

这个app的位置就是 index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <link rel="icon" href="/favicon.ico">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Vite App</title>
  </head>
  <body>
    <!-- 浏览器可执行文件:
            1.HTML
            2.CSS
            3.JavaScript
            4.Image
            
            构建工具:Webpack  vite
            最终的内容都会打包成为main.js文件,引入进来
    -->
    <div id="app"></div>
    <script type="module" src="/src/main.js"></script>
  </body>
</html>

公共资源

在 src 目录下的 assets 文件夹的作用就是存放公共资源,例如:图片、公共css或者字体图标等

Vue引入第三方

Swiper 开源、免费、强大的触摸滑动插件

Swiper 是纯javascript打造的滑动特效插件,面向手机、平板电脑等移动终端

Swiper 能实现触屏焦点图、触屏Tab切换、触屏轮播图切换等常用效果

温馨提示

官方文档:https://swiperjs.com/vue

安装指定版本: npm instal --save swiper@8.1.6

基础实现

<template>
  <div class="hello">
    <swiper class="mySwiper">
      <swiper-slide>Slide 1</swiper-slide>
      <swiper-slide>Slide 2</swiper-slide>
      <swiper-slide>Slide 3</swiper-slide>
    </swiper>
  </div>
</template>

<script>
import { Swiper, SwiperSlide } from 'swiper/vue';
import 'swiper/css';

export default {
  name: 'HelloWorld',
  components: {
    Swiper,
    SwiperSlide,
  }
}
</script>

添加指示器

<template>
  <div class="hello">
    <swiper class="mySwiper" :modules="modules" :pagination="{ clickable: true }">
      <swiper-slide>
        <img src="../assets/logo.png" alt="">
      </swiper-slide>
      <swiper-slide>
        <img src="../assets/logo.png" alt="">
      </swiper-slide>
      <swiper-slide>
        <img src="../assets/logo.png" alt="">
      </swiper-slide>
    </swiper>
  </div>
</template>

<script>
import { Pagination } from 'swiper';
import { Swiper, SwiperSlide } from 'swiper/vue';
import 'swiper/css';
import 'swiper/css/pagination';

export default {
  name: 'HelloWorld',
  data(){
    return{
      modules: [ Pagination ]
    }
  },
  components: {
    Swiper,
    SwiperSlide,
  }
}
</script>

Axios网络请求

Axios 是一个基于 promise 的网络请求库

安装

Axios的应用是需要单独安装的 npm install --save axios

引入

组件中引入: import axios from "axios"

全局引用:

import axios from "axios"

const app = createApp(App);
app.config.globalProperties.$axios = axios
app.mount('#app')

// 在组件中调用
this.$axios

网络请求基本示例

get请求
axios({
    method: "get",
    url: "http://iwenwiki.com/api/blueberrypai/getChengpinDetails.php"
}).then(res => {
    console.log(res.data);
})
post请求

温馨提示

post请求参数是需要额外处理的

  1. 安装依赖: npm install --save querystring
  2. 转换参数格式: qs.stringify({})
axios({
    method:"post",
    url:"http://iwenwiki.com/api/blueberrypai/login.php",
    data:qs.stringify({
        user_id:"iwen@qq.com",
        password:"iwen123",
        verification_code:"crfvw"
    })
}).then(res =>{
    console.log(res.data);
})

快捷方案

get请求
axios.get("http://iwenwiki.com/api/blueberrypai/getChengpinDetails.php")
    .then(res =>{
      console.log(res.data);
    })
post请求
axios.post("http://iwenwiki.com/api/blueberrypai/login.php", qs.stringify({
      user_id: "iwen@qq.com",
      password: "iwen123",
      verification_code: "crfvw"
    }))
      .then(res => {
        console.log(res.data);
      })

Axios网络请求封装

在日常应用过程中,一个项目中的网络请求会很多,此时一般采取的方案是将网络请求封装起来

安装axios

cnpm install --save axios

返回以下的信息:

√ Linked 9 latest versions fallback to D:\workspaces\workspace2-vs\14_网络请求封装\vuedemo14\node_modules\.store\node_modules
√ Installed 1 packages on D:\workspaces\workspace2-vs\14_网络请求封装\vuedemo14
√ All packages installed (9 packages installed from npm registry, used 871ms(network 862ms), speed 813.82KB/s, json 9(89.89KB), tarball 611.62KB, manifests cache hit 0, etag hit 0 / miss 0)

dependencies:
+ axios ^1.7.7

安装POST请求querystring

post请求的参数需要转换

cnpm install --save querystring

返回以下的信息:

√ Linked 1 latest versions fallback to D:\workspaces\workspace2-vs\14_网络请求封装\vuedemo14\node_modules\.store\node_modules
deprecate querystring@latest The querystring API is considered Legacy. new code should use the URLSearchParams API instead.
√ Installed 1 packages on D:\workspaces\workspace2-vs\14_网络请求封装\vuedemo14
√ All packages installed (1 packages installed from npm registry, used 300ms(network 298ms), speed 15.08KB/s, json 1(1.16KB), tarball 3.33KB, manifests cache hit 0, etag hit 0 / miss 0)

dependencies:
+ querystring ^0.2.1

启动:

有两种启动方式:

npm run dev

还有一种,目前我没有启动成功,后面再看看怎么回事儿。

npm run serve

request.js

在项目的src文件夹下面创建一个新的文件夹,名字叫做:utils:主要放网络请求的方法

在文件夹下面创建一个文件:request.js

import axios from "axios";
import { error } from "console";
import querystring from "querystring"

//文档参考:https://static.kancloud.cn/cookies_fzx/cookie/1564694

//错误拦截器描述
const errorHandle = (status,info) => {
    switch(status){
        case 400:
            console.log("语义有误");
            break;
        case 401:
            console.log("服务器认证失败");
            break;
        case 403:
            console.log("服务器拒绝访问");
            break;
        case 404:
            console.log("地址错误");
            break;
        case 500:
            console.log("服务器遇到意外");
            break;
        case 502:
            console.log("服务器无响应");
            break;
        default:
            console.log(info);
            break;
    }
}

//创建网络请求对象
const instance = axios.create({
    //网络请求的公共配置
    timeout: 5000
})

//拦截器最常用的
//发送 数据之前
instance.interceptors.request.use(
    // function(){}, 以前的写法
    // function(){}
    config => {
        //因为我们知道post的参数是需要转换的,所以放在拦截器里面
        //优势:在做网络请求的时候就不需要操心格式的问题了
        if(config.methods === "post"){ 
            config.data = querystring.stringify(config.data)
        }
        //config:包含着网络请求的所有信息
        return config;
    },
    error => {
        return Promise.reject(error);
    }
)
//获取 数据之前
instance.interceptors.response.use(
    response => {
        return response.status === 200? Promise.resolve(response):Promise.reject(response)
    },
    error => {
        const { response } = error;
        //错误的处理才是我们需要最关注的
        errorHandle(response.status,response.info)
    }
)

export default instance;

api

src目录下创建文件夹api,并创建文件indexpath分别用来存放网络请求方法和请求路径

网络请求都集中放在api的文件夹里面了

首先创建path.js

// path.js
const base = {
    baseUrl:"http://iwenwiki.com",
    chengpin:"/api/blueberrypai/getChengpinDetails.php"
}

export default base
// index.js
import path from "./path"
import axios from "../utils/request"

const api = {
    //成品详情地址
    getChengpin(){
        return axios.get(path.baseUrl + path.chengpin)
    }
}

export default api

将最终的结果使用在HelloWorld中

<script>
import api from '../api/index';

export default {
  name: "HelloWorld",
  props: {
    msg: String
  },
  mounted(){
    api.getChengpin().then(res =>{
      console.log(res.data);
    })
  }
}
</script>

网络请求跨域解决方案

S采取的是同源策略

同源策略是浏览器的一项安全策略,浏览器只允许js 代码请求和当前所在服务器域名,端口,协议相同的数据接口上的数据,这就是同源策略。

也就是说,当协议、域名、端口任意一个不相同时,都会产生跨域问题,所以又应该如何解决跨域问题呢?

跨域错误提示信息

在这里插入图片描述

目前主流的跨域解决方案有两种:

  1. 后台解决:cors
  2. 前台解决:proxy

在项目中文件:vite.config.js中,增加跨域配置:

url例如:http://iwenwiki.com

devServer: {
    proxy: {
      '/api': {
        target: '<url>',
        changeOrigin: true
      }
    }
}

温馨提示

解决完跨域配置之后,要记得重启服务器才行哦!

在之前的请求当中

将axios的get内容的前缀信息去掉:

axios.get(“/api/FingerUnion/list.php”)

<template>
  <div>
    <h1>{{ msg }}</h1>
    <h3>
      You’ve successfully created a project with
    </h3>
  </div>
</template>
<script>
import axios from "axios"

export default {
  name: "helloworld",
  props:{
    msg:{
      type:String
    }
  },
  //组件渲染之后
  mounted(){
    axios.get("/api/FingerUnion/list.php")
    .then(res => {
      console.log(res.data);
    })
  }
}
</script>
<style scoped>
</style>

Vue引入路由配置

在Vue中,我们可以通过vue-router路由管理页面之间的关系

Vue Router 是 Vue.js 的官方路由。它与 Vue.js 核心深度集成,让用 Vue.js 构建单页应用变得轻而易举。

在Vue中引入路由

第一步:安装路由 npm install --save vue-router

第二步:配置独立的路由文件

import { createRouter, createWebHashHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import AboutView from '../views/AboutView.vue'

//配置信息中需要页面的相关配置
const routes = [
    {
        //配置路径地址
        path:"/",
        //指定到相关的页面   
        component:HomeView  
    },
    {
        path:"/about",
        component:AboutView
    }
]


const router = createRouter({
    history:createWebHashHistory(),
    routes
})
export default router;

第三步:引入路由到项目

//导入router
import router from './router'

//通过.use(router)明确安装router功能
createApp(App).use(router).mount('#app')

第四步:指定路由显示入口 <router-view/>

第五步:指定路由跳转

<template>
    <RouterLink to="/">首页</RouterLink>
    <RouterLink to="/about">关于</RouterLink>
    <RouterView></RouterView>
</template>

VUE学习笔记

VUE项目的创建过程

cmd在指定的目录下面使用指令:

npm init vue@latest

然后出现下面的画面:

> npx
> create-vue


Vue.js - The Progressive JavaScript Framework

√ 请输入项目名称: ... vuedemo1
√ 是否使用 TypeScript 语法? .../ 是
√ 是否启用 JSX 支持? .../ 是
√ 是否引入 Vue Router 进行单页面应用开发? .../ 是
√ 是否引入 Pinia 用于状态管理? .../ 是
√ 是否引入 Vitest 用于单元测试? .../ 是
√ 是否要引入一款端到端(End to End)测试工具? » 不需要
√ 是否引入 ESLint 用于代码质量检测? .../ 是
√ 是否引入 Vue DevTools 7 扩展用于调试? (试验阶段) .../ 是

正在初始化项目 D:\workspaces\workspace2-vs\vuedemo1...

项目初始化完成,可执行以下命令:

  cd vuedemo1
  npm install
  npm run dev

以上的内容都选择no就可以了。

接下来进入到项目中,开始安装项目。

插播一下,需要先安装cnpm,国内的镜像地址,并且设置环境变量。

设置两个文件夹,这两个文件夹的位置就是nodejs的根目录的下面

打开安装根路径

创建两个空文件夹node_cache 、node_global

然后设置环境变量。

img

更换 npm 的 registry 源为国内源

npm install -g cnpm --registry=https://registry.npmmirror.com

安装

C:\Windows\System32>npm install -g cnpm --registry=https://registry.npmmirror.com

added 1 package in 11s
....

C:\Windows\System32>cnpm -v
cnpm@9.4.0 (C:\Users\77572\AppData\Roaming\npm\node_modules\cnpm\lib\parse_argv.js)
....
registry=https://registry.npmmirror.com

cnpm的设置成功。

安装项目,执行代码:

初始化

npm init vue@latest

安装

cnpm install

运行项目:

npm run dev

访问:http://localhost:5173/

运行vscode

打开项目,注意在插件中心中安装一个插件:Volar扩展,这样可以让你的文档内容高亮显示。

项目的目录结构:

.vscode			VSCode工具的配置文件
node_moudules	Vue项目的运行依赖文件夹
public			资源文件夹
src				源码文件夹
.gitignore		git忽略文件
index.html		入口HTML文件
jsconfig.json	为项目提供更好的编译支持:智能提示、代码导航、重构
package.json	信息描述文件
README.md		注释文件
vite.config.js	vue的配置文件

遇见问题:

  1. 无法创建项目

  2. 检查网络连接:确保你的计算机可以正常访问互联网。

  3. 检查镜像源地址:确认你使用的镜像源地址是正确的,并且该镜像服务当前是可用的。

  4. 清除npm缓存:运行npm cache clean --force清除npm缓存后再尝试。

  5. 更换npm镜像源:可以尝试更换回官方npm源。运行

    1. npm config set registry https://registry.npmmirror.com
      

模板语法

  1. 清空所有的components文件夹下的内容,
  2. 在main.js当中,删除:import ‘./assets/main.css’
  3. 在App.vue中只留下template和script即可。

从空的内容开始写起来:

<template>
   <h3>模板语法</h3>
   <p>{{ msg }}</p>
</template>

<script>

export default {
  data(){
    return{
      msg:"语法1234"
    }
  }
}
</script>

记住凡是有return结果的,都可以使用两个大括号。

<template>
   <h3>模板语法</h3>
   <p>{{ msg }}</p>
   <p>{{ num +1 }}</p>
   <p>{{ ok?'yes':'no' }}</p>
   <p>{{ message.split('').reverse().join('') }}</p>
   <p>{{ rawHTML }}</p>
   <p v-html="rawHTML"></p><!-- 可以实现html原始内容输出 -->
</template>

<script>

export default {
  data(){
    return{
      msg:"语法1234",
      num:10,
      ok:false,
      message:"大家好我是张三",
      rawHTML:"<a href='http://www.baidu.com'>百度一下</a>"
    }
  }
}
</script>

属性绑定

v-bind:xxx

比如:v-bind:id=“xxx”,v-bin:class=“xxxx”

<template>
  <!-- <div class="{{ msg }}">{{ msg }}</div> -->
  <div v-bind:class="msg" v-bind:id="dymainId">{{ msg }}</div>
</template>

<script>
export default {
  data(){
    return{
      msg:"helloworld",
      dymainId:"appId"
    }
  }
}

</script>
<style>
.helloworld{
  color: red;
}
</style>

简写方案:

:bind:xxx

可以是布尔型的

<button :disabled="isButtonDis">按钮点击</button>
export default {
  data(){
    return{
      isButtonDis:true
    }
  }
}

动态的绑定多个值:

<div v-bind="objectAttrs">111</div>
export default {
  data(){
    return{
      objectAttrs: {
          class: "dtid",
          id: "dtClass"
      }
    }
  }
}

条件渲染

在vue中,提供了条件渲染,这类似于JavaScript中的条件语句

  • v-if
  • v-else
  • v-else-if
  • v-show

v-if

v-if 指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回真值时才被渲染

<template>
    <h3>条件渲染</h3>
    <div v-if="flag">您能看见我吗</div>
</template>
<script>
export default {
    data(){
        return{
            flag:true
        }
    }
}
</script>

v-else

你也可以使用 v-else 为 v-if 添加一个“else 区块”

<template>
    <h3>条件渲染</h3>
    <div v-if="flag">您能看见我吗</div>
    <div v-else="flag">那你一定能看到else</div>
</template>
<script>
export default {
    data(){
        return{
            flag:false
        }
    }
}
</script>

v-esle-if

顾名思义,V-else-if 提供的是相应于 v-if 的”else if 区块”。它可以连续多次重复使用

<template>
    <h3>条件渲染</h3>
    <div v-if="flag">您能看见我吗</div>
    <div v-else="flag">那你一定能看到else</div>
    <div v-if="type=='A'">A</div>
    <div v-else-if="type=='B'">B</div>
    <div v-else-if="type=='C'">C</div>
    <div v-else="type=='D'">not ABC</div>
</template>
<script>
export default {
    data(){
        return{
            flag:false,
            type: "B"
        }
    }
}
</script>

v-show

另一个可以用来按条件显示一个元素的指令是 v-show 。其用法基本一样

<template>
    <h3>条件渲染</h3>
    <div v-show="!flag">您能看见show吗?</div>
</template>
<script>
export default {
    data(){
        return{
            flag:false
        }
    }
}
</script> 

v-if VS v-show

vif 是“真实的”按条件染,因为它确保了在切换时,条件区块内的事件监听器和子组件都会被销毁与重建。

vif 也是情性的:如果在初次染时条件值为 false,则不会做任何事。条件区块只有当条件首次变为 true 时才被渲染。

相比之下,v-show 简单许多,元素无论初始条件如何,始终会被染,只有 CSS display 属性会被切换.

总的来说,vif 有更高的切换开销,而 show 有更高的初始染开销。因此,如果需要频繁切换,则使用show 较好;如果在运行时绑定条件很少改变,则 v-if 会更合适

列表渲染

我们可以使用 v-for 指令基于一个数组来染一个列表形式的特殊语法v-for 指令的值需要使用 item in items其中 items 是源数据的数组,而 item 是选代项的别名

<template>
    <h3>列表渲染</h3>
    <p v-for="item in names">{{ item }}</p>
</template>
<script>
export default {
    data(){
        return{
            names:["apple","boy","cat"]
        }
    }
}
</script>

复杂数据

大多数情况,我们渲染的数据源来源于网络请求,也就是 JSON 格式

<template>
    <h3>列表渲染</h3>
    <p v-for="item in names">{{ item }}</p>
    <div v-for="item in result">
        <p>{{ item.title }}</p>
        <img :src="item.avator" :alt="item.id">
    </div>
</template>
<script>
export default {
    data() {
        return {
            names: ["apple", "boy", "cat"],
            result: [{
                "id": 2261677,
                "title": "鄂尔多斯| 感受一座城市的璀璨夜景 感受一座城市,除了白日里的车水马龙,喧嚣繁华之",
                "avator": "https://pic.qyer.com/avatar/002/25/77/30/200?v=156226451",
            },
            {
                "id": 2261566,
                "title": "成都这家洞穴暗黑风咖啡厅酷毙了!早C晚A走起成都天气这么热 咖啡头人必",
                "avator": "https://pic.qyer.com/avatar/011/07/08/69/200?v=1572185180"
            },
            {
                "id": 2261662,
                "title": "[川西新龙-措卡湖] 措卡湖,意为“乱石从中的黑色海水”,神秘小众 原汁原味。",
                "avator": "https://pic.qyer.com/avatar/009/88/48/58/200?v=1507386782"
            }
            ]
        }
    }
}
</script>

v-for 也支持使用可选的第二个参数表示当前项的位置索引

<p v-for="(item,index) in names">{{ item }}-{{ index }}</p>

你也可以使用 of 作为分隔符来替代 in 这更接近JavaScript 的选代器语法

<p v-for="(item,index) of names">{{ item }} - {{ index }}</p>

v-fo 与对象

你可以使用 v-for 来遍历一个对象的所有属性

<template>
    <h3>列表渲染</h3>
    <div>
        <p v-for="(item,key,index) of userInfo">{{ item }} - {{ key }} - {{ index }}</p>
    </div>
</template>
<script>
export default {
    data() {
        return {
            userInfo:{
                name: "小明",
                age: 20,
                sex: "男"
            }
        }
    }
}
</script>

结果显示:

小明 - name - 0
20 - age - 1
男 - sex - 2

通过key管理状态

Vue 默认按照"就地更新”的策略来更新通过 or 染的元素列表。当数据项的顺序改变时,Vue 不会随之移动 DOM 元素的顺序,而是就地更新每个元素,确保它们在原本指定的索引位置上渲染。

为了给 Vue 一个提示,以便它可以跟踪每个节点的标识,从而重用和重新排序现有的元素,你需要为每个元素对应的块提供一个唯一的 key attribute:

<template>
    <h3>Key属性添加到v-for当中</h3>
    <p v-for="(item,index) in names" :key="index">{{ item }}</p>
</template>
<script>
export default {
    data(){
        return{
            names:["xiaomi","huawei","vivo","oppo"]
        }
    }
}
</script>

key的来源

请不要使用index作为key的值,我们要确保每一条数据的唯一索引不会发生变化

<template>
    <h3>Key属性添加到v-for当中</h3>
    <p v-for="(item,index) in names" :key="index">{{ item }}</p>
    <div v-for="item in result" :key="item.id">
        <p>{{ item.title }}</p>
        <img :src="item.avator">
    </div>
</template>
<script>
export default {
    data(){
        return{
            names:["xiaomi","huawei","vivo","oppo"],
            result: [{
                "id": 2261677,
                "title": "鄂尔多斯| 感受一座城市的璀璨夜景 感受一座城市,除了白日里的车水马龙,喧嚣繁华之",
                "avator": "https://pic.qyer.com/avatar/002/25/77/30/200?v=156226451",
            },
            {
                "id": 2261566,
                "title": "成都这家洞穴暗黑风咖啡厅酷毙了!早C晚A走起成都天气这么热 咖啡头人必",
                "avator": "https://pic.qyer.com/avatar/011/07/08/69/200?v=1572185180"
            },
            {
                "id": 2261662,
                "title": "[川西新龙-措卡湖] 措卡湖,意为“乱石从中的黑色海水”,神秘小众 原汁原味。",
                "avator": "https://pic.qyer.com/avatar/009/88/48/58/200?v=1507386782"
            }
            ]
        }
    }
}
</script>

事件处理

我们可以使用 v-on 指令(简写为 @)来监听 DOM 事件,并在事件触发时执行对应的 JavaScript。用法:

v-on:click=“methodName” 或 @click="handler

事件处理器的值可以是

  1. 内联事件处理器: 事件被触发时执行的内联JavaScript 语句(与 onlick 类似)

  2. 方法事件处理器:一个指向组件上定义的方法的属性名或是路径

内联事件处理器

内联事件处理器通常用于简单场景

<template>
    <h3>内联事件处理器</h3>
    <button @click="count++">Add</button>
    <p>{{ count }}</p>
</template>
<script>
export default {
    data(){
        return{
            count:0
        }
    }
}
</script>

方法事件处理器

<template>
    <h3>内联事件处理器2</h3>
    <!-- 放入的是一个事件 -->
    <button @click="addCount">Add</button>
    <p>{{ count }}</p>
</template>
<script>
export default {
    data(){
        return{
            count:0
        }
    },
    //所有的函数都放在这里
    methods:{
        addCount(){
            this.count++
        }
    }
}
</script>

事件参数

事件参数可以获取 event 对象和通过事件传递数据

获取event对象

<template>
    <h3>内联事件处理器3</h3>
    <!-- 放入的是一个事件 -->
    <button @click="addCount">Add</button>
    <p>{{ count }}</p>
</template>
<script>
export default {
    data(){
        return{
            count:0
        }
    },
    //所有的函数都放在这里
    methods:{
        addCount(e){
            //Vue中的event对象,就是原生JS的Event对象
            e.target.innerHTML="Add"+this.count;
            console.log(e.target);
            this.count++
        }
    }
}
</script>

传递参数

<template>
    <h3>事件传参</h3>
    <p @click="getNameHandler(item)" v-for="(item,index) of names" :key="index">{{ item }}</p>
</template>
<script>
export default {
    data(){
        return{
            names:["Jack","Tom","Lucy"]
        }
    },
    //所有的函数都放在这里
    methods:{
        getNameHandler(name){
            console.log(name);
        }
    }
}
</script>
<style>
    p{
        cursor: pointer;
    }
</style>

传递参数过程中获取event对象

<template>
    <h3>事件传参</h3>
    <p @click="getNameHandler(item,$event)" v-for="(item,index) of names" :key="index">{{ item }}</p>
</template>
<script>
export default {
    data(){
        return{
            names:["Jack","Tom","Lucy"]
        }
    },
    //所有的函数都放在这里
    methods:{
        getNameHandler(name,e){
            console.log(name);
            console.log(e);
        }
    }
}
</script>

可以传递event对象,但是一定是最后面。使用的语法是:$event

事件修饰符

如处理事件时调用event.prevenDefault() 或 event.stopPropagation()是很常见的。尽管我们可以直接在方法内调用,但如果方法能更专注于数据逻辑而不用去处理DOM事件的细节会更好。

为解决这一问题,Vue为v-on提供了时间修饰符,常用有以下几个:

  1. .stop
  2. .prevent
  3. .once
  4. .enter
具体参考地址:
地址:https://cn.vuejs.org/guide/essentials/event-handling#event-modifiers

阻止默认事件

@click.prevent 代替了 e.preventDefault(); 的行为操作

<template>
    <h3>事件修饰符</h3>
    <a href="http://www.baidu.com" @click.prevent="clickHandle">百度一下</a>
</template>
<script>
export default {
    data(){
        return{

        }
    },
    methods:{
        clickHandle(e){
            //阻止了默认事件
            //e.preventDefault();
            console.log("点击了");
        }
    }
}
</script>

阻止事件冒泡

<template>
    <h3>事件修饰符</h3>
    <a href="http://www.baidu.com" @click.prevent="clickHandle">百度一下</a>
    <div @click="clickDiv">
        <p @click.stop="clickP">测试冒泡</p>
    </div>
</template>
<script>
export default {
    data(){
        return{

        }
    },
    methods:{
        clickHandle(e){
            //阻止了默认事件
            //e.preventDefault();
            console.log("点击了");
        },
        clickDiv(){
            console.log("点击了DIV");
        },
        clickP(e){
            //阻止事件冒泡(点击子元素不会触发父元素)
            //e.stopPropagation();
            console.log("点击了P");
        }
    }
}
</script>

数组侦测

变更方法

Vue 能够侦听响应式数组的变更方法,并在它们被调用时触发相关的更新。这些变更方法包括:

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • short()
  • reverse()
<template>
    <h3>数组变化侦听</h3>
    <button v-on:click="addListHandle">添加数据</button>
    <ul>
        <li v-for="(item,index) of names" :key="index">{{ item }}</li>
    </ul>
</template>
<script>
export default {
    data(){
        return{
            names:["xiaomi","huawei","vivo","oppo"]
        }
    },
    methods:{
        addListHandle(){
            // 引起UI自动更新
            this.names.push("rongyao");
        }
    }
}
</script>

替换一个数组

变更方法,顾名思义,就是会对调用它们的原数组进行变更。相对地,也有一些不可变 (immutable) 方法,例如 filter(),concat0 和 sice(),这些都不会更改原数组,而总是返回一个新数组。当遇到的是非变更方法时,我们需要将旧的数组替换为新的。

<template>
    <h3>数组变化侦听</h3>
    <button v-on:click="addListHandle">添加数据</button>
    <ul>
        <li v-for="(item,index) of names" :key="index">{{ item }}</li>
    </ul>
</template>
<script>
export default {
    data(){
        return{
            names:["xiaomi","huawei","vivo","oppo"]
        }
    },
    methods:{
        addListHandle(){
            // 引起UI自动更新
            //this.names.push("rongyao");
            //不会更新UI
            this.names.concat("rongyao");
            console.log(this.names.concat("rongyao"));
            //如果想要讲UI更新的话
            this.names = this.names.concat("rongyao");
        }
    }
}
</script>
<template>
    <h3>数组变化侦听</h3>
    <button @click="countHandle">合并数组,都将数组合并到数组1当中</button>
    <h3>数组1</h3>
    <p v-for="(item,index) of numbers1" :key="index">{{ item }}</p>
    <h3>数组2</h3>
    <p v-for="(item,index) of numbers2" :key="index">{{ item }}</p>
</template>
<script>
export default {
    data(){
        return{
            numbers1:[1,2,3,4,5,6],
            numbers2:[7,8,9]
        }
    },
    methods:{
        countHandle(){
            this.numbers1 = this.numbers1.concat(this.numbers2);
        }
    }
}
</script>

计算属性

模板中的表达式虽然方便,但也只能用来做简单的操作。如果在模板中写太多逻辑,会让模板变得臃肿,难以维护。因此我们推荐使用计算属性来描述依赖响应式状态的复杂逻辑

<template>
    <h3>{{ object1.name }}</h3>
    <h3>{{ calc }}</h3>
</template>
<script>
export default {
    data(){
        return{
            object1:{
                name:"jack",
                hobby:["java","js","html","mysql"]
            }
        }
    },
    //计算属性
    computed:{
        calc(){
            return this.object1.hobby.length>0?"yes":"no"
        }
    }
}
</script>

计算属性缓存 vs 方法

<template>
    <h3>{{ object1.name }}</h3>
    <h3>{{ calc }}</h3>
    <h3>{{ calc2() }}</h3>
</template>
<script>
export default {
    data(){
        return{
            object1:{
                name:"jack",
                hobby:["java","js","html","mysql"]
            }
        }
    },
    //计算属性
    computed:{
        calc(){
            return this.object1.hobby.length>0?"yes":"no"
        }
    },
    // 使用函数完成
    methods:{
        calc2(){
            return this.object1.hobby.length>0?"yes":"no"
        }
    }
}
</script>

重点区别:

计算属性: 计算属性值会基于其响应式依赖被缓存一个计算属性仅会在其响应式依赖更新时才重新计算

方法: 方法调用总是会在重染发生时再次执行函数。

总结:将负责的属性提取到计算属性中实现,而不会直接在模板语法中实现。

Class绑定

数据绑定的一个常见需求场景是操纵元素的 CSS class 列表,因为 class 是 attribute,我们可以和其他attribute 一样使用 v-bind 将它们和动态的字符串绑定。但是,在处理比较复杂的绑定时,通过拼接生成字符串是麻烦且易出错的。因此,Vue 专门为 class 的 v-bind 用法提供了特殊的功能增强。除了字符串外,表达式的值也可以是对象或数组

<template>
    <p :class="{'c1':isActive,'c2':hasError}">Class样式的绑定</p>
    <p>Class样式的绑定</p>
</template>
<script>
export default {
    data(){
        return{
            isActive:true,
            hasError:true
        }
    }
}
</script>
<style>
.c1{
    font-size: 32px;
}
.c2{
    color:red;
}
</style>

多个对象绑定

<template>
    <p :class="{'c1':isActive,'c2':hasError}">Class样式的绑定</p>
    <p :class="classObject">Class样式的绑定</p>
</template>
<script>
export default {
    data(){
        return{
            isActive:true,
            hasError:true,
            classObject:{
                'c1':true,
                'c2':true
            }
        }
    }
}
</script>
<style>
.c1{
    font-size: 32px;
}
.c2{
    color:red;
}
</style>

数组绑定

详情见API

Class 与 Style 绑定 | Vue.js (vuejs.org)

Style绑定

数据绑定的一个常见需求场景是操纵元素的 CSS style列表,因为 style 是 attribute,我们可以和其他attribute 一样使用 bind 将它们和动态的字符串绑定。但是,在处理比较复杂的绑定时,通过拼接生成字符串是麻烦且易出错的。因此,Vue 专门为 style 的 v-bind 用法提供了特殊的功能增强。除了字符串外,表达式的值也可以是对象数组

绑定对象

<template>
    <h3>Style绑定</h3>
    <p :style="{color:activeColor,fontSize:fColor+'px'}">这是我样式所绑定的内容信息</p>
</template>
<script>
export default {
    data() {
        return {
            activeColor: 'red',
            fColor: 30
        }
    }
}
</script>
<template>
    <h3>Style绑定</h3>
    <p :style="{color:activeColor,fontSize:fColor+'px'}">这是我样式所绑定的内容信息</p>
    <p :style="styleObject">这是我样式所绑定的内容信息2</p>
</template>
<script>
export default {
    data() {
        return {
            activeColor: 'red',
            fColor: 30,
            styleObject:{
                color:"red",
                fontSize:'32px'
            }
        }
    }
}
</script>

侦听器

我们可以使用 watch 选项在每次相应式属性发生变化时触发一个函数

<template>
    <h3>侦听器</h3>
    <button @click="updateHandle">修改数据</button>
    <p>{{ message }}</p>
</template>
<script>

export default {

    data(){

        return{
            message:"hello"
        }
    },
    methods:{
        updateHandle(){
            this.message = "world"
        }
    },
    // 侦听器
    watch:{
        // newObj:改变之后的数据
        // oldObj:改变之前的数据
        // 函数名必须与侦听的数据对象保持一致
        message(newObj,oldObj){
            // 数据发生变化,自动执行的函数
            console.log(newObj,oldObj);
        }
    }
}

</script>

表单的输入绑定

在前端处理表单时,我们常常需要将表单输入框的内容同步给 JavaScript 中相应的变量。手动连接值绑定和更改事件监听器可能会很麻烦,v-model 指令帮我们简化了这一步骤

<template>
    <h3>表单输入绑定</h3>
    <form>
        <input type="text" v-model="message">
        <p>{{ message }}</p>
    </form>
</template>
<script>

export default {

    data() {
        return {
            message:""
        }
    }
}
</script>

复选框

单一的复选框,绑定布尔类型值

<template>
    <h3>表单输入绑定</h3>
    <form>
        <input type="checkbox" v-model="checked"/>是否选中
        <p>{{ checked }}</p>
    </form>
</template>
<script>

export default {
    data() {
        return {
            checked:false
        }
    }
}
</script>

修饰符

v-model 也提供了修饰符:.lazy、.number、.trim、.lazy

.number只获取数字,.trim去掉前后的空格

默认情况下,vmode 会在每次 inut 事件后更新数据。你可以添加 zy 修饰符来改为在每次 change 事件后更新数据

<template>
    <h3>表单输入绑定</h3>
    <form>
        <input type="text" v-model="message"><br>
        <input type="text" v-model.lazy="message">
        <p>{{ message }}</p>
        <input type="checkbox" id="checkbox" v-model="checked"/>是否选中
        <label for="checkbox">{{ checked }}</label>
    </form>
</template>
<script>

export default {

    data() {
        return {
            message:"",
            checked:false
        }
    }
}
</script>

模板引用

虽然 Vue 的声明性渲染模型为你抽象了大部分对 DOM 的直接操作,但在某些情况下,我们仍然需要直接访问底层 DOM 元素。要实现这一点,我们可以使用特殊的 ref attribute 挂载结束后引用都会被暴露在 this.$refs 之上

<template>
    <div ref="container" claas="container">{{content}}</div>
    <input type="text" ref="username"/>
    <button @click="getElementHandle">获取元素</button>
</template>
<script>
/**
 * 内容改变:{{ 模板语法 }}
 * 属性改变:v-bind:指令
 * 事件:v-on:click
 * 
 * 如果没有特别的需求,不要操作DOM
 */
export default {
    data() {
        return {
            content:"内容"
        }
    },
    methods:{
        getElementHandle(){
            // innerHTML:原生JS的属性
            console.log(this.$refs.container.innerHTML="newValue");
            console.log(this.$refs.username.value);
        }
    }
}

</script>

其实就是读取DOM,建议:如果没有特别的需求,不要操作DOM。

组件组成

组件最大的优势就是可复用性当使用构建步骤时,我们一般会将 Vue 组件定义在一个单独的 .vue 文件中,这被叫做单文件组件(简称 SFC)

组件组成结构

<template>
<!-- 包含所有的HTML -->
</template>
<script>
//包含所有的逻辑
export default {}
</script>
<style>
/**包含了所有的样式*/
</style>

组件的引用

<template>
    <!-- 第三步:显示组件 -->
     <MyComponent/>
</template>

<script>
// 第一步:引入组件
import MyComponent from './components/MyComponent.vue'

export default {
  // 第二步:注入组件
  components:{
    MyComponent
  }
}
</script>
<style scoped>
</style>

style scoped 这里 scoped 的作用:让当前样式只在当前组件中生效

组件嵌套关系

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

组件允许我们将UI 划分为独立的、可重用的部分,并目可以对每个部分进行单独的思考。在实际应用中,组件常常被组织成层层嵌套的树状结构这和我们嵌套 HTML 元素的方式类似,Vue 实现了自己的组件模型,使我们可以在每个组件内封装自定义内容与逻辑

创建组件及引用关系

Header
<template>
    <h3>Header</h3>
</template>
<style scoped>
h3{
    width: 100%;
    height: 100px;
    border: 5px solid #999;
    text-align: center;
    line-height: 100px;
    box-sizing: border-box;
}
</style>
Main
<template>
    <div class="main">
        <h3>Main</h3>
        <Article />
        <Article />
    </div>
</template>
<script>
import Article from './Article.vue';
export default {
    components: {
        Article
    }
}
</script>
<style scoped>
.main {
    float: left;
    width: 70%;
    height: 400px;
    border: 5px solid #999;
    box-sizing: border-box;
}
</style>
Aside
<template>
    <div class="aside">
        <h3>Aside</h3>
        <Item />
        <Item />
        <Item />
    </div>
</template>
<script>
import Item from './Item.vue';
export default {
    components: {
        Item
    }

}
</script>
<style scoped>
.aside {
    float: left;
    width: 30%;
    height: 400px;
    border: 5px solid #999;
    box-sizing: border-box;
}
</style>
Article
<template>
    <h3>Aritcle</h3>
</template>
<style scoped>
h3{
    width: 80%;
    margin: 0 auto;
    text-align: center;
    line-height: 100px;
    box-sizing: border-box;
    margin-top: 50px;
    background: #999;
}
</style>
Item
<template>
    <h3>Item</h3>
</template>
<style scoped>
h3{
   width: 80%;
   margin: 0 auto;
   text-align: center; 
   line-height: 80px;
   box-sizing: border-box;
   margin-top: 25px;
   background: #999;
}
</style>

组件的注册方式

个 Vue 组件在使用前需要先被”注册”,这样 Vue 才能在染模板时找到其对应的实现。组件注册有两种方式:全局注册和局部注册

全局注册

在最外面注册一次,在任意组件中都可以引用。

main.js 当中注册

import { createApp } from 'vue'
import App from './App.vue'
import Header from './pages/Header.vue'

const app = createApp(App)

// 在这中间写组件的注册
app.component("Header",Header);

app.mount("#app")

局部注册

意思是在某一个组件中引用和注册。

全局注册虽然很方便,但有以下几个问题:

  1. 全局注册,但并没有被使用的组件无法在生产打包时被自动移除(也叫“tree-shaking”)。如果你全局注册了一个组件,即使它并没有被实际使用,它仍然会出现在打包后的JS文件中
  2. 全局注册在大型项目中使项目的依赖关系变得不那么明确。在父组件中使用子组件时,不太容易定位子组件的实现。和使用过多的全局变量一样,这可能会影响应用长期的可维护性

局部注册需要使用 components 选项

组件传递数据 Props

组件与组件之间不是完全独立的,而是有交集的,那就是组件与组件之间是可以传递数据的传递数据的解决方案就是 props

<template>
    <h3>Parent</h3>
    <Child title="Parent的数据" testdemo="测试的文本"/>
</template>
<script>
import Child from './Child.vue';
export default {
    components:{
        Child
    }
}
</script>

子组件显示 title

<template>
    <h3>Child</h3>
    <p>{{ title }}</p>
    <p>{{ testdemo }}</p>
</template>
<script>
export default {
    props:["title","testdemo"]
}
</script>

动态数据传递

<template>
    <h3>Parent</h3>
    <Child :title="message" testdemo="测试的文本"/>
</template>
<script>
import Child from './Child.vue';
export default {
    data(){
        return{
            message:"动态的信息数据"
        }
    },
    components:{
        Child
    }
}
</script>

注意事项:

props传递数据,只能从父级传递到子级,不能反其道而行

组件传递多种数据类型

通过 props 传递数据,不仅可以传递字符串类型的数据,还可以是其他类型,例如: 数字、对象、数组等;但实际上任何类型的值都可以作为 props 的值被传递

Number

<template>
    <h3>Parent</h3>
    <Child :title="message" testdemo="测试的文本" :age="age"/>
</template>
<script>
import Child from './Child.vue';
export default {
    data(){
        return{
            message:"动态的信息数据",
            age:20
        }
    },
    components:{
        Child
    }
}
</script>

数组类型

<template>
    <h3>Parent</h3>
    <Child :title="message" testdemo="测试的文本" :age="age" :names="names"/>
</template>
<script>
import Child from './Child.vue';
export default {
    data(){
        return{
            message:"动态的信息数据",
            age:20,
            names:["huawei","rongyao","vivo","oppo"]
        }
    },
    components:{
        Child
    }
}
</script>
<template>
    <h3>Child</h3>
    <p>{{ title }}</p>
    <p>{{ testdemo }}</p>
    <p>{{ age }}</p>
    <p>{{ names }}</p>
    <ul>
        <li v-for="(item,index) of names" :key="index">{{ item }}</li>
    </ul>
</template>
<script>
export default {
    props:["title","testdemo","age","names"]
}
</script>

对象类型

<template>
    <h3>Parent</h3>
    <Child :userInfo="userInfo"/>
</template>
<script>
import Child from './Child.vue';
export default {
    data(){
        return{
            userInfo:{
                name:"zhangsan",
                age:20
            }
        }
    },
    components:{
        Child
    }
}
</script>
<template>
    <h3>Child</h3>
    <p>{{ userInfo.name }} - {{ userInfo.age }}</p>
</template>
<script>
export default {
    props:["userInfo"]
}
</script>

组件传递数据Props效验

Vue组件可以更细致地声明对传入的props的校验要求

验证的意思,具体指定什么类型

校验类型

<template>
    <h3>ComponentA</h3>
    <ComponentB :title="title"/>
</template>
<script>
import ComponentB from './ComponentB.vue';
export default {
    data(){
        return{
            title:"字符串信息123"
        }
    },
    components:{
        ComponentB
    }   
}
</script>
<template>
    <h3>ComponentB</h3>
    <p>{{ title }}</p>
</template>
<script>
export default {
    //props:["title"]
    props: {
        title: {
            //type:String
            type: [Number, String, Array, Object]
        }
    }
}
</script>

默认值

<template>
    <h3>ComponentB</h3>
    <p>{{ title }}</p>
    <p>{{ age }}</p>
    <ul>
        <li v-for="(item,index) of names">{{ item }}</li>
    </ul>
</template>
<script>
export default {
    //props:["title"]
    props: {
        title: {
            //type:String
            type: [Number, String, Array, Object]
        },
        age: {
            type: Number,
            default: 0 //默认值
        },
        // 数字和字符串可以直接default,但是如果是数组和对象,必须通过工厂函数返回默认值
        names: {
            type: Array,
            default(){
                return ["空"]
            }
        }
    }
}
</script>

必选项

<template>
    <h3>ComponentB</h3>
    <p>{{ title }}</p>
</template>
<script>
export default {
    props: {
        title: {
            type:String
            required:true
        }
    }
}
</script>

组件事件

在组件的模板表达式中,可以直接使用 $emit 方法触发自定义事件,触发自定义事件的目的是组件之间传递数据

就是子组件给父组件传值的过程

<template>
    <h3>子组件</h3>
    <button @click="clickEventHandle">子组件的按钮</button>
</template>
<script>
export default {
    data(){
        return{     
            msg:"子元素的msg"
        }
    },
    methods:{
        clickEventHandle(){
            // 自定义事件
            this.$emit("someEvent",this.msg)
        }
    }
}
</script>
<style>

</style>
<template>
    <h3>组件事件</h3>
    <Child @someEvent="getHandle"/>
    <p>父元素:{{ message }}</p>
</template>
<script>
import Child from './Child.vue';

export default {
    data(){
        return{
            message:""
        }
    },
    components:{
        Child
    },
    methods:{
        getHandle(data){
            console.log("触发了!",data);
            this.message = data
        }
    }
}
</script>
<style></style>

组件事件配合 v-model 使用

如果是用户输入,我们希望在获取数据的同时发送数据配合 v-model 来使用

在组件A中得到值,在组件B中去显示…

<template>
    <h3>Main</h3>
    <p>搜索内容为:{{ search }}</p>
    <SearchComponent @searchEvent="getSearch"/>
</template>
<script>
import SearchComponent from './SearchComponent.vue';
export default {
    data(){
        return{
            search:""
        }
    },
    components:{
        SearchComponent
    },
    methods:{
        getSearch(data){
            this.search = data
        }
    }
}
</script>
<template>
    搜索:<input type="text" v-model="search">
</template>
<script>
export default {
    data(){
        return{
            search:""
        }
    },
    // 侦听器
    watch:{
        search(newValue,oldValue){
            this.$emit("searchEvent",newValue)
        }
    }
}
</script>

组件数据传递

我们之前讲解过了组件之间的数据传递 props 自定义事件 两种方式

  1. props:父传子
  2. 自定义事件:子传父

出了上述的方案,props 也可以实现子传父

<template>
    <h3>ComponentA</h3>
    <!-- onEvent 里面放函数 -->
    <!-- 父传子可以直接传递,不需要任何出发形式 -->
    <p>父元素:{{ message }}</p>
    <ComponentB title="标题" :onEvent="getName"/>
</template>
<script>
import ComponentB from './ComponentB.vue';
export default {
    data(){
        return{
            message:""
        }
    },
    components:{
        ComponentB
    },
    methods:{
        getName(name){
            console.log(name);
            this.message = name;   
        }
    }
}
</script>
<template>
    <h3>ComponentB</h3>
    <p>{{ title }}</p>
    <p>{{ onEvent("张三") }}</p>
</template>
<script>
export default {
    data() {
        return {

        }
    },
    props: {
        title: String,
        onEvent: Function
    }
}
</script>

个人理解:

父传子,传递了一个函数,而子模块返回了一个数据给了父模块。

这样的感觉有点像:回调函数的感觉,最终有个返回信息到父元素模块。

透传 属性 Attribues

“透传 attribute”指的是传递给一个组件,却没有被该组件声明为 props 或 emits 的 attribute 或者 v-on 事件监听器。最常见的例子就是 class 、style 和 id 当一个组件以单个元素为根作渲染时,透传的 attribute 会自动被添加到根元素上

非重点,使用率很低。

插槽 Slots

我们已经了解到组件能够接收任意类型的 JavaScript 值作为 props,但组件要如何接收模板内容呢? 在某些场景中,我们可能想要为子组件传递一些模板片段,让子组件在它们的组件中渲染这些片段

<script setup>
import Base from "./components/Base.vue"
</script>

<template>
  <Base>
    <h3>标题标签</h3>
    <div>内容元素</div>
  </Base>
</template>
<template>
    <h3>插槽基础知识</h3>
    <slot></slot>
</template>

<script></script>

元素是一个插槽出口(slot outlet),标示了父元素提供的插槽内容 (slot content) 将在哪里被渲染

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

渲染作用域

插槽内容可以访问到父组件作用域,因为插槽内容本身是在父组件模板中定义的。

<template>
  <Slot2>
    <h3>{{message}}</h3>
  </Slot2>
</template>

<script>
import Base from "./components/Base.vue"
import Slot2 from "./components/Slot2.vue"
export default {
  data(){
    return{
      message:"续集来自APP父级"
    }
  },
  components:{
    Slot2
  }
}
</script>
<template>
    <h3>Slots2</h3>
    <slot></slot>
</template>
<script></script>

插槽的内容在父元素中定义即可

默认内容

在外部没有提供任何内容的情况下,可以为插槽指定默认内容

<template>
    <h3>Slots2</h3>
    <slot>插槽默认值</slot>
</template>
<script></script>

具名插槽

v-slot: 名字

<template>
  <Slot2>
    <template v-slot:header>
      <h3>{{message}}</h3>
    </template>
    <template v-slot:content>
      <h3>内容</h3>
    </template>
  </Slot2>
</template>

多个插槽可以根据名字描述,具体摆放的位置

<template>
    <h3>Slots2</h3>
    <slot name="header">插槽默认值</slot>
    <hr>
    <slot name="content">插槽默认值</slot>
</template>
<script></script>

简写:

<template>
  <Slot2>
    <template #header>
      <h3>{{message}}</h3>
    </template>
    <template #content>
      <h3>内容</h3>
    </template>
  </Slot2>
</template>

插槽中的数据传递

在某些场景下插槽的内容可能想要同时使用父组件域内和子组件域内的数据。要做到这一点,我们需要一种方法来让子组件在渲染时将一部分数据提供给插槽我们也确实有办法这么做!可以像对组件传递 props 那样,向一个插槽的出口上传递 attributes

<template>
  <Slot3 v-slot="s3">
    <h3>{{ content }} - {{ s3.msg }}</h3>
  </Slot3>
</template>

<script>
import Slot3 from "./components/Slot3.vue"
export default {
  data(){
    return{
      content:"测试内容"
    }
  },
  components:{
    Slot3
  }
}
</script>

子组件中增加 属性,比如::msg,这样都是自定义的

<template>
    <h3>子组件</h3>
    <slot :msg="childMess">
        默认值
    </slot>
    <hr>
</template>
<script>
export default {
    data(){
        return{
            childMess:"子组件数据123"
        }
    }
}
</script>

总结:

插槽中的数据是:父组件的数据和子组件的数据合并到了一起去现实的。

具名插槽添加数据

<template>
<slot3>
      <template #header="s3">
        <h3>{{ content }} - {{ s3.msg }}</h3>
      </template>
      <template #main="s4">
        <p>{{ s4.job }}</p>
      </template>
   </slot3>
</template>
<template>
    <h3>子组件</h3>
    <slot name="header" :msg="childMess"></slot>
    <slot name="main" :job="jobMess"></slot>
</template>
<script>
export default {
    data(){
        return{
            childMess:"子组件数据123",
            jobMess:"具名插槽传递参数"
        }
    }
}
</script>

组件的生命周期

每个 Vue 组件实例在创建时都需要经历一系列的初始化步骤,比如设置好 数据侦听编译模板挂载实例到 DOM,以及在数据改变时更新 DOM。在此过程中,它也会运行被称为生命周期钩子的函数,让开发者有机会在特定阶段运行自己的代码

生命周期示意图

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

组件准备被创建:Renderer encounters component (渲染器遇见组件)

  1. 组件创建前:beforeCreate
  2. 组件创建后:created
  3. 组件渲染前:beforeMount
  4. 组件渲染后:mounted
  5. 组件数据更新前:beforeUpdate
  6. 组件数据更新后:updated
  7. 组件卸载前:beforeUnmount
  8. 组件卸载后:unmounted

以上八个重要时刻,都要记下来

<template>
  <h3>组件的生命周期</h3>
  <p>{{ messsage }}</p>
  <button @click="updateHandle">更新数据</button>
</template>

<script>
/**
 *  生命周期函数
 *    创建期:beforeCreate  created
 *    挂载期:beforeMount   mounted
 *    更新期:beforeUpdate  updated
 *    销毁期:beforeUnmount unmounted
 */
export default {
  data() {
    return {
      messsage: "更新之前"
    }
  },
  methods:{
    updateHandle(){
      this.messsage = "更新之后"
    }
  },
  beforeCreate() {
    console.log("组件创建之前");
  },
  created() {
    console.log("组件创建之后");
  },
  beforeMount() {
    console.log("组件渲染之前");
  },
  mounted() {
    console.log("组件渲染之后");
  },
  beforeUpdate() {
    console.log("组件更新之前");
  },
  updated() {
    console.log("组件更新之后");
  },
  beforeUnmount() {
    console.log("组件销毁之前");
  },
  unmounted() {
    console.log("组件销毁之后");
  }
}
</script>

生命周期的应用

组件的生命周期会随着我们对 vue 的了解越多,也会越来越重要,这里我们先进两个堂用的应用常见:

  1. 通过 ref 获取元素DOM结构

  2. 模拟网络请求染数据

通过 ref 获取元素DOM结构

<template>
    <h3>UserComponent</h3>
    <p>组件生命周期应用</p>
    <p ref="name">名字zhangsan</p>
</template>
<script>
// 读取元素的名称,放一个按钮在点击按钮的时候读取到元素信息
export default {
    beforeMount(){ // 挂载期前(渲染前)
        console.log(this.$refs.name);//undefined
    },
    mounted(){
        console.log(this.$refs.name);
    }
}
</script>

必须在适当的时机,在生命周期当中,挂载(渲染)之前无法得到,挂在之后可以得到。

模拟网络请求渲染数据

提示:当页面的内容渲染完成后,这时候才开始加载数据。

<template>
    <h3>UserComponent</h3>
    <p>组件生命周期应用</p>
    <p ref="name">名字zhangsan</p>
    <ul>
        <li v-for="(item,index) of banner" :key="index">
            <h3>{{ item.title }}</h3>
            <p>{{ item.content }}</p>
        </li>
    </ul>
</template>
<script>
// 读取元素的名称,放一个按钮在点击按钮的时候读取到元素信息
export default {
    data(){
        return{
            banner:[]
        }
    },
    beforeMount(){ // 挂载期前(渲染前)
        console.log(this.$refs.name);//undefined
    },
    mounted(){
        console.log(this.$refs.name);
        this.banner = [
            {
                "title":"vue生命周期mounted标题1",
                "content":"文章内容1"
            },
            {
                "title":"vue生命周期mounted标题2",
                "content":"文章内容2"
            },
            {
                "title":"vue生命周期mounted标题3",
                "content":"文章内容3"
            },
            {
                "title":"vue生命周期mounted标题4",
                "content":"文章内容4"
            },
        ]
    }
}
</script>

动态组件

有些场景会需要在两个组件间来回切换,比如Tab界面

A、B 两个组件

<template>
    <h3>ComponentA</h3>
</template>
<template>
  <component :is="tabComponent"></component>
  <button @click="changeHandle">切换组件</button>
</template>

<script>
import ComponentA from "./components/ComponentA.vue";
import ComponentB from "./components/ComponentB.vue";
export default {
  data() {
    return {
      //以字符串的形式赋值
      tabComponent:"ComponentA" 
    }
  },
  methods: {
    changeHandle(){
      this.tabComponent = this.tabComponent == "ComponentA" ? "ComponentB":"ComponentA"
    }
  },
  components: {
    ComponentA,
    ComponentB
  }
}
</script>

总结:

这里 :is=“ ” 相当于 v-bind:is=“ ” ,后面的双引号里面放的是一个变量名称,这个变量名称需要在data中定义一下,然后在下面函数中对这个变量进行判断和再次的赋值操作。

组件保持存活

当使用 来在多个组件间作切换时,被切换掉的组件会被卸载。我们可以通过组件强制被切换掉的组件仍然保持”存活“的状态

组件被卸载

<template>
    <h3>ComponentA</h3>
</template>
<script>
export default {
    beforeUnmount(){
        console.log("组件卸载之前")
    },
    unmounted(){
        console.log("组件卸载之后")
    }
}
</script>

保持存活

<template>
  <KeepAlive>
    <component :is="tabComponent"></component>
  </KeepAlive>
  <button @click="changeHandle">切换组件</button>
</template>

异步组件

在大型项目中,我们可能需要拆分应用为更小的块,并仅在需要时再从服务器加载相关组件。Vue 提供了 defineAsyncComponent 方法来实现比功能

<script>
import UserComponent from "./components/UserComponent.vue"
import ComponentA from "./components/ComponentA.vue";
// import ComponentB from "./components/ComponentB.vue";
// 异步加载组件
import { defineAsyncComponent } from 'vue'
const ComponentB = defineAsyncComponent(() => 
  import("./components/ComponentB.vue")
)
//...
</script>

总结:

异步组件需要的步骤是:

  1. 导入:import { defineAsyncComponent } from ‘vue’
  2. 定义:defineAsyncComponent( () => import(“…”) )

依赖注入

通常情况下,当我们需要从父组件向子组件传递数据时,会使用 props 。想象一下这样的结构: 有一些多层级嵌套的组件,形成了一颗巨大的组件树,而某个深层的子组件需要一个较远的祖先组件中的部分数据。在这种情况下,如果仅使用 props 则必须将其沿着组件链逐级传递下去,这会非常麻烦

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这一问题被称为 ”prop 逐级透传” 一个父组件相对于其所有的后代组件,会作为依赖提供者。任何provide 和 inject 可以帮助我们解决这一问题。后代的组件树,无论层级有多深,都可以注入由父组件提供给整条链路的依赖

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Provide(提供)

要为组件后代提供数据,需要使用 provide 选项

<template>
  <h3>祖宗App</h3>
  <Parent title="祖宗的财产"/>
</template>
<script>
import Parent from './components/Parent.vue';
export default {
  provide:{
    message: "hello!"
  },
  components:{
    Parent
  }
}
</script>

在孙子组件当中接收信息,使用:inject:[“属性”]

<template>
    <h3>Child</h3>
    <p>{{ title }}</p>
    <p>{{ message }}</p>
</template>
<script>
export default {
    inject: ["message"],
    props:{
        title:{
            type:String
        }
    }
}
</script>

也可以读取 data 中的数据

<script>
export default {
  data(){
    return{
      message:"hello123!"
    }
  },
  // provide:{
  //   message: "爷爷的财产"
  // },
  provide(){
    return{
      message : this.message
    }
  },
  components:{
    Parent
  }
}
</script>

注入会在组件自身的状态**之前 **被解析,因此你可以在 data() 中访问注入的属性

<template>
    <h3>Child</h3>
    <p>{{ fullmessage }}</p>
</template>
<script>
export default {
    inject: ["message"],
    data(){
        return {
            // 基于注入值的初始数据
            fullmessage: this.message
        }
    }
}
</script>

注入全局数据

在main.js 当中挂载

const app = createApp(App)
app.provide("golabData","我是全局数据")
app.mount('#app')

提示:

provide 和 inject 只能由上到下的传递,不能反向传递

vue应用

应用实例

每个Vue应用都是通过 createApp 函数创建一个新的应用实例

应用实例:vue的实例对象

import { createApp } from 'vue'

const app = createApp({
	/* 根组件选项 */
}}

根组件

我们传入 createApp 的对象实际上是一个组件,每个应用都需要一个根组件”,其他组件将作为其子组件。

import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)

挂载应用

应用实例必须在调用了 mount0 方法后才会染出来。该方法接收一个“容器”参数,可以是一个实际的 DOM 元素或是一个 CSS 选择器字符串

app.mount('#app')
<div id="app"></div>

这个app的位置就是 index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <link rel="icon" href="/favicon.ico">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Vite App</title>
  </head>
  <body>
    <!-- 浏览器可执行文件:
            1.HTML
            2.CSS
            3.JavaScript
            4.Image
            
            构建工具:Webpack  vite
            最终的内容都会打包成为main.js文件,引入进来
    -->
    <div id="app"></div>
    <script type="module" src="/src/main.js"></script>
  </body>
</html>

公共资源

在 src 目录下的 assets 文件夹的作用就是存放公共资源,例如:图片、公共css或者字体图标等

Vue引入第三方

Swiper 开源、免费、强大的触摸滑动插件

Swiper 是纯javascript打造的滑动特效插件,面向手机、平板电脑等移动终端

Swiper 能实现触屏焦点图、触屏Tab切换、触屏轮播图切换等常用效果

温馨提示

官方文档:https://swiperjs.com/vue

安装指定版本: npm instal --save swiper@8.1.6

基础实现

<template>
  <div class="hello">
    <swiper class="mySwiper">
      <swiper-slide>Slide 1</swiper-slide>
      <swiper-slide>Slide 2</swiper-slide>
      <swiper-slide>Slide 3</swiper-slide>
    </swiper>
  </div>
</template>

<script>
import { Swiper, SwiperSlide } from 'swiper/vue';
import 'swiper/css';

export default {
  name: 'HelloWorld',
  components: {
    Swiper,
    SwiperSlide,
  }
}
</script>

添加指示器

<template>
  <div class="hello">
    <swiper class="mySwiper" :modules="modules" :pagination="{ clickable: true }">
      <swiper-slide>
        <img src="../assets/logo.png" alt="">
      </swiper-slide>
      <swiper-slide>
        <img src="../assets/logo.png" alt="">
      </swiper-slide>
      <swiper-slide>
        <img src="../assets/logo.png" alt="">
      </swiper-slide>
    </swiper>
  </div>
</template>

<script>
import { Pagination } from 'swiper';
import { Swiper, SwiperSlide } from 'swiper/vue';
import 'swiper/css';
import 'swiper/css/pagination';

export default {
  name: 'HelloWorld',
  data(){
    return{
      modules: [ Pagination ]
    }
  },
  components: {
    Swiper,
    SwiperSlide,
  }
}
</script>

Axios网络请求

Axios 是一个基于 promise 的网络请求库

安装

Axios的应用是需要单独安装的 npm install --save axios

引入

组件中引入: import axios from "axios"

全局引用:

import axios from "axios"

const app = createApp(App);
app.config.globalProperties.$axios = axios
app.mount('#app')

// 在组件中调用
this.$axios

网络请求基本示例

get请求
axios({
    method: "get",
    url: "http://iwenwiki.com/api/blueberrypai/getChengpinDetails.php"
}).then(res => {
    console.log(res.data);
})
post请求

温馨提示

post请求参数是需要额外处理的

  1. 安装依赖: npm install --save querystring
  2. 转换参数格式: qs.stringify({})
axios({
    method:"post",
    url:"http://iwenwiki.com/api/blueberrypai/login.php",
    data:qs.stringify({
        user_id:"iwen@qq.com",
        password:"iwen123",
        verification_code:"crfvw"
    })
}).then(res =>{
    console.log(res.data);
})

快捷方案

get请求
axios.get("http://iwenwiki.com/api/blueberrypai/getChengpinDetails.php")
    .then(res =>{
      console.log(res.data);
    })
post请求
axios.post("http://iwenwiki.com/api/blueberrypai/login.php", qs.stringify({
      user_id: "iwen@qq.com",
      password: "iwen123",
      verification_code: "crfvw"
    }))
      .then(res => {
        console.log(res.data);
      })

Axios网络请求封装

在日常应用过程中,一个项目中的网络请求会很多,此时一般采取的方案是将网络请求封装起来

安装axios

cnpm install --save axios

返回以下的信息:

√ Linked 9 latest versions fallback to D:\workspaces\workspace2-vs\14_网络请求封装\vuedemo14\node_modules\.store\node_modules
√ Installed 1 packages on D:\workspaces\workspace2-vs\14_网络请求封装\vuedemo14
√ All packages installed (9 packages installed from npm registry, used 871ms(network 862ms), speed 813.82KB/s, json 9(89.89KB), tarball 611.62KB, manifests cache hit 0, etag hit 0 / miss 0)

dependencies:
+ axios ^1.7.7

安装POST请求querystring

post请求的参数需要转换

cnpm install --save querystring

返回以下的信息:

√ Linked 1 latest versions fallback to D:\workspaces\workspace2-vs\14_网络请求封装\vuedemo14\node_modules\.store\node_modules
deprecate querystring@latest The querystring API is considered Legacy. new code should use the URLSearchParams API instead.
√ Installed 1 packages on D:\workspaces\workspace2-vs\14_网络请求封装\vuedemo14
√ All packages installed (1 packages installed from npm registry, used 300ms(network 298ms), speed 15.08KB/s, json 1(1.16KB), tarball 3.33KB, manifests cache hit 0, etag hit 0 / miss 0)

dependencies:
+ querystring ^0.2.1

启动:

有两种启动方式:

npm run dev

还有一种,目前我没有启动成功,后面再看看怎么回事儿。

npm run serve

request.js

在项目的src文件夹下面创建一个新的文件夹,名字叫做:utils:主要放网络请求的方法

在文件夹下面创建一个文件:request.js

import axios from "axios";
import { error } from "console";
import querystring from "querystring"

//文档参考:https://static.kancloud.cn/cookies_fzx/cookie/1564694

//错误拦截器描述
const errorHandle = (status,info) => {
    switch(status){
        case 400:
            console.log("语义有误");
            break;
        case 401:
            console.log("服务器认证失败");
            break;
        case 403:
            console.log("服务器拒绝访问");
            break;
        case 404:
            console.log("地址错误");
            break;
        case 500:
            console.log("服务器遇到意外");
            break;
        case 502:
            console.log("服务器无响应");
            break;
        default:
            console.log(info);
            break;
    }
}

//创建网络请求对象
const instance = axios.create({
    //网络请求的公共配置
    timeout: 5000
})

//拦截器最常用的
//发送 数据之前
instance.interceptors.request.use(
    // function(){}, 以前的写法
    // function(){}
    config => {
        //因为我们知道post的参数是需要转换的,所以放在拦截器里面
        //优势:在做网络请求的时候就不需要操心格式的问题了
        if(config.methods === "post"){ 
            config.data = querystring.stringify(config.data)
        }
        //config:包含着网络请求的所有信息
        return config;
    },
    error => {
        return Promise.reject(error);
    }
)
//获取 数据之前
instance.interceptors.response.use(
    response => {
        return response.status === 200? Promise.resolve(response):Promise.reject(response)
    },
    error => {
        const { response } = error;
        //错误的处理才是我们需要最关注的
        errorHandle(response.status,response.info)
    }
)

export default instance;

api

src目录下创建文件夹api,并创建文件indexpath分别用来存放网络请求方法和请求路径

网络请求都集中放在api的文件夹里面了

首先创建path.js

// path.js
const base = {
    baseUrl:"http://iwenwiki.com",
    chengpin:"/api/blueberrypai/getChengpinDetails.php"
}

export default base
// index.js
import path from "./path"
import axios from "../utils/request"

const api = {
    //成品详情地址
    getChengpin(){
        return axios.get(path.baseUrl + path.chengpin)
    }
}

export default api

将最终的结果使用在HelloWorld中

<script>
import api from '../api/index';

export default {
  name: "HelloWorld",
  props: {
    msg: String
  },
  mounted(){
    api.getChengpin().then(res =>{
      console.log(res.data);
    })
  }
}
</script>

网络请求跨域解决方案

S采取的是同源策略

同源策略是浏览器的一项安全策略,浏览器只允许js 代码请求和当前所在服务器域名,端口,协议相同的数据接口上的数据,这就是同源策略。

也就是说,当协议、域名、端口任意一个不相同时,都会产生跨域问题,所以又应该如何解决跨域问题呢?

跨域错误提示信息

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

目前主流的跨域解决方案有两种:

  1. 后台解决:cors
  2. 前台解决:proxy

在项目中文件:vite.config.js中,增加跨域配置:

url例如:http://iwenwiki.com

devServer: {
    proxy: {
      '/api': {
        target: '<url>',
        changeOrigin: true
      }
    }
}

温馨提示

解决完跨域配置之后,要记得重启服务器才行哦!

在之前的请求当中

将axios的get内容的前缀信息去掉:

axios.get(“/api/FingerUnion/list.php”)

<template>
  <div>
    <h1>{{ msg }}</h1>
    <h3>
      You’ve successfully created a project with
    </h3>
  </div>
</template>
<script>
import axios from "axios"

export default {
  name: "helloworld",
  props:{
    msg:{
      type:String
    }
  },
  //组件渲染之后
  mounted(){
    axios.get("/api/FingerUnion/list.php")
    .then(res => {
      console.log(res.data);
    })
  }
}
</script>
<style scoped>
</style>

Vue引入路由配置

在Vue中,我们可以通过vue-router路由管理页面之间的关系

Vue Router 是 Vue.js 的官方路由。它与 Vue.js 核心深度集成,让用 Vue.js 构建单页应用变得轻而易举。

在Vue中引入路由

第一步:安装路由 npm install --save vue-router

第二步:配置独立的路由文件

import { createRouter, createWebHashHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import AboutView from '../views/AboutView.vue'

//配置信息中需要页面的相关配置
const routes = [
    {
        //配置路径地址
        path:"/",
        //指定到相关的页面   
        component:HomeView  
    },
    {
        path:"/about",
        component:AboutView
    }
]


const router = createRouter({
    history:createWebHashHistory(),
    routes
})
export default router;

第三步:引入路由到项目

//导入router
import router from './router'

//通过.use(router)明确安装router功能
createApp(App).use(router).mount('#app')

第四步:指定路由显示入口 <router-view/>

第五步:指定路由跳转

<template>
    <RouterLink to="/">首页</RouterLink>
    <RouterLink to="/about">关于</RouterLink>
    <RouterView></RouterView>
</template>