Vue开发必备:从入门到实战指南

前端开发相关文档链接

Axios文档: https://www.axios-http.cn/docs/interceptors
VueRouter文档:https://v3.router.vuejs.org/zh/guide/
Vue API文档:https://cn.vuejs.org/api/
可视化图表库:https://echarts.apache.org/zh/index.html
Element Plus组件库:https://element-plus.org/zh-CN/

一、Vue简介

Vue 是一个用于构建用户界面的渐进式 JavaScript 框架,是前端三大框架之一(Angular、React、Vue)。它设计灵活,可以逐步集成到项目中,既适用于小型交互功能,也能支撑复杂的单页应用(SPA)。Vue 的核心库专注于视图层,同时提供配套工具和生态系统支持高级功能开发。

文档链接:https://cn.vuejs.org

注:vue2时,router为3,vuex为3;vue3时,router为4,vuex为4

1.1 快速上手

下面是vue的一个简单应用的示例

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
    </head>
    <body>
        <div id="app">
            <!-- 调用data中的数据 -->
            {{ msg }}
        </div>

        <!-- 如果不下载,直接导入官方包   导入后有 Vue 构造函数 -->
        <script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
        <script>
            const app = new Vue({
                el: "#app",    // 指定挂载点
                data: {        // 提供数据
                    msg: "hello world"
                }
            })
        </script>
    </body>
</html>

插值表达式

上面的代码用到了插值表达式{{ }},将data中的 msg 数据会展示在界面中,插值表达式中还可以以运算、函数等方式写入,插值表达式用于展示文本,而不适用于标签属性

<div id="app">
    <p>{{ usename }}</p>
    <p>{{ age + 1 }}</p>
    <p>{{ age > 10 ? "大于" : "小于" }}</p>
    <p>{{ func() }}</p>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
<script>
    const app = new Vue({
        el: "#app",    // 指定挂载点
        data: {        // 提供数据
            usename: "Tony",
            age: 18
        }
    })
</script>

1.2 构造函数的选项对象

选项参数说明
el提供一个在页面上已存在的DOM元素作为Vue实例的挂载目标
data实例的数据对象。将会递归将data的属性转换为getter/setter,从而让data的属性能够响应数据变化
methodsVue实例的方法集合,可以在Vue直接调用或将方法绑定到DOM元素的事件上
computedVue实例的计算属性集合
watch观察Vue实例变化的一个表达式或计算属性函数
components包含Vue实例可用组件的哈希表
filters包含Vue实例可用过滤器的哈希表
template定义字符串模板作为Vue实例的标识使用

computed

作用:封装了一段对于数据的处理,求得一个结果。依赖的数据变化,自动重新计算。
拥有缓存特性,即:多次调用时只会计算一次
声明在computed配置项中,一个计算属性对应一个函数,使用起来和普通属性一样使用{{ 计算属性名 }}

<div id="app">
    <!-- total后面不能加括号 -->
    <p>{{ total }}</p>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
<script>
    const app = new Vue({
        el: "#app",
        computed: {
        	total () {
	        	return 10
	        }
        }
    })
</script>

如果要进行修改操作,需要写计算属性的完整写法

<div id="app">
    <!-- total后面不能加括号 -->
    <p>{{ total }}</p>
    <button @click="func">按钮</button>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
<script>
    const app = new Vue({
        el: "#app",
        data: {
            usename: "Tony"
        },
        methods: {
            func () {
                this.total = "Jack"
            }
        },
        computed: {
        	total: {
	        	get () {
                    return this.usename
                },
                set (value) {
                    this.usename = value
                }
	        }
        }
    })
</script>

watch侦听器(监视器)

作用:执行一些业务逻辑或异步操作监视数据变化
简单写法:简单类型数据,直接监视

<script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
<script>
    const app = new Vue({
        el: "#app",
        data: {
            usename: "Tony"
        },
        watch: {
            // 监听 usename 变化 变化时调用
            usename (newValue, oldValue) { 
                console.log(newValue, oldValue)
            }
            // 子属性用法如下 
            // 'xx.usename' (newValue, oldValue) {}
        }
    })
</script>

完整写法:添加额外配置项
deep:true 对复杂类型深度监视
immediate:true 初始化立刻执行一次handler方法

<script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
<script>
    const app = new Vue({
        el: "#app",
        data: {
            obj: {
                use: "Tony", 
                age: 10
            }
        },
        watch: {
            // 对属性中所以子属性监视
            obj: { 
                deep: true,
                handler (newValue, oldValue) {
                    console.log(newValue, oldValue)
                }
            }
        }
    })
</script>

1.3 生命周期(钩子函数)

Vue 组件的生命周期指的是从创建、挂载、更新到销毁的完整过程。每个阶段会触发对应的生命周期钩子函数,开发时可以通过这些钩子执行特定逻辑

生命周期函数可以分为如下四类(钩子函数)
初始化阶段(响应数据):beforeCreate → created
挂载阶段(渲染模板): beforeMount → mounted
更新阶段(数据修改):beforeUpdate → updated
销毁阶段(销毁实例):beforeDestroy → destroyed

函数含义
beforeCreate实例初始化后,数据观测(data observer)和事件配置之前调用。此时无法访问 data、methods 等属性
created实例创建完成,数据观测和事件配置已完成。可访问 data 和 methods,但尚未挂载 DOM,$el 不可用
beforeMount模板编译完成,但尚未将虚拟 DOM 渲染为真实 DOM
mounted实例挂载到 DOM 后调用,$el 可用。常用于操作 DOM 或发起异步请求。
beforeUpdate数据更新时触发,发生在虚拟 DOM 重新渲染和打补丁之前
updated数据变更导致虚拟 DOM 重新渲染后调用。避免在此钩子中修改数据,可能导致无限循环。
beforeDestroy实例销毁前调用,此时实例仍完全可用。适合清理定时器、解绑事件等操作。
destroyed实例销毁后调用,所有指令和事件监听器被移除,子实例也被销毁
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
<script>
    const app = new Vue({
        el: "#app",
        data: {
            obj: "hello world"
        },
        beforeCreate () {
            console.log("beforeCreate")
        },
        beforeMount () {
            console.log("beforeMount")
        }
    })
</script>

1.4 Vue常用指令介绍(v-html、v-for等)

指令含义
v-html设置元素的innerHTML
v-show控制元素显示与隐藏
(有元素标签,通过 display 控制显示隐藏,适合频繁切换
v-if
v-else
v-else-if
控制元素显示与隐藏
(根据条件来控制 元素标签 的创建和移除 )
v-for循环
v-on事件绑定
(v-on:click='xxx'简化为@click='xxx')
v-bind动态的设置html的标签属性
(v-bind:xxx='xxx'简化为:xxx='xxx')
v-model双向数据绑定
(常用于表单输入框等)
v-slot占位符

v-html

作用:设置元素的innerHTML
语法:v-html="表达式"

<div id="app">
    <div v-html="msg"></div>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
<script>
    const app = new Vue({
        el: "#app",
        data: {
            // 以 innerHTML形式解析到界面  会解析标签
            msg: `<h1>标题</h1>
            <p>内容</p>`
        }
    })
</script>

v-on

作用:事件绑定,比如按钮点击事件绑定
语法:v-on:事件名="内联语句或函数",或简写为@事件名="内联语句或函数"

<body>
    <div id="app">
        <p> {{ age }}</p>
        <button v-on:click="age--">点击-1</button>
        <!-- v-on: 简化为 @    传参 @click="func(参数)"-->
        <button @click="func">点击+1</button>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
    <script>
        const app = new Vue({
            el: "#app",    // 指定挂载点
            data: {        // 提供数据
                age: 18
            },
            methods: {
                func () {  // 接收参数 func (参数) {}
                    this.age++
                }
            }
        })
    </script>
</body>

除了click还有如下事件

事件含义
click点击一次
dblclick双击
focus获取焦点
blur失去焦点
mouseover鼠标移入
mouserout鼠标移出
keyup键盘按下
@keyup.enter键盘回车监听
@click.stop阻止冒泡
@click.prevent阻止默认行为
<div id="app">
    <!-- @keyup.enter 绑定 func 函数,输入后点 enter 键 等于触发func 函数 -->
    <input @keyup.enter="func" type="text" v-model.trim="username"></input>
    <button @click="func">按钮</button>
</div>

v-bind

作用:动态的设置html的标签属性(src、url、title等)
语法:v-bind:属性名="表达式",或简写为:属性名="表达式"

<body>
    <div id="app">
        <a v-bind:href="url">baidu</a>
        <!-- v-bind: 简化为 : -->
        <a :href="url">baidu</a>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
    <script>
        const app = new Vue({
            el: "#app",
            data: {
                url: 'https://www.baidu.com',
            }
        })
    </script>
</body>

v-bind 可以操作 class 属性
语法::class="对象/数组"
对象:键就是类名,值是布尔值。如果值为true,有这个类,否则没有这个类
数组:数组中所有的类,都会添加到盒子上,本质就是一个class列表

<div :class="{ 类名1: 布尔值, 类名2: 布尔值}"></div>
<div :class="[类名1, 类名2]"></div>

v-bind 可以操作 style 属性
语法::style="样式对象"

<!-- backbround-color 要写成 'background-color' 或 backgroundColor -->
<div :style="{ css属性名1: CSS属性值, css属性名2: CSS属性值}"></div>
<div :class="[类名1, 类名2]"></div>

v-for

作用:基于数据循环,多次渲染整个元素
语法:v-for="(item, index) in 数组",或者不带索引v-for="item in 数组"

<body>
    <div id="app">
        <ul>
            <!-- :key 添加唯一标识 -->
            <li v-for="(item,index) in items">{{ index+1 }} {{ item }}</li>
        </ul>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
    <script>
        const app = new Vue({
            el: "#app",
            data: {
                items: ["Tony", "Jack"]
            }
        })
    </script>
</body>

也可以字典格式
:key作用:给元素添加的唯一标识,便于Vue进行列表项的正确排序复用(必须唯一性,推荐使用id,不推荐使用index)

<ul>
  <li v-for="(item, key) in items" :key="key">{{ key }} : {{ item }}</li>
</ul>
items: {
    use:"Tony",
    age: 18
}

也可以是复杂的数组

<ul>
  <li v-for="(item, key) in items" :key="item.id">{{ item.use }} : {{ item.age }}</li>
</ul>
items: [
  {
        use: "Tony",
        age: 18,
        id: "001"
    },
    {
        use: "Jack",
        age: 10,
        id: "002"
    }
]

v-model

作用:给表单元素使用,双向数据绑定(输入框内容变化,值也会变化) ,可以快速获取或设置表单元素内容
语法:v-model='变量',去除收尾空格:v-model.trim='变量',转数字:v-model.number ='变量'

<body>
    <div id="app">
        账号:<input type="text" v-model="username"></input>
        密码:<input type="password" v-model="password"></input>
        <button @click="func">按钮</button>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
    <script>
        const app = new Vue({
            el: "#app",
            data: {
                username: "Tony",
                password: "123456"
            },
            methods: {
                func() {
                    console.log(this.username)
                    console.log(this.password)
                }
            }
        })
    </script>
</body>

常见的表单元素都可以用v-model绑定关联,快速获取或设置表单元素的值,它会根据控件类型自动选取正确的方法来更新元素
比如:输入框(input:text)、文本域(textarea)、复选框(input:checkbox)、单选框(input:radio)、下拉菜单(select)

<body>
    <div id="app">
        <input type="text" v-model="username">  <!-- 输入框 -->
         <br>
        <input type="checkbox" v-model="isFlag">  <!-- 复选框 -->
         <br>
        <!-- 单选 -->    <!-- name属性相同会分到一组 -->
         <input v-model="gender" type="radio" name="gender" value="1"><input v-model="gender" type="radio" name="gender" value="2"><br>
         <select v-model="city">
            <option value="101">北京</option>
            <option value="102">上海</option>
         </select>
         <textarea v-model="desc"></textarea>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
    <script>
        const app = new Vue({
            el: "#app",
            data: {
                username: "Tony",
                isFlag: true,
                gender: "1",
                city:  "102",
                desc: ""
            }
        })
    </script>
</body>

1.5 自定义指令(v-自定义)

Vue 允许注册自定义指令,用于对 DOM 元素进行底层操作。自定义指令适合封装需要直接操作 DOM 的逻辑,例如自动聚焦、拖拽、权限控制等

自定义指令提供以下钩子函数,用于在不同阶段执行逻辑

钩子函数作用
bind指令第一次绑定到元素时调用(仅一次)。
inserted被绑定元素插入父节点时调用。
update所在组件更新时调用(可能发生在子组件更新前)。
componentUpdated所在组件及其子组件全部更新后调用。
unbind指令与元素解绑时调用。

全局注册

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

Vue.config.productionTip = false


Vue.directive("指令名",  {
    "inserted" (el) {
        // 可以对 el 标签,扩展额外功能
        el.focus()
    }
}

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

局部注册

export default {
	directives: {
	    "指令名": {
		    inserted (el) {
		        // 可以对 el 标签,扩展额外功能
		        el.focus()
		    }
		  }
	}
}

自定义指令的使用

<input v-指令名 type="text">

举例:实现一个color指令-传入不同的颜色变量,当变量变化时,文字颜色同步变化

<!-- color为颜色值,当发生变化时,颜色也发生变化 -->
<div v-color="color">颜色</div>

通过binding.value可以拿到变量color的值,inserted提供的是元素被添加到页面中时的逻辑,指令值修改会触发update函数

export default {
	directives: {
	    "color": {
		    inserted (el, binding) {
		        el.style.color = binding.value
		    },
		    update (el, binding) {
		        el.style.color = binding.value
		    },
		  }
	}
}

1.6 Vue异步更新、$nextTick

当进入页面立马获取输入框的焦点时,可能获取不到,原因是Vue异步更新,界面可能还没完全渲染,此时需要用$nextTick,等DOM更新后,才会触发执行此方法里的函数体

<template>
  <div>
    <input ref="inp" type="text">
  </div>
</template>

<script>
export default {
  methods: {
    handleClick () {
      // 获取焦点
      // this.$refs.inp.focus()  // 直接使用可能获取不到,可能还没渲染出来
      this.$nextTick(() => {  // 渲染完成之后,再获取
        this.$refs.inp.focus()
      })
    }
  }
}
</script>

<style scoped></style>

二、工程化搭建VUE项目(工程化开发与脚手架)

安装脚手架

全局安装(以管理员身份运行):yarn global add @vue/cli(下载后如果无法识别vue,按这个指令下载 npm i @vue/cli -g
查看 Vue 版本:vue --version

2.1 项目创建与组件化开发

创建启动项目

创建项目架子(管理员省份运行):vue create project-name(项目名-不能用中文)
启动项目:yarn servenpm run serve(package.json中的script中配置了serve项)

创建项目会出现如下目录结构

PROJECT_DEMO
 | - node_modules     # 第三方文件夹
 | - public           # 存放 HTML 文件的地方
 |   | - favicon.ico  # 网站图标
 |   | - index.html   # index.html 模板文件
 | - src              # 项目代码开发位置
 |   | - assets       # 静态文件目录  (图片、字体)
 |   | - components   # 组件目录  (通用组件)
 |   | - App.vue      # App根组件  
 |   | - main.js      # 入口文件 (打包或运行入口文件)
 | - .gitignore       # git忽视文件
 | - babel.config.js  # babel配置文件
 | - jsconfig.js      # js配置文件
 | - onpackage.json   # 项目配置文件>包含项目名、版本号、scripts、依赖包等
 | - README.md        # 项目说明文档
 | - vue.config.js    # vue-cli配置文件
 | - yarn.lock        # yarn锁文件,由yarn自动生成的,锁定安装版本

组件化开发

组件化:一个页面可以拆分成一个个组件,每个组件有着自己独立的结构、样式、行为。
好处:便于维护,利于复用提升开发效率。
组件分类:普通组件、根组件。

根组件:整个应用最上层的组件,包裹所有普通小组件。

组件的三大组成部分:结构(<template>)、逻辑(<script>)、样式(<style>

<!-- 结构 -->
<template>
  <div class="hello">
    <h1>Hello</h1>
  </div>
</template>

<!-- 逻辑 -->
<script>
export default {
  name: 'HelloWorld',
  props: {
    msg: String
  }
}
</script>

<!-- 样式 -->
<style scoped>
.hello {
  margin: 40px 0 0;
}
</style>

样式要支持less语法,需要在style上加上lang=“less”,即<style lang="less">
还需要安装less、less-loader包,yarn add less less-loader -D

样式冲突(<style>

全局样式(默认):默认组件中的样式会作用到全局

<style>
/* 默认全局样式 会影响其他组件样式  */
</style>

局部样式:可以给组件加上 scoped 属性,可以让样式只作用于当前组件

<style scoped>
/* 局部样式 只在当前组件中有效 (加上 scoped )  */
</style>

2.2 普通组件的注册使用(局部与全局)

局部注册(只能在注册的组件内使用)

  1. 在components中创建 .vue 文件(三个组成部分)
<template>
  <div>header部分</div>
</template>

<script></script>

<style scoped></style>
  1. 在使用的组件内导入注册并使用(如下面的MyHeader)
<template>
  <div id="app">
    <!-- 组件使用 -->
    <MyHeader></MyHeader>
  </div>
</template>

<script>
// 导入需要注册的组件
import MyHeader from './components/MyHeader.vue'

export default {
  components: {
    MyHeader: MyHeader  // 局部注册
  }
}
</script>

<style scoped></style>

全局注册(所有组件内都能使用)

  1. 在components中创建 .vue 文件(三个组成部分)
<template>
  <button>按钮</button>
</template>

<script></script>

<style scoped></style>
  1. main.js中进行全局注册
import Vue from 'vue'
import App from './App.vue'
// 导入全局组件
import MyButton from './components/MyButton.vue'

Vue.config.productionTip = false
// 进行全局注册
Vue.component("MyButton", MyButton)

new Vue({
  render: h => h(App),
}).$mount('#app')
  1. 全局使用
<template>
  <div id="app">
    <!-- 组件使用 -->
    <MyButton></MyButton>
  </div>
</template>

<script></script>

<style scoped></style>

2.3 组件间插槽使用(slot)

作用:让组件内部的一些结构支持自定义
需求:要在页面中显示一个对话框,封装成一个组件
问题:组件的内容部分,不希望写死,希望能使用的时候自定义。

插槽使用方法

  1. 定义子组件,子组件中使用<slot></slot>占位(封装的代码如下 MyDialog ),如果有默认内容,没传入时使用默认内容,可使用<slot>默认内容</slot>占位
<template>
  <div class="dialog-content">
     <!-- 占位符 调用中的内容插入此处 -->
     <slot></slot>
  </div>
</template>
  1. 使用组件时,<MyDialog></MyDialog>标签内部,传入结构替换<slot></slot>所在位置
<MyDialog>
	要插入的内容
</MyDialog>

插槽 - 具名插槽

上面自定义的组件中只是传入一个值,当一个组件有多个地方需要传入值,需要使用具名插槽

  1. 多个slot使用name属性区分名字
<template>
  <div>
    <!-- 占位符 调用中的内容插入此处 -->
     <slot name="head"></slot>
  </div>
  <div>
    <!-- 占位符 调用中的内容插入此处 -->
     <slot name="content"></slot>
  </div>
</template>
  1. template配合v-slot:名字来分发对应标签 v-slot: 可简写为#
<MyDialog>
    <template v-slot:head>标题</template>
    <!-- v-slot: 可简写为 #head -->
    <template #content>内容</template>
</MyDialog>

插槽 - 作用域插槽

作用域插槽:定义slot插槽的同时也可以传值的。给插槽上可以绑定数据,将来使用组件时可以用

使用步骤如下:

  1. 子组件中(如 MyTable),给 slot 标签占位符,以添加属性的方式传值
<slot :id="item.id" msg="test"></slot> <!-- 此处传入父组件的button 传参id、msg到父组件 -->
  1. 所有添加的属性,都会被收集到一个对象中
{id: 3, msg: "test"}
  1. 在父组件的 template 中,通过#插槽名="obj”接收,默认插槽名为 default
<MyTable :list="list">
	<template #default="obj">   <!-- obj接收子组件传参的id、msg -->
		<button @click="del(obj.id)">删除</button>
	</template>
</MyTable>

三、组件通信

组件通信:组件与组件之间的数据传递。组件的数据是独立的,无法直接访问其他组件的数据。想用其他组件的数据,可以通过组件通信方式

3.1 父子通信

父传子(props):将值传给子组件,父组件中的:MyTitle="MyTitle"是将值传给子组件,子组件中,通过props接收(:MyTitle名字要和props中的一致)
子传父(emit):从子组件将数据传给父组件,子组件通过emit传给父组件,父组件通过@returnData="handleReturn"接收处理(@returnData要和emit中的保持一致)

父组件代码

<template>
  <div id="app">
    <MyHeader :MyTitle="MyTitle" @returnData="handleReturn"></MyHeader>
  </div>
</template>

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

export default {
  components: {
    MyHeader: MyHeader
  },
  data () {
    return {
      MyTitle: "标题"
    }
  },
  methods: {
    // 处理子元素返回来的值
    handleReturn (message) {
      console.log(message)
    }
  }
}
</script>

<style scoped></style>

子组件代码

<template>
  <button @click="handleClick"> {{ MyTitle }}</button>
</template>

<script>
export default {
  // 接收父组件传来的值
  props: ['MyTitle'],  // MyTitle 与父组件名字 一致
  methods: {
    handleClick () {
      // returnData 与父组件名字 一致  hello 为传出的数据
      this.$emit('returnData', 'hello')
    }
  }
}
</script>

<style></style>

传入校验

父传子可以在子组件中对其进行校验,比如类型、是否必传、默认值、自定义校验等

<script>
export default {
  // 接收父组件传来的值
  props: {
    // usename 值
    usename: String, // 声明传入 字符串类型
    // phoneNum 值
    phoneNum: {  // 更多校验功能
      type: Number,  // 数字类型
      required: true,  // 是否必传
      default: "",  // 默认值
      validator (value) {  // 自定义校验逻辑
        return true
      }
    }
  },
  methods: {
    handleClick () {
      // returnData 与父组件名字 一致  
      // hello 为传出的数据
      this.$emit('returnData', 'hello')
    }
  }
}
</script>

非父子通信 - event bus 事件总线

非父子组件之间,进行简易消息传递。(复杂场景用 Vuex)

  1. 在utils/EventBus.js 创建一个都能访问到的事件总线(空Vue实例)
import Vue from'vue'
const Bus = new Vue()
export default Bus
  1. A组件(接收方),监听Bus实例的事件
created () {
    Bus.$on('sendMsg', (msg) => {
        this.msg = msg
    )}
}
  1. B组件(发送方),触发Bus实例的事件(一对多,添加了监听的都能收到)
Bus.$emit('sendMsg''这是一个消息')

非父子通信 - provide & inject

provide & inject作用:跨层级共享数据(爷孙层级)

  1. 父组件 provide提供数据
<script>
export default {
  provide () {
    return {
        // 普通类型【非响应式】
        color: this.color,
        //复杂类型【响应式】(字典、列表)  推荐
        userInfo: this.userInfo,
    }
  },
}
</script>
  1. 子/孙组件inject取值使用
<script>
export default {
  inject: ['color', 'userInfo']
}
</script>

3.2 单向数据流(prop&data)

共同点:都可以给组件提供数据
区别data的数据是自己的,可随便改;prop数据是外部的,不能直接改,要遵循 单向数据流

<script>
export default {
  data () {
    return {
      MyTitle: "标题"   // 自己数据,可随便改
    }
  },
  props: {
    usename: String   // 外部数据,不能直接改
  }
}
</script>

3.3 v-model sync 简化组件代码

原理:V-model本质上是一个语法糖。例如应用在输入框上,就是value属性input事件的合写。
作用:提供数据的双向绑定(数据变,视图跟着变:value,视图变,数据跟着变@input)
注:$event 用于在模板中,获取事件的形参

<template>
  <input :value="msg" @input="msg = $event.target.value" type="text">
  <!-- v-model 等于 :value 和 @input -->
  <input v-model="msg" type="text">
</template>

表单类组件封装&v-model简化代码

表单类组件封装
父传子:数据应该是父组件props传递过来的,V-model拆解绑定数据
子传父:监听输入,子传父传值给父组件修改

<template>
  <div id="app">
    <MyHeader :cityId="selectId" @handleChange="selectId = $event"></MyHeader>
  </div>
</template>

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

export default {
  components: { MyHeader: MyHeader },
  data () {
    return {
      selectId: "101"
    }
  }
}
</script>

<style></style>
<template>
  <div>
    <select :value="cityId" @input="handleChange">
      <option value="101">北京</option>
      <option value="102">上海</option>
    </select>
  </div>
</template>

<script>
export default {
  // 接收父组件传来的值
  props: {
    cityId: String
  },
  methods: {
    handleChange (e) {
      this.$emit('handleChange', e.target.value)
    }
  }
}
</script>

<style></style>

v-model简化代码

父组件v-model简化上面代码,实现子组件和父组件数据双向绑定

  1. 子组件中:props通过value接收(一定为value),事件触发input(一定为input)
  2. 父组件中:v-model给组件直接绑数据
<template>
  <div id="app">
    <MyHeader v-model="selectId"></MyHeader>
  </div>
</template>

<script>
// 导入需要注册的组件
import MyHeader from './components/MyHeader.vue'

export default {
  components: {
    MyHeader: MyHeader  // 局部注册
  },
  data () {
    return {
      selectId: "101"
    }
  }
}
</script>

<style></style>
<template>
  <div>
    <select :value="value" @input="handleChange">
      <option value="101">北京</option>
      <option value="102">上海</option>
    </select>
  </div>
</template>

<script>
export default {
  // 接收父组件传来的值
  props: {
    value: String  // 一定为value
  },
  methods: {
    handleChange (e) {
      this.$emit('input', e.target.value)  // 一定为input
    }
  }
}
</script>

<style></style>

.sync 修饰符

作用:可以实现子组件与父组件数据的双向绑定,简化代码
特点:prop属性名 可以自定义,非固定为value

<template>
  <div id="app">
    <MyHeader :visible="isShow" @update:visibl="isShow = $event"></MyHeader>
    <!-- 简化为如下代码 -->
    <MyHeader :visible.sync="isShow"></MyHeader>
  </div>
</template>
<script>
export default {
  props: {
    visible: Boolean
  },
  methods: {
    handleChange (e) {
      this.$emit('update:visible', false)
    }
  }
}
</script>

3.4 ref与refs(获取dom元素)

作用:利用 ref$refs 可以用于获取dom元素,或组件实例
特点:查找范围 当前组件内(更精确稳定)

获取DOM:目标标签添加 ref属性

<template>
  <div ref="myChart"></div>
</template>

<script>
export default {
  mounted() {
    const my_chart = echarts.init(this.$refs.myChart)
    
    // 图表配置项 略
    const option = {}
    my_chart.setoption(option)
  }
}
</script>

<style></style>

可以通过ref在父组件中调用子组件中的方法(组件实例)

<template>
  <div id="app">
    <MyHeader ref="baseForm"></MyHeader>
    <button @click="handleClick">获取数据</button>
  </div>
</template>

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

export default {
  components: {
    MyHeader: MyHeader
  },
  methods: {
    handleClick () {
      console.log(this.$refs.baseForm.getValues())
    }
  }
}
</script>

<style></style>
<template>
  <input v-model="username" type="text">
</template>

<script>
export default {
  data () {
    return {
      username: ''
    }
  },
  methods: {
    getValues () {
      return this.username
    }
  }
}
</script>

<style></style>

四、路由(VueRouter)

Vue中路由:路径和组件的映射关系

VueRouter介绍

作用:修改地址栏路径时,切换显示匹配的组件
说明:Vue官方的一个路由插件,是一个第三方包
官网:https://v3.router.vuejs.org/zh/

4.1 路由基本配置与使用

VueRouter使用基本步骤

  1. 下载VueRouter模块到当前工程(vue2 3.x,vue3 4.x):yarn add vue-router@3.6.5
  2. 引入:import VueRouter from 'vue-router'
  3. 安装注册:Vue.use(VueRouter)
  4. 创建路由对象:const router = new VueRouter()
  5. 注入、将路由对象注入到newVue实例中,建立关联new Vue({ render:h => h(App), router: router }).$mount("@app")

实例:在代码的main.js中添加如下内容

import VueRouter from 'vue-router'
Vue.use(VueRouter)

// vue2的router3配置方式
const router = new VueRouter({
	mode: 'history',
	routes: []
})
// vue3的router4配置方式
const router = createRouter({
	// history模式使用createWebHistory   hash模式使用createWebHashHistory
	history: createWebHistory('/'),
	routes: []
})

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

使用的两个核心步骤

  1. 创建需要的组件(views目录),配置路由规则(下面代码为main.js中)
import A from "./views/A.vue"
import B from "./views/B.vue"
import C from "./views/C.vue"

const router = new VueRouter({
  routes: [
    {path: '/a', component: A},
    {path: '/b', component: B},
    {path: '/c', component: C}
  ]
})
  1. 配置导航,配置路由出口(路径匹配的组件显示的位置) <a href="#/a">链接</a>
<div class="footer_wrap>
	<a href="#/a">a</a>
	<a href="#/b">b</a>
	<a href="#/c">c</a>
</div>
<div class="top">
	<!-- 路由出口 > 匹配的组件所展示的位置  上面点击,下面切换 -->
	<router-view></router-view>
</div>

router-link自带类名

a标签可以使用router-link取代,router-link默认会提供高亮类名,不需要像 a 标签一样自己配置点击项的类名增删操作,只需统一配好对于类即可

<div class="footer_wrap>
	<a href="#/a">a</a>
	<a href="#/b">b</a>
	<a href="#/c">c</a>
</div>

<!-- 效果同上 -->
<div class="footer_wrap>
	<router-link to="/a">a</router-link>
	<router-link to="/b">b</router-link>
	<router-link to="/c">c</router-link>
</div>

a.router-link-active {
	xxx
}

类名:router-link-exact-active,router-link-active
touter-link-active 模糊匹配,to="/my"可匹配/my/a(常用)
router-link-exact-active 精确匹配

router-link定制类名

const router = newVueRouter({
	routes: [...],
	linkActiveclass:"类名1",
	linkExactActiveClass: "类名2"
)}
<div class="footer_wrap>
	<router-link to="/a" class="类名2 类名1">a</router-link>
	<router-link to="/b">b</router-link>
	<router-link to="/c">c</router-link>
</div>

4.2 路由的封装抽离

上面的代码中,路由配置都是写在main.js中,现在将其单独写到一个文件 (src/router/index.js),然后在main.js中只需导入使用即可

src/router/index.js

// @ 表示src目录
import A from "@/views/A.vue"
import B from "@/views/B.vue"
import C from "@/views/C.vue"
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)

const router = new VueRouter({
  routes: [
    {path: '/a', component: A},
    {path: '/b', component: B},
    {path: '/c', component: C}
  ]
})

export default router

main.js

import router from './router/index.js'

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

4.3 路由传参与重定向

声明式导航 - 跳转传参

在跳转路由时,进行传值,有两种方式传参:查询参数传参动态路由传参

查询参数传参(适合多个参数)

  1. 跳转:to="/path?参数名1=值1&参数名2=值2"
  2. 对应页面组件接收传递过来的值:$route.query.参数名
<div class="footer_wrap>
	<router-link to="/a?key=hello" class="类名2 类名1">a</router-link>
</div>


<!-- 组件代码 -->
<div>
	{{ $route.query.key }}
</div>
<sctipt>
	export default {
		name: '',
		created() {
			connsole.log(this.query.key)
		}
	}
</script>

动态路由传参(适合单个参数)

  1. 配置动态路由
const router = newVueRouter({
	routes: [
		...,
		{
			path: "/search/:参数名",
			component: Search
		}
	]
)}
  1. 配置导航链接:to="/path/参数值"
  2. 对应页面组件接收传递过来的值:$route.params.参数名

动态路由参数可选符
问题:配了路由path:"/search/:words” 为什么按下面步骤操作,会未匹配到组件,显示空白
原因:/search/:Words 表示必须要传参数。如果不传参数,也希望匹配,可以加个"?"表示可选参数("/search/:参数名?"即可不传)

Vue路由 - 重定向

问题:url默认是/路径,未匹配到组件时,会出现空白网页打开
说明:重定向匹配path后,强制跳转path路径
语法:{path:匹配路径,redirect:重定向到的路径},

const router = newVueRouter({
	routes: [
		{ path: "/", redirect: "/home"},
		{ path: "/home", component: home }
	]
)}

Vue路由 - 404

问题:当路径找不到匹配时,给个提示页面
说明:配在路由最后
语法:path:"*"(任意路径)-前面不匹配就命中最后这个

const router = newVueRouter({
	routes: [
		{ path: "/", redirect: "/home"},
		{ path: "/home", component: home }
		{ path: "*", component: NotFind }
	]
)}

Vue路由 - 模式设置

问题:路由的路径看起来不自然,有#,能否切成真正路径形式?
hash路由(默认) 例:http://localhost:8080/#/home
history路由(常用) 例如:http://localhost:8080/home

const router = newVueRouter({
	routes,
	mode: "history"  // hash
)}

编程式导航 - 基本跳转

问题:点击按钮跳转如何实现?
编程式导航:用S代码来进行跳转

两种语法:

  1. path路径跳转(简易方便)
// 方式一   
// 传参方式1:'路由路径?参数1=值1&参数2=值2'   
// 传参方式2:'路由路径/参数值'
this.$router.push('路由路径')  // 
// 方式二
// 传参方式1:{ path:'路由路径', query: {参数1: 值1, 参数2: 值2 }}
// 传参方式2:{ path:'路由路径/参数值' }
this.$router.push({ path:'路由路径' }) //
  1. name命名路由跳转(适合path路径长的场景)
const router = newVueRouter({
	routes: [
		{ name: "路由名", path: "/path", component: path}
	]
)}
// 传参方式:{ path:'路由路径', query: {参数1: 值1, 参数2: 值2 }}
// 传参方式:{ path:'路由路径', params: {参数1: 值1 }}
this.$router.push({ name: '路由名' path: '路由名' })

嵌套路由配置

const router = newVueRouter({
	routes: [
		{ 
			path: "/", 
			component: index,
			// 通过 children配置项配规则,可以配置嵌套子路由
			children: { path: "/childindex", component: childindex }
		},
		{ path: "/home", component: home }
		{ path: "*", component: NotFind }
	]
)}
<div>
	<!-- 准备二级路由出口 -->
	<router-view></router-view>
</div>
<div class="footer_wrap>
	<router-link to="/a">a</router-link>
	<router-link to="/b">b</router-link>
	<router-link to="/c">c</router-link>
</div>

路由返回上一层

<span @click="$router.back()">
	
</span>

组件缓存 keep-alive

keep-alive:keep-alive是Vue的内置组件,当它包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。keep-alive是一个抽象组件,它自身不会渲染成一个DOM元素,也不会出现在父组件链中。
keep-alive的优点:在组件切换过程中把切换出去的组件保留在内存中,防止重复渲染DOM,减少加载时间及性能消耗,提高用户体验性。

<template>
	<div class="h5-wrapper">
		<keep-alive>
			<router-view></router-view>
		</keep-alive>
	</div>
</template>

keep-alive的三个属性
include:组件名数组,只有匹配的组件会被缓存
exclude:组件名数组,任何匹配的组件都不会被缓存
max:最多可以缓存多少组件实例.

<template>
	<div class="h5-wrapper" :include="['xxxPage']">
		<keep-alive>
			<router-view></router-view>
		</keep-alive>
	</div>
</template>

组件缓存 keep-alive 提供了两个声明周期函数 actived(进入时) 和 deactived(退出时)

自定义创建项目

基于VueCli自定义创建项目架子,使用该方式就会自动配置好路由等配置

安装脚手架 -> 创建项目 -> 自定义(Babel/Router/CSS/Linter)
创建项目:vue create ProjectName,创建时选择Manually select features,选择Babel/Router/CSS/Linter,css选择器选择 Less,规范选择 ESLint + Standard configLint on saveIndedicated config files

五、vuex(多组件共享一组数据)

概念:Vuex是一个vue的状态管理工具,状态就是数据。简单讲vuex是一个插件,可以帮我们管理vue通用的数据(多组件共享的数据)
应用场景:某个状态在很多个组件来使用(个人信息);多个组件共同维护一份数据(购物车)
优势:共同维护一份数据,数据集中化管理;响应式变化;操作简洁(Vuex提供了一些辅助函数)

构建vuex多组件数据共享环境

基于脚手架创建项目,构建vuex多组件数据共享环境
效果是多个组件,共享一份数据:任意一个组件都可以修改数据;多个组件的数据是同步的

  1. 装包:yarn add vuex@3
  2. 新建 store/index.js 专门存放 vuex
  3. 创建仓库 new Vuex.Store()
// 存在vuex相关核心代码
import Vue from 'vue'
import Vuex from 'vuex'

// 插件安装
Vue.use(Vuex)

// 创建仓库(空仓库)
const store = new Vuex.Store()

// 导出给 main.js 使用
export default store
  1. 在 main.js 中导入挂载到Vues实例上
import Vue from 'vue'
import App from './App.vue'
import store from '@/store/index'

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
  store
}).$mount('#app')

组件中访问

<script>
	export default {
		name: 'app',
		created () {
			console.log(this.$store)
		}
	}
</script>

核心概念 - state 状态(数据)

目标:明确如何给仓库提供数据,如何使用仓库的数据

  1. 提供数据:State提供唯一的公共数据源,所有共享的数据都要统一放到Store中的State中存储。在state对象中可以添加我们要共享的数据。(store/index.js)
// 创建仓库(空仓库)
const store = new Vuex.Store({
	// state 类似 vue 的 data
	// data 是自己的数据,state 是所有组件共享的数据
	state: {
		count: 1
	}
})
  1. 使用数据,通过store直接访问或通过辅助函数
<!-- 直接访问 -->
<div>{{ $store.state.count}}</div>
<script>
	export default {
		name: 'app',
		created () {
			// 组件中
			console.log(this.$store.state.count)
			// js中
			console.log(store.state.count)
		}
	}
</script>


<!-- 通过辅助函数访问 -->
<div>{{ count }}</div>
<script>
	import { mapState } from 'vuex'
	
	console.log(mapState(['count', 'title']))
	export default {
		name: 'app',
		computed () {
			...mapState(['count', 'title'])
		}
	}
</script>

核心概念- mutations

目标:明确vuex同样遵循单向数据流,组件中不能直接修改仓库的数据,可以通过 strict:true 可以开启严格模式

  1. 定义mutations对象,对象中存放修改state的方法
const store = new Vuex.Store({
    state: {
        count: 0
    },
    mutations: {
        addCount (state) { // 带参 (state, n)
            state.count += 1
        }
    }
})
  1. 组件中提交调用 mutations:this.$store.commit('addCount') (带参:this.$store.commit('addCount', 10)

辅助函数:mapMutations

目标:掌握辅助函数mapMutations,映射方法
mapMutations和mapState很像,它是把位于mutations中的方法提取了出来,映射到组件methods中

const store = new Vuex.Store({
    state: {
        count: 0
    },
    mutations: {
        addCount (state) { // 带参 (state, n)
            state.count += 1
        }
    }
})
<div>{{ count }}</div>
<script>
	import { mapMutations } from 'vuex'
	
	export default {
		name: 'app',
		computed () {
			...mapMutations(['addCount'])
		}
	}
</script>

调用:this.addCount()

核心概念actions

目标:明确actions的基本语法,处理异步操作。
需求:一秒钟之后,修改state的count成666。
说明:mutations必须是同步的(便于监测数据变化,记录调试)

const store = new Vuex.Store({
    state: {
        count: 0
    },
    mutations: {
        addCount (state, n) { // 带参 (state, n)
            state.count += n
        }
    },
    actions: {
			setAsyncCount (context, num) {
				setTimeout(() => {
					context.commit('addCount', num)
				}, 1000)
			}
		}
})

调用:this.$store.dispatch('setAsycnCount', 200)

辅助函数- mapActions

目标:掌握辅助函数mapActions,映射方法
mapActions是把位于actions中的方法提取了出来,映射到组件methods中

const store = new Vuex.Store({
    state: {
        count: 0
    },
    mutations: {
        addCount (state, n) { // 带参 (state, n)
            state.count += n
        }
    },
    actions: {
			setAsyncCount (context, num) {
				setTimeout(() => {
					context.commit('addCount', num)
				}, 1000)
			}
		}
})
<div>{{ count }}</div>
<script>
	import { mapActions } from 'vuex'
	
	export default {
		name: 'app',
		computed () {
			...mapActions(['setAsyncCount'])
		}
	}
</script>

调用:this.setAsyncCount()

核心概念-getters

目标:掌握核心概念getters的基本语法(类似于计算属性)
说明:除了state之外,有时我们还需要从state中派生出一些状态,这些状态是依赖state的,此时会用到getters

例如:state中定义了list,为1-10的数组,组件中,需要显示所有大于5的数据

const store = new Vuex.Store({
    state: {
        list: [3, 4 ,5 ,6 , 7]
    },
    getters: {
        filterList (state) {
            return state.list.filter(item => item >5)
        }
    }
})

调用:this.$store.getters.filterList
通过mapGetters函数映射

核心概念-模块module(进阶语法)

目标:掌握核心概念module模块的创建
由于vuex使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store对象就有可能变得相当臃肿。(当项目变得越来越大的时候,Vuex会变得越来越难以维护)

模块拆分
在store下创建模块文件,如:store/modules/user.js

const state = {
	uesrInfo: {
		name: 'Tony',
		age: 10
	}
}
const mutations = {}
const actions = {}
const getters = {}
export default {
	mutations,
	actions,
	getters
}

主模块导入

import user from './modules/user'

// 创建仓库(空仓库)
const store = new Vuex.Store({
	modules:{
		user
	}
})

目标:掌握模块中state的访问语法
尽管已经分模块了,但其实子模块的状态,还是会挂到根级别的state中,属性名就是模块名

使用模块中的数据:

  1. 直接通过模块名访问$store.state.模块名.xxx
  2. 通过 mapState 映射:默认根级别的映射mapState(['xxx']),子模块的映射 mapState('模块名',['xxx'])- 需要开启命名空间(namespaced: true)
export default {
	namespaced: true  // 开启命名空间
	mutations,
	actions,
	getters
}

目标:掌握模块中getters的访问语法使用模块中 getters中的数据:

  1. 直接通过模块名访问$store.getters['模块名/xxx']
  2. 通过 mapGetters 映射:默认根级别的映射 mapGetters(['xxx']),子模块的映射 mapGetters('模块名',['xxx'])-需要开启命名空间

目标:掌握模块中mutation的调用语法
注意:默认模块中的mutation和actions会被挂载到全局,需要开启命名空间,才会挂载到子模块。
调用子模块中 mutation:

  1. 直接通过store调用 $store.commit('模块名/xxx',额外参数)
  2. 通过 mapMutations 映射:默认根级别的映射 mapMutations(['xxx'])子模块的映射mapMutations('模块名',['xxx])-需要开启命名空间

目标:掌握模块中action的调用语法(同理- 直接类比mutation即可)
注意:默认模块中的mutation和actions会被挂载到全局,需要开启命名空间,才会挂载到子模块。调用子模块中action:

  1. 直接通过 store调用 $store.dispatch('模块名/xxx',额外参数)
  2. 通过mapActions 映射
    默认根级别的映射 mapActions(['xxx'])子模块的映射mapActions('模块名',['xxx'])-需要开启命名空间

六、vue3

create-vue是Vue官方新的脚手架工具,底层切换到了vite(下一代构建工具),为开发提供极速响应

6.1 Vue3的基本使用

使用create-vue创建项目

条件:已安装16.0或更高版本的Node.js
创建一个Vue应用:npm init vue@latest(这一指令将会安装并执行create-vue)

项目目录和关键文件

  1. vite.config.js - 项目的配置文件基于vite的配置
  2. package.json - 项目包文件核心依赖项变成了Vue3.x和vite
  3. main.js - 入口文件createApp函数创建应用实例
  4. app.vue - 根组件 SFC单文件组件 script - template - style
    变化一:脚本script和模板template顺序调整
    变化二:模板template不再要求唯一根元素
    变化三:脚本script添加setup标识支持组合式API
  5. index.html - 单页入口提供id为app的挂载点
<template>
	<div>{{ message }}</div>
</template>

<!-- 加上setup允许在script中直接编写组合式API -->
<script setup></script>

<style scoped></style>

组合式API - setup选项

原始写法

<template>
	<div>{{ message }}</div>
</template>

<script setup>
export default {
	// setup 执行的比 beforeCreate 早
	// 数据 和 函数,需要在setup最后 return 才能模板中应用
  setup () {
    const message = 'hello'
    const logMessage = () => {
      console.log(message)
    }

    return {
      message,
      logMessage
    }
  },
  beforeCreate () {
    
  }
}
</script>

语法糖写法

<template>
	<div>{{ message }}</div>
</template>

<script setup>
const message = 'hello'
const logMessage = () => {
	console.log(message)
}
</script>

组合式API - computed

计算属性基本思想和Vue2的完全一致,组合式API下的计算属性只是修改了写法

使用步骤:导入computed函数,执行函数 在回调参数中return基于响应式数据做计算的值,用变量接收

<template>
  <div class="dialog">
    {{  state  }}
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'

const list = ref([3, 4, 5, 6, 7])
list.value.push(8)
// 过滤出大于5的值
const computedState = computed(() => {
  return list.value.filter(item => item > 5)
})
console.log(computedState)
</script>

<style scoped></style>

组合式API - watch

作用:侦听一个或者多个数据的变化,数据变化时执行回调函数
俩个额外参数:1.immediate(立即执行)2.deep(深度侦听)

<script setup>
import { ref, watch } from 'vue'

const count = ref(0)
const name = ref('hello')

// 监听单个数据
watch(count, (newValue, oldValue) => {
  console.log(newValue, oldValue)
})

// 监听多个数据
watch(
  [count, name], 
  ([newCount, newName], [oldCount, oldName]) => {
    console.log("数据发生变化")
  })
</script>

immediate:在侦听器创建时立即触发回调,响应式数据变化之后继续执行回调
deep:默认watch 进行的是 浅层监视深度监视,ref(简单类型) 可以直接监视,ref(复杂类型)监视不到复杂类型内部数据的变化,需要deep

// 监听单个数据
watch(count, (newValue, oldValue) => {
  console.log(newValue, oldValue)
}, {
  immediate: true,
  deep: true
})

组合式API-生命周期函数

选项式API组合式API
beforeCreate/createdsetup
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeUnmountonBeforeUnmount
unmountedonUnmounted

6.2 组合式API - reactive和ref函数

默认的数据不是响应式的,这两种数据是响应式的

reactive()

接受对象类型数据的参数传入并返回一个响应式的对象

<template>
  <div class="dialog">
    {{  state.count  }}
  </div>
</template>

<script setup>
import { reactive } from 'vue'

// reactive:接收一个对象类型的数据,返回一个响应式的对象
const state = reactive({ count: 1 })
console.log(state.count)
</script>

<style scoped></style>

ref()

接收简单类型或者对象类型的数据传入并返回一个响应式的对象

<template>
  <div class="dialog">
    {{  state  }}
  </div>
</template>

<script setup>
import { ref } from 'vue'

// reactive:接收一个对象类型的数据,返回一个响应式的对象
const state = ref(0)
// 调用需要使用value
console.log(state.value)
</script>

<style scoped></style>

6.3 组合式API-父子通信

父传子基本思想

  1. 父组件中给子组件绑定属性 <xxx :message='xxx' />
  2. 子组件内部通过props选项接收
<xxx :message='xxx' />
<template>
  <div class="dialog">
    {{  message  }}
  </div>
</template>

<script setup>
import { ref, watch } from 'vue'

// 接收父组件传入的值
const props = defineProps({
  message: String
})
</script>

<style scoped></style>

子传父基本思想

  1. 父组件中给子组件标签通过@绑定事件 <xxx @message='xxxfunc' />
  2. 子组件内部通过emit方法触发事件
<template>
  <div class="dialog">
    {{  message  }}
  </div>
</template>

<script setup>
import { ref, watch } from 'vue'

const emit = defineEmits(['message'])
const sendMsg = () => {
	emit('message', 'data')
}
</script>

<style scoped></style>

组合式API-模版引用

通过ref标识获取真实的dom对象或者组件实例对象

  1. 调用ref函数生成一个ref对象
  2. 通过ref标识绑定ref对象到标签

defineExpose()
默认情况下在<script setup>语法糖下组件内部的属性和方法是不开放给父组件访问的,可以通过defineExpose编译宏指定哪些属性和方法允许访问

<template>
  <xxx ref='testRef' />
</template>

<script setup>
import { ref, onMounted } from 'vue'

const testRef = ref(null)
onMounted(() => {
	// 调用某子函数  (下面子组件的sendMsg函数)
	inpref.value.sendMsg()
})
</script>

<style scoped></style>
<script setup>
import { ref, onMounted } from 'vue'

const message = ref('')
consr sendMsg = () => {
	console.log('hello')
}

defineExpose({
	message,
	sendMsg
})
</script>

组合式API -provide和inject

顶层组件向任意的底层组件传递数据和方法,实现跨层组件通信

跨层传递普通数据(可以传递数据、函数等,在子层级中调用父层级的函数)

  1. 顶层组件通过provide函数提供数据provide('key',顶层组件中的数据)
  2. 底层组件通过inject函数获取数据const message = inject('key')
<script setup>
import { provide } from 'vue'

provide('message''hello')
</script>
<script setup>
import { inject } from 'vue'

const message = inject('message')
</script>

6.4 Vue3.3 新特性

Vue3.3新特性-defineOptions

背景说明:
<script setup>之前,如果要定义props,emits可以轻而易举地添加一个与setup平级的属性。
但是用了<script setup>后,就没法这么干了setup属性已经没有了,自然无法添加与其平级的属性。

了解决这一问题,引入了defineProps 与 defineEmits 这两个宏,但这只解决了props与emits这两个属性。
如果我们要定义组件的name或其他自定义的属性,还是得回到最原始的用法,再添加一个普通的<Script>标签。
这样就会存在两个<script>标签。让人无法接受。

所以在Vue 3.3中新引入了 defineOptions宏。顾名思义,主要是用来定义Options API的选项。可以用
defineOptions 定义任意的选项,props,emits,expose,slots 除外(因为这些可以使用definexXX来做到)

<script setup>
defineOptions({
	name: 'xxx',
	inheritAttrs: false,
	// ...
})
</script>

Vue3.3新特性-defineModel

在Vue3中,自定义组件上使用y-model,相当于传递一个modelValue属性,同时触发 update:modelValue 事件

<Child v-model="isVisible">
//相当于
<Child :modelValue="isVisible" @update:modelValue="isVisible=$event">

我们需要先定义props,再定义emits。其中有许多重复的代码。如果需要修改此值,还需要手动调用emit函数

<script setup>
	import { defineModel } from 'vue'
	 const modelValue = defineModel()
	 modelValue.value++
</script>

还需要在vite.config.js中配置

export default defineConfig({
	plugins: [
		vue({
			script: {
				defineModel: true
			}
		}),
	],
	xxx
})

七、项目发布

打包指令:yarn build
在项目根目录自动创建一个文件夹 dist ,里面的为打包后的文件,需要放到服务器中
默认情况,需要放到服务器根目录打开,如果希望双击运行,需要配置 vue.config.js 相对路径publicPath: './'

八、相关组件库

组件库介绍
json-server模拟后端的数据库(文档链接
Piniavuex的替代品(文档链接
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值