vue的一些概念
- Vue:一套用于构建用户界面的渐进式javascript框架
- 渐进式:逐渐增强,通过学习,可以集成更多的功能
- 库:一些方法的集合
- 框架:一套完整的项目解决方案
- MVVM:一种软件架构模式,通过数据双向绑定让数据自动地双向同步,不再需要操作DOM
组件化和模块化的区别
模块化:一个独立的js文件就是一个模块
组件化:一个组件会包含(HTML+CSS+JS) 把一个完整的页面拆分成多个组件构成,可以完成整个结构样式行为的拆分复用
组件化的优点:1.容易维护;2.便于复用
vue/cli脚手架
vue官方提供的一个全局命令工具,可以帮我们快速创建一个vue项目的基础架子(减少我们花在配置webpack上的精力)
优点:
-
开箱即用;
-
零配置(不用配置webpack);
vue主动隐藏了webpack.config.js,可以创建一个vue.config.js文件,覆盖webpack配置文件
-
内置babel(语法降级)等工具
命令行安装(只装一次):yarn global add @vue/cli;验证vue --version
-
创建项目
vue create 项目名(不能用中文) -
启动项目
yarn serve -
覆盖webpack的配置
// 配置端口和自动打开浏览器 // module.exports模块导出,详见nodejs-d3 module.exports = { devServer: { open: true, port: 3000 } }
目录分析与清理
- public/index.html不用动,提供一个最基础的页面
- src/main.js不用动, 渲染了App.vue组件
- src/App.vue默认有很多的内容,可以全部删除
- assets文件夹与components直接删除
单文件组件
一个.vue文件就是一个组件,后续开发vue,所有的功能都是基于组件实现。
一个单文件组件由三部分构成:
- template(必须) 影响组件渲染的结构 html
- 只能有一个根元素
- script 逻辑 js
- style 样式 css less scss
- style用于提供组件的样式,默认只能用css
- 可以通过
lang="less"开启less的功能,需要安装依赖包
yarn add less-loader@7.2.1 less -D
// less-loader处理less文件
// less识别less语法
vue通过data提供数据
<script>
// 默认导出组件的配置项
export default {
data () {
return {
// data提供的数据=>组件实例
money: 100,
msg: 'hello'
}
}
}
</script>
插值表达式
注意点:
- 数据在 data 中要存在
- 不能在标签属性中使用
- 能使用表达式, 支持点语法和三元运算符,但是不能使用 if for
v-bind
动态设置元素的属性
// 简写
:属性名="属性值"
// 完整语法 v-bind:属性名="属性值"
不写:的话,“属性值”就被当作(默认的)字符串了,没有了动态的效果
v-on
注册事件
// 三种语法形式
v-on:事件名=“要执行的少量代码" // 可直接组件实例
v-on:事件名=“methods中的函数名" // 想使用组件实例,必须带上`this.` 前缀
v-on:事件名=“methods中的函数名(实参)" // 想使用组件实例,必须带上`this.` 前缀
// 简写:
`@事件名`代替`v-on:事件名`
获取事件对象
-
无实参, 直接使用形参
e -
有实参, 实参使用
$event,形参使用e
<a @click="fn" href="http://www.baidu.com">去百度</a>
<a @click="fn2(100, $event)"
methods: {
fn(e) {
e.preventDefault()
},
fn2(num, e) {
e.preventDefault()
}
}
事件修饰符
.prevent 阻止默认行为
.stop 阻止冒泡
<div id="app">
<a @click.prevent="fn" href="http://www.baidu.com">去百度</a>
</div>
按键修饰符
监听特殊按键的键盘事件
-
@keyup.enter回车 -
@keyup.esc返回
v-if 和 v-show
控制盒子的显示隐藏
v-show="布尔值" (true显示, false隐藏)
v-if="布尔值" (true显示, false隐藏)
区别:
v-show 实质是在控制元素的 css 样式为 display: none;
v-if 实质是在动态的创建 或者 删除元素节点
用法:
频繁的切换显示隐藏, 用 v-show
不用频繁切换, 要么显示, 要么隐藏的情况, 适合于用 v-if
v-else 和 v-else-if
按条件控制同一位置的不同元素显示/隐藏
v-if/v-else-if/v-else必须紧贴在一起
<div id="app">
<h1 v-if="age >= 60">60岁以上, 广场舞</h1>
<h1 v-else-if="age >= 30">30岁以上, 搓麻将</h1>
<h1 v-else-if="age >= 20">20岁以上, 蹦迪</h1>
<h1 v-else>20岁以下, 唱跳rap篮球</h1>
</div>
v-model(★★★)
给表单元素使用, 双向数据绑定
<template>
<div>
<!-- prevent修饰符阻止默认跳转 -->
<form @submit.prevent="submit">
<!-- number修饰符一般配合number框一起使用 -->
账号:<input type="number" v-model.number="form.account" /><br />
密码:<input type="password" v-model="form.password" /><br />
<!-- 一组单选框需要配置value值 -->
性别:
男<input type="radio" v-model="form.sex" value="male" />
女<input
type="radio"
v-model="form.sex"
value="female"
/><br />
<!-- 一组复选框需要配置value值,并用v-modle绑定一个新数组来收集这些值,这个新数组影响复选框的初始值 -->
爱好:
学习<input type="checkbox" v-model="form.hobby" value="学习" />
打游戏<input type="checkbox" v-model="form.hobby" value="打游戏" />
吃饭<input
type="checkbox"
v-model="form.hobby"
value="吃饭"
/><br />
<!-- 一组下拉框select配置v-model,option配置value -->
地区:
<select v-model="form.city">
<option value="">--请输入地区--</option>
<option value="beijing">北京</option>
<option value="shanghai">上海</option>
<option value="guangzhou">广州</option>
<option value="shenzhen">深圳</option>
</select><br />
<!-- lazy修饰符只在失焦时收集数据 -->
其他信息:
<textarea cols="30" rows="10" v-model.lazy="form.other"></textarea><br />
<!-- 单个复选框v-model默认绑定的是checked -->
<input type="checkbox" v-model="form.agree" />阅读并接受<a href="#">《用户协议》</a><br />
<button>提交</button>
</form>
</div>
</template>
<script>
export default {
data() {
return {
form:{
account: '',
password: '',
sex: 'male',
hobby: ['吃饭'],
city: '',
other: '',
agree: false,
}
}
},
methods:{
submit() {
console.log(this.form)
}
}
}
</script>
<style></style>
v-model 修饰符(★)
.number转数字,以parseFloat转成数字类型.trim去除首尾空白.lazy懒触发(失焦时触发),在change时而非input时更新
v-text 和 v-html
相当于innerText和innerHTML
v-text不常用
v-html会在后端要求输出一段转为字符串的标签时使用到
v-for
一般遍历数组(一般不遍历对象)
遍历数字时,相当于将标签复制数值对应的次数(★)
v-for="(item, index) in arr或数字" :key="数组中具有唯一性的变量"
// item和index不能反
v-bind 对于class和style的增强
动态的操作class和style
// :class 不会影响到原来的 class 属性
// 允许使用对象或者数组
// 对象(常用):如果键值对的值为true,那么就有这个,否则没有这个类
// 数组:数组中所有的类都会添加到盒子上
vue的就地复用策略(★)
就地复用:Vue会尽可能的就地(同层级,同位置),对比虚拟dom,复用旧dom结构,进行差异化更新。
好处:可以复用旧的dom结构,更新高效
虚拟dom:本质就是在内存中创建的一个保存节点信息, 属性和内容的 描述真实dom的 JS 对象
diff算法(★)
策略1:先同层级根元素比较,如果根元素变化,那么不考虑复用,整个dom树删除重建果;如果根元素不变,对比出属性的变化更新,并考虑往下递归复用。
策略2:对比同级兄弟元素时,默认按照下标进行对比复用;如果果指定了 key,就会按照相同 key 的元素来进行对比复用。
key的作用
- 设置 和 不设置 key 有的区别:
- 不设置 key, 默认同级兄弟元素按照下标进行比较
- 设置了key,按照相同key的新旧元素比较
- key值要求:
- 字符串或者数值,唯一不重复
- 有 id 用 id, 有唯一值用唯一值,实在都没有,才用索引
- key的好处:
- 提高虚拟DOM的对比复用性能
filters 过滤器
常用于文本的格式化
定义过滤器:
局部过滤器: 组件私有的过滤器,可以在 filters 节点中定义过滤器,该过滤器只能在当前组件中调用
export default {
filters: {
upper (input) {
return input.slice(0, 1).toUpperCase() + input.slice(1)
}
}
}
全局过滤器: 如果希望在多个 vue 组件之间共享过滤器,可以在main.js中通过Vue.filter()方法定义全局过滤器
// 参数1: 过滤器名称
// 参数2: 过滤器函数,处理过滤逻辑
Vue.filter('upper', function (input) {
return input.slice(0, 1).toUpperCase() + input.slice(1)
})
调用多个过滤器
过滤器可以串联地进行调用
<template>
<!-- 把msg的值,交给filterA进行处理 -->
<!-- 把filterA处理的结果,再交给filterB进行处理 -->
<!-- 最终把filterB处理,把最终的值渲染到页面 -->
<div>{{msg | filterA | filterB}}</div>
</template>
案例:让单词首字母大写,且最多显示10个字符
过滤器传参
在使用过滤器的时候,可以额外传递参数
// 使用过滤器的时候可以传递额外的参数
<p>{{msg | filterA(arg1, arg2)}}</p>
// 定义过滤器的时候,需要接收额外的参数
filters: {
// 建议给过滤器的额外参数提供默认值
filterA (input, arg1 = 0, arg2 = 1) {
}
}
computed 计算属性
一个特殊属性, 值依赖于另外一些数据动态计算出来
注意点:
1. 计算属性必须定义在 computed 节点中
2. 计算属性必须是一个 function,计算属性必须有返回值
3. 计算属性不能被当作方法调用, 要作为属性来用,以不要和data里重名
好处:
- 计算完了一次,就会自动进行缓存
- 如果依赖项不变, 下次使用直接从缓存取
- 依赖项改变, 函数自动执行并重新缓存
计算属性的完整写法
应用:
-
计算属性默认情况下只能获取,不能修改
-
要给计算属性赋值,就必须写计算属性的完整写法
computed: {
// 默认情况下只能获取,不能修改
// fullName() {
// return this.firstName + ' ' + this.lastName
// },
fullName: {
// 获取时调用
get() {
return this.firstName + ' ' + this.lastName
},
// 设置时调用 value可以拿到最新值
set(value) {
this.firstName = value.split(' ')[0]
this.lastName = value.split(' ')[1]
console.log('我调用了', value)
}
}
}
全选反选案例(★)
<template>
<div>
<span>全选:</span>
<input type="checkbox" v-model="checkAll" />
<button>反选</button>
<ul>
// 这里的每一个checkbox都被一个li包裹,所以他们是独立的每一个checkbox,适用于v-model绑定的是checked选中状态
<li v-for="item in arr" :key="item.name">
<input type="checkbox" v-model="item.c" />
<span>{{ item.name }}</span>
</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
arr: [
{
name: '猪八戒',
c: false,
},
{
name: '孙悟空',
c: false,
},
{
name: '唐僧',
c: false,
},
{
name: '白龙马',
c: false,
},
],
}
},
computed: {
// 老写法: 计算属性只能获取
// checkAll() {
// return this.arr.every((item) => {
// return item.c === true
// })
// },
// 新写法: 计算属性既能获取. 又能设置
checkAll: {
get() {
return this.arr.every((item) => {
return item.c === true
})
},
set(value) {
this.arr.forEach((item) => {
// 赋值
item.c = value
})
},
},
},
}
</script>
<style></style>
watch 属性监听(★)
可以侦听到 data/computed 属性值的改变
如果监听的是复杂数据类型,需要深度监听,需要指定deep为true, 需要用到监听的完整的写法
何让侦听器函数马上执行一次呢?immediate: true
// 基本使用
watch: {
// 只要属性发生了改变,这个函数就会执行
// 参数1: value 变化后的值
// 参数2: oldValue 变化前的值
简单数据名 (value, oldValue) {
}
}
/*
watch: {
msg (value, oldValue) {
console.log('你变了', value, oldValue)
}
}
*/
// 1. 默认情况下,watch只能监听到简单类型的数据变化,如果监听的是复杂类型,只会监听地址是否发生改变,不会监听对象内部属性的变化。
// 2. 需要使用监听的完整写法 是一个对象
watch: {
复杂数据名: {
// handler 数据发生变化,需要执行的处理程序
// deep: true 如果true,代表深度监听,不仅会监听地址的变化,还会监听对象内部属性的变化
// immediate: 立即 立刻 是否立即监听 默认是false 如果是true,代表页面一加载,会先执行一次处理程序
// value: 数据最新值
handler (value) {
console.log(value)
},
deep: true,
immediate: true
}
}
/*
watch: {
friend: {
handler (value) {
console.log('你变了', value)
},
deep: true,
immediate: true
}
}
*/
组件化开发
组件:可复用的 Vue 实例, 封装标签, 样式和JS代码
什么时候封装组件:遇到重复标签, 可复用的时候
组件化 :运用封装的思想,把页面上 可复用的部分 封装为 组件,方便项目的 开发 和 维护
组件化开发的好处:
- 提高了 复用性和灵活性
- 提升了 开发效率 和 后期可维护性
创建和使用组件步骤
-
在
components文件夹中创建.vue文件 – 标签 – 样式 – JS -
引入组件(全局 / 局部)
import HmHeader from './components/HmHeader' -
注册组件 (全局 / 局部)
-
全局
// 短横线命名法 Vue.component('hm-header', HmHeader) // 大驼峰命名法(推荐) Vue.component('HmHeader', HmHeader) // 区别: // 使用 短横线命名法 时 只能使用`<hm-header> </hm-header>` // 使用 大驼峰命名法 时 `<HmHeader> </HmHeader>` 和 `<hm-header> </hm-header>` 都可以 -
局部
<script> export default { components: { HmHeader } } </script>
-
-
使用组件 (组件名用作标签)
-
全局注册的组件 可以在任意的组件中去使用
-
局部注册的组件 只可以在注册时所在的组件中使用
<template> <div> <!-- 组件注册好了,就跟使用html标签一样了 --> <HmHeader></HmHeader> <HmContent></HmContent> <HmFooter></HmFooter> </div> </template>
-
修改组件在开发者工具中显示的名字
在目录结构复杂时,组件选项name可以让开发者在开发者工具中快速找到目标文件
// 假设此时在HmHeader组件(局部)中
<template>
<div>
</div>
</template>
<script>
export default {
// 指定name后,仅仅是组件在开发者工具中显示的名字变成xxx(name基本等于无用属性)
name:'xxx'
}
</script>
// 假设此时在main.js(全局)中
import HmHeader from './components/HmHeader'
Vue.component(HmHeader.name, HmHeader)
scoped解决组件间样式冲突
-
默认情况下,写在组件中的样式会全局生效
-
给当前组件的
style标签添加scoped属性后,当前组件中的样式只会局部有效
<style lang="less" scoped>
div {
background-color: pink;
}
</style>
scoped原理:
- 当前组件内的标签都被添加 data-v-hash值 的属性
- 当前组件内的css选择器都被添加 [data-v-hash值] 的属性选择器
组件通信
每个组件的数据是独立的(在各自的data中), 组件数据无法互相直接访问
组件通信的方式:父传子、子传父等
被引入的是子
父传子
步骤:
-
在父组件中,给子组件的标签添加属性
<Son price="100" title="不错" :info="msg"></Son> -
子组件中, 通过组件选项
props接收props: ['price', 'title', 'info']
v-for 遍历展示组件
<template>
<div class="container">
<h3>我是app组件的内容</h3>
<my-product
v-for="item in list" :key="item.id"
:price="item.proprice"
:title="item.proname"
:info="msg">
</my-product>
</div>
</template>
props校验
子组件通过props默认以数组的形式接收父组件传递的数据且不会进行校验,如果希望校验, 需要提供对象形式的 props
①prop默认以数组接收
<script>
export default {
props: ['price', 'title', 'info']
}
</script>
②希望对接收的数据校验时, props以对象接收
props 提供了多种数据验证方案,例如:
- 基础的类型检查 Number
- 多个可能的类型 [String, Number]
- 必填项校验 required: true
- 默认值 default: 100
- 自定义验证函数
// 希望对接收的数据校验时, 需要提供对象形式的 props
<script>
export default {
props: ['price', 'title', 'info']
}
</script>
单向数据流
在vue中需要遵循单向数据流原则:
- 父组件的数据发生了改变,子组件会自动跟着变
- 子组件不能直接修改父组件传递过来的props,props是只读的 3. 如果父组件传给子组件的是一个对象,子组件修改对象的属性,是不会报错的,但也应该避免
子传父
步骤:
-
子组件可以通过
this.$emit('事件名', 参数1, 参数2, ...)触发事件的同时传参的<template> <div id="product"> ... // 第一步:注册事件 <button @click="cheapBuy">砍价</button> </div> </template> <script> export default { ... methods: { // 第二步:事件处理函数 cheapBuy () { // 第三步:传递自定义事件和实参 this.$emit('sayPrice', this.id) } }, } </script> <style> </style> -
父组件可以给子组件注册对应的自定义事件,并提供对应的函数接收参数
<template> <div id="app"> <my-product ... // 第四步:添加自定义事件 @sayPrice="reducePrice"> </my-product> </div> </template> <script> export default { ... methods: { // 第五步:自定义事件的处理函数和形参 reducePrice (id) { const produt=this.list.find(item=>item.id===id) } produt.price -= 100 }, } </script> <style> </style>
非父子组件通信
语法:
-
创建事件总线
src/EventBus/index.js – 创建空白Vue对象并导出
cd src md EventBus cd EventBus new-item index.js// 在 EventBus/index.js 中,创建空白Vue对象并导出 import Vue from 'vue' const eventBus = new Vue() export default eventBus // 在要通信的两个组件中都引入eventBus // (路径中如果目标文件的文件名为index,可以省略) import eventBus from '../EventBus' -
在监听事件的组件(类似于子传父的父) eventBus.$on('事件名', 函数)
<template> <div class="rose"> <h3>我是rose</h3> <div>等着jack来找俺</div> {{ msg }} </div> </template> <script> import eventBus from '../EventBus/index' // 跟中介打招呼 越早越好 export default { // created会在组件创建时触发,在此时就跟中介打招呼 created() { // this => vue实例 rose console.log(this) console.log('我是rose,我要跟中介打招呼了') // 2. 跟中介打好招呼,等着jack来电 eventBus.$on('roseWaitJack', (info) => { console.log(info) // this => vue实例 rose this.msg = info }) }, data() { return { msg: '', str: '台风会走位,回来了,我先走了', } } } </script> -
在触发事件的组件(类似于子传父的子) eventBus.$emit('事件名', 值)
<template> <div class="jack"> <h3>我是jack</h3> <button @click="clickFn">我想跟rose说话</button> {{ str2 }} </div> </template> <script> import eventBus from '../EventBus' export default { data() { return { msg: 'you jump, i look look', str2: '', } }, methods: { clickFn() { // 告诉中介,想和rose通话 // 通话线路是roseWaitJack,要说的话是this.msg eventBus.$emit('roseWaitJack', this.msg) }, }, } </script>
vue组件的生命周期
Vue实例的生命周期就是它从创建 到 销毁 的整个过程
生命周期函数(钩子函数)
生命周期函数:是由 vue 框架提供的内置函数,会伴随着组件的生命周期,自动按次序执行。
三大阶段和8个方法
- 初始化阶段
- beforeCreate:data数据初始化之前,组件还没有数据
- created: data数据初始化之后,可以获取到组件的数据
- beforeMount:DOM渲染之前,DOM还没渲染
- mounted:DOM渲染之后,可以操作DOM了
- 运行阶段
- beforeUpdate: 数据更新,DOM更新前
- updated: 数据更新,DOM更新后
- 销毁阶段
- beforeDestroy: 组件销毁前
- destroyed: 组件销毁后
八个生命周期钩子函数中,最常用的两个:
created可以开始发送ajax请求,获取数据
mounted可以开始操作dom了
ref 和 $refs获取 dom元素或组件实例
步骤:
-
给需要获取的 dom 元素或者组件, 添加 ref 属性
<template> <div> <div ref="box">我是div盒子</div> <jack ref="jack"></jack> <button @click="fn">按钮</button> </div> </template> -
通过
this.$refs.xxx获取, 拿到组件可以调用组件的方法import Jack from './jack.vue' export default { methods: { fn () { console.log(this.$refs.box) console.log(this.$refs.jack) this.$refs.jack.sayHi() } }, components: { Jack } }
$nextTick等待DOM更新
data改变,更新DOM是异步的,this.$nextTick里的函数体会在DOM完成更新后才执行,此时才可以获取到更新好的DOM
<template>
<div>
<!-- 需求: 点击按钮, 切换显示输入框 -->
<input ref="inp" type="text" v-if="isShowInput">
<button @click="fn" v-else>点此搜索</button>
</div>
</template>
<script>
export default {
data () {
return {
isShowInput: false
}
},
methods: {
fn () {
this.isShowInput = true
this.$nextTick(() => {
// 显示输入框后立即获取焦点
// 由于原本vue更新DOM是异步的,所以使用$nextTick让获取焦点在DOM更新完成才执行
this.$refs.inp.focus()
})
}
}
}
</script>
dynamic 动态组件
解决多组件同一位置, 切换显示的需求
步骤:
- 准备被切换的 2个组件, 并引入注册
- 准备变量来承载要显示的"组件名"
- 设置挂载点
<component>, is属性设置要显示的组件 - 点击按钮 – 修改comName变量里的"组件名" ( 修改 is 的值)
<template>
<div>
<h3>动态组件的演示</h3>
<!-- 动态组件 => 多个组件使用同一个挂载点, 并可以动态的切换展示 -->
<button @click="comName = 'MySwiper'">swiper</button>
<button @click="comName = 'MyNav'">nav</button>
<component :is="comName"></component>
</div>
</template>
<script>
import MyNav from './MyNav.vue'
import MySwiper from './MySwiper.vue'
export default {
components: {
MyNav,
MySwiper
},
data () {
return {
comName: 'MyNav'
}
}
}
</script>
keep-alive 保持组件的状态
默认情况下,切换动态组件时无法保持组件的状态。会将组件销毁, 将来显示时, 又会重新创建
- keep-alive 包裹动态组件时,会缓存不活动的组件实例
- 缓存的组件是不会被销毁的,所以beforeDestroy和Destroyed两个钩子函数不会执行
- 缓存组件有两个新增的钩子函数
- activated( ) { } 缓存组件激活时触发
- deactivated( ) { } 缓存组件失活时触发
<template>
<div>
<h3>动态组件的演示</h3>
<!-- 动态组件 => 多个组件使用同一个挂载点, 并可以动态的切换展示 -->
<button @click="comName = 'MySwiper'">swiper</button>
<button @click="comName = 'MyNav'">nav</button>
<keep-alive>
<component :is="comName"></component>
</keep-alive>
</div>
</template>
<script>
import MyNav from './MyNav.vue'
import MySwiper from './MySwiper.vue'
export default {
components: {
MyNav,
MySwiper
},
data () {
return {
comName: 'MyNav'
}
},
// 缓存组件激活的时候触发
activated () {
console.log('组件激活了')
},
// 缓存组件失活的时候触发
deactivated () {
console.log('组件失活了')
}
}
</script>
插槽
能够自定义组件内部的一些结构
插槽基本语法:
- 组件内用
<slot></slot>占位 - 使用组件时
<MyDialog></MyDialog>夹着的地方, 传入标签替换slot
// 组件内
<template>
<div class="MyDialog">
<div class="header">
<h3>友情提示</h3>
</div>
<div class="content">
// 插槽位置
<slot></slot>
</div>
<div class="footer">
<button>关闭</button>
</div>
</div>
</template>
// 使用组件时
<template>
<div id=>
<MySwiper>
<component :is="comName"></component>
</MySwiper>
</div>
</template>
<script>
import MySwiper from './MySwiper.vue'
export default {
components: {
MySwiper
}
}
</script>
后备内容(slot的默认内容)
<slot> 插槽可以包裹后备内容,如果组件的使用者没有为插槽提供任何内容,则后备内容会生效
<template>
<div class="my-dialog">
<div class="content">
<slot>这是后备内容</slot>
</div>
</div>
</template>
具名插槽
插槽的分类:
-
默认插槽(匿名插槽)
<slot></slot>等价于<slot name="default"></slot> -
具名插槽
具有名字的插槽, 可以实现定向分发
具名插槽的使用步骤:
-
给插槽起名字
<div class="header"> <slot name="header"></slot> </div> <div class="content"> <slot>这是后备内容</slot> </div> <div class="footer"> <slot name="footer"></slot> </div> -
需要使用 template 标签, 将内容包裹成一个整体
-
通过
v-slot:插槽名, 指定具体分发给谁(可以用#替换v-slot:)<my-dialog> <template v-slot:header> <h3>这是大标题</h3> </template> <template v-slot:default> <p>这是内容</p> </template> <template v-slot:footer> <button>确认</button> <button>取消</button> </template> </my-dialog>
作用域插槽
给插槽绑定数据,让对应的组件可以使用这些数据
步骤:
-
给 slot 标签, 以 添加属性的方式传值
<slot name="bottom" :yes="yes" :no="no" money="100"></slot> -
所有属性都会被收集到一个对象中
-
template中, 通过
v-slot:插槽名= "obj"接收,也可以使用解构赋值简化数据的接收<template #bottom="obj"> <!-- {{ obj }} --> <button>{{ obj.yes }}</button> <button>{{ obj.no }}</button> <button>{{ obj.money }}</button> </template> // 或 <template #bottom="{ yes, no, money }"> <button>{{ yes }}</button> <button>{{ no }}</button> <button>{{ money }}</button> </template>
自定义指令
自己定义指令, 封装dom操作,扩展额外功能
局部注册:
<template>
<div>
<h3>自定义指令</h3>
// 以 v-指令名 方式使用
<div v-color="color">我是内容</div>
</div>
</template>
<script>
export default {
data(){
return {
color:'green'
}
},
directives: {
// 自定义一个局部指令
// 指令名后跟一个对象
color: {
// 钩子函数inserted,在指令所在的元素渲染的时候触发
inserted (el, binding) {
// 第一个参数el,代表指令所在标签的DOM元素
// binding.value,代表指令后面跟着的值的最新值
el.style.color = binding.value
},
// 钩子函数update,在指令所在的元素改变时触发
update (el, binding) {
el.style.color = binding.value
}
}
}
}
</script>
全局注册:
Vue.directive('指令名', {
inserted (el) {
el.focus()
}
})
单页应用程序与路由
SPA - 单页应用程序
SPA: Single Page Application 单页面应用程序
MPA : Multiple Page Application多页面应用程序
定义:
SPA是只有一张Web页面的应用,是加载单个HTML 页面并在用户与应用程序交互时动态更新该页面的Web应用程序
优点:
-
不整个刷新页面,每次请求仅获取需要的部分,用户体验更好
-
数据传递容易, 开发效率高
缺点:
- 开发成本高(需要使用vue-router)
- 首次加载会比较慢一点,不利于seo
路由介绍
路由 : 是浏览器 URL 中的哈希值( # hash) 与 展示视图内容(组件) 之间的对应规则
- 路由是由开发人员制定的一套映射规则
- 当 URL 中的哈希值(
#hash) 发生改变后,路由会根据制定好的规则, 展示对应的视图内容(即切换到对应的组件)
vue 中的路由 : 是 hash 和 component 的对应关系, 一个哈希值对应一个组件
前端路由 - 工作模式原理
基本思路:
- 用户点击了页面上的路由链接
- 导致了 URL 地址栏中的 Hash 值发生了变化
- 前端路由监听了到 Hash 地址的变化
- 前端路由把当前 Hash 地址对应的组件渲染都浏览器中
实现简单的前端路由:
- 导入并注册
my-home.vuemy-moviemy-about三个组件
<script>
import MyAbout from './components/my-about.vue'
import MyHome from './components/my-home.vue'
import MyMovie from './components/my-movie.vue'
export default {
components: {
MyHome,
MyAbout,
MyMovie
}
}
</script>
- 通过 comName 动态组件, 控制要显示的组件
<template>
<div>
<h1>App组件</h1>
<component :is="comName"></component>
</div>
</template>
<script>
import MyAbout from './components/my-about.vue'
import MyHome from './components/my-home.vue'
import MyMovie from './components/my-movie.vue'
export default {
data () {
return {
comName: 'my-home'
}
},
components: {
MyHome,
MyAbout,
MyMovie
}
}
</script>
- 声明三个导航链接, 点击时修改地址栏的 hash 值
<template>
<div>
<h1>App组件</h1>
<a href="#/home">首页</a>
<a href="#/movie">电影</a>
<a href="#/about">关于</a>
<component :is="comName"></component>
</div>
</template>
- 在 created钩子函数 中, 监视地址栏 hash 的变化, 一旦变化, 动态切换展示的组件
created () {
// 原生JS中window.onhashchange可以监视监视地址栏 hash 的变化
window.onhashchange = () => {
// location.hash可以获取当前地址的hash
console.log(location.hash)
switch(location.hash) {
case '#/home':
this.comName = 'my-home'
break
case '#/movie':
this.comName = 'my-movie'
break
case '#/about':
this.comName = 'my-about'
break
}
}
},
vue-router
路由 - 组件分类
.vue文件本质无区别, 一般用法如下:
-
views下的.vue文件是页面组件, 配合路由切换,
-
components下的.vue文件是复用组件,一般引入到views下的.vue中复用展示数据
创建路由
App.vue - 页面标签和样式准备
<template>
<div>
<div class="footer_wrap">
<a href="#/find">发现音乐</a>
<a href="#/my">我的音乐</a>
<a href="#/part">朋友</a>
</div>
<div class="top">
</div>
</div>
</template>
<script>
export default {};
</script>
<style scoped>
.footer_wrap {
position: fixed;
left: 0;
top: 0;
display: flex;
width: 100%;
text-align: center;
background-color: #333;
color: #ccc;
}
.footer_wrap a {
flex: 1;
text-decoration: none;
padding: 20px 0;
line-height: 20px;
background-color: #333;
color: #ccc;
border: 1px solid black;
}
.footer_wrap a:hover {
background-color: #555;
}
.top {
padding-top: 62px;
}
</style>
步骤:
- 安装
yarn add vue-router
- 导入路由
// 在main.js中
import VueRouter from 'vue-router'
- 使用路由插件
// 在vue中,使用使用vue的插件,都需要调用Vue.use()
Vue.use(VueRouter)
- 创建路由对象
const router = new VueRouter({
})
- 关联到vue实例
new Vue({
router
})
配置路由规则
// 4. 创建一个路由对象
const router = new VueRouter({
// 路由的规则routes
routes: [
{
// 路径 锚点
// 组件
path: '/find',
component: Find,
},
{
path: '/my',
component: My,
},
{
path: '/friend',
component: Friend,
},
],
})
指定路由的出口
<div class="top">
<!-- 路由的出口:将来路由对应的组件显示位置 -->
<router-view></router-view>
</div>
路由的封装
- 新建文件
router/index.js
// 在router/index.js中
import Vue from 'vue'
// 导入VueRouter
import VueRouter from 'vue-router'
import Find from '../views/Find'
import My from '../views/My'
import Friend from '../views/Friend'
// 使用vue插件 vue的插件想要生效必须调用Vue.use()方法
Vue.use(VueRouter)
// 创建一个路由对象
const router = new VueRouter({
// 路由的规则
// route: 一条路由规则
routes: [
{
// 路径 锚点
// 组件
path: '/find',
component: Find,
},
{
path: '/my',
component: My,
},
{
path: '/friend',
component: Friend,
},
],
})
export default router
- main.js中
import router from './router'
new Vue({
// 关联路由对象和vue实例
render: (h) => h(App),
router,
}).$mount('#app')
vue路由 - 声明式导航
声明式导航 - 基础使用
- vue-router提供了一个全局组件 router-link
- router-link实质上最终会渲染成a链接 to属性等价于提供 href属性(to无需#)
- router-link提供了声明式导航高亮的功能(自带类名)
<template>
<div>
<div class="footer_wrap">
<!--
router-link
1.将来还是会渲染成a
2.to 不加#
-->
<router-link to="/find">发现音乐</router-link>
<router-link to="/my">我的音乐</router-link>
<router-link to="/part">朋友</router-link>
</div>
<div class="top">
<!-- 组件展示在这里 -->
<!-- 路由出口: 将来路由对应的组件显示位置 -->
<router-view></router-view>
</div>
</div>
</template>
<script>
export default {};
</script>
<style scoped>
/* 省略了 其他样式 */
.router-link-exact-active,
.router-link-active {
background-color: #555 !important;
}
</style>
导航高亮
RouterLink会自动给当前的链接添加两个类名:
-
router-link-active: 激活的导航链接 模糊匹配
将来地址栏的地址 必须和 to="/my" 完全相同 才会加这个类名
-
router-link-exact-active: 激活的导航链接 精确匹配
将来地址栏的地址只要是以/find 开头 to="/find/..." 就会加这个类名
<RouterLink to="/" exact>发现音乐</RouterLink>
<RouterLink to="/my">我的音乐</RouterLink>
<RouterLink to="/friend">朋友</RouterLink>
可以修改默认高亮的类名
const router = new VueRouter({
// 修改高亮类名
// 模糊
linkActiveClass: 'active',
// 精确
linkExactActiveClass: 'exact-active',
}
声明式导航 - 跳转传参
在跳转路由时, 可以给路由对应的组件内传值
通过router-link上的to属性传值, 语法格式如下:
-
/path?参数名=值
-
/path/:id – 需要路由对象提前配置 path: “/path/参数名”
对应页面组件接收传递过来的值
- $route.query.参数名
- $route.params.参数名
路由传参步骤:
-
创建components/Part.vue - 准备接收路由上传递的参数和值
// 在components/Part.vue中 <template> <div> <p>关注明星</p> <p>发现精彩</p> <p>寻找伙伴</p> <p>加入我们</p> <p>人名: {{ $route.query.name }} -- {{ $route.params.username }}</p> </div> </template> -
路由定义
// 在router/index.js中 { path: "/part", component: Part }, { path: "/part/:username", // 有:的路径代表要接收具体的值 component: Part }, -
导航跳转, 传值给Part.vue组件
// 在App.vue中 <router-link to="/part?name=小明">小明</router-link> <router-link to="/part/小红">小红</router-link>
vue路由 - 重定向和模式
路由 - 重定向
匹配path后, 强制切换到目标path上
- 网页打开url默认hash值是/路径
- redirect是设置要重定向到哪个路由路径
例如: 网页默认打开, 匹配路由"/", 强制切换到"/find"上
const routes = [
{
path: "/", // 默认hash值路径
redirect: "/find" // 重定向到/find
// 浏览器url中#后的路径被改变成/find-重新匹配数组规则
}
]
路由 - 404页面
如果路由hash值, 没有和数组里规则匹配,默认给一个404页面
语法: 路由最后, path匹配*(任意路径) – 前面不匹配就命中最后这个, 显示对应组件页面
-
创建NotFound页面
<template> <img src="../assets/404.png" alt=""> </template> <script> export default { } </script> <style scoped> img{ width: 100%; } </style> -
在main.js - 修改路由配置
import NotFound from '@/views/NotFound' const routes = [ // ...省略了其他配置 // 404在最后(规则是从前往后逐个比较path) { path: "*", component: NotFound } ]
路由 - 模式设置
修改路由在地址栏的模式
hash路由例如: http://localhost:8080/#/home
history路由例如: http://localhost:8080/home (以后上线需要服务器端支持, 否则找的是文件夹)
router/index.js
const router = new VueRouter({
routes,
mode: "history" // 打包上线后需要后台支持, 模式是hash
})
vue路由 - 编程式导航
编程式导航用JS代码跳转, 声明式导航用a标签
编程式导航 - 基础使用
语法:
// 字符串
this.$router.push('home')
// 对象
this.$router.push({ path: 'home' })
// 命名的路由
this.$router.push({ name: 'user', params: { userId: '123' }})
// 带查询参数,变成 /register?plan=private
this.$router.push({ path: 'register', query: { plan: 'private' }})
- main.js - 路由数组里, 给路由起名字
{
path: "/find",
name: "Find",
component: Find
},
{
path: "/my",
name: "My",
component: My
},
{
path: "/part",
name: "Part",
component: Part
},
- App.vue - 换成span 配合js的编程式导航跳转
<template>
<div>
<div class="footer_wrap">
<span @click="btn('/find', 'Find')">发现音乐</span>
<span @click="btn('/my', 'My')">我的音乐</span>
<span @click="btn('/part', 'Part')">朋友</span>
</div>
<div class="top">
<router-view></router-view>
</div>
</div>
</template>
<script>
// 目标: 编程式导航 - js方式跳转路由
// 语法:
// this.$router.push({path: "路由路径"})
// this.$router.push({name: "路由名"})
// 注意:
// 虽然用name跳转, 但是url的hash值还是切换path路径值
// 场景:
// 方便修改: name路由名(在页面上看不见随便定义)
// path可以在url的hash值看到(尽量符合组内规范)
export default {
methods: {
btn(targetPath, targetName){
// 方式1: path跳转
this.$router.push({
// path: targetPath,
name: targetName
})
}
}
};
</script>
编程式导航 - 跳转传参
语法 query / params 任选 一个
// 命名的路由
router.push({ name: 'user', params: { userId: '123' }})
// 带查询参数,变成 /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})
// 对应路由接收 $route.params.参数名 取值
// 对应路由接收 $route.query.参数名 取值
$route.query和$route.params的区别:
-
如果提供了
path,params会被忽略 -
$route.query一般用path来引入,也可以用name来引入
-
$route.params传参只能用name来引入
App.vue
<template>
<div>
<div class="footer_wrap">
<span @click="btn('/find', 'Find')">发现音乐</span>
<span @click="btn('/my', 'My')">我的音乐</span>
<span @click="oneBtn">小明</span>
<span @click="twoBtn">小红</span>
</div>
<div class="top">
<router-view></router-view>
</div>
</div>
</template>
<script>
// 目标: 编程式导航 - 跳转路由传参
// 方式1:
// params => $route.params.参数名
// 方式2:
// query => $route.query.参数名
// 重要: path会自动忽略params
// 推荐: name+query方式传参
// 注意: 如果当前url上"hash值和?参数"与你要跳转到的"hash值和?参数"一致, 爆出冗余导航的问题, 不会跳转路由
export default {
methods: {
btn(targetPath, targetName){
// 方式1: path跳转
this.$router.push({
// path: targetPath,
name: targetName
})
},
oneBtn(){
this.$router.push({
name: 'Part',
params: {
username: '小明'
}
})
},
twoBtn(){
this.$router.push({
name: 'Part',
query: {
name: '小红'
}
})
}
}
};
</script>
vue路由 - 嵌套
vue路由 - 路由嵌套
在现有的一级路由下, 再嵌套二级路由
router-view嵌套架构图
-
创建需要用的所有组件
src/views/Find.vue -- 发现音乐页
src/views/My.vue -- 我的音乐页
src/views/Second/Recommend.vue -- 发现音乐页 / 推荐页面
src/views/Second/Ranking.vue -- 发现音乐页 / 排行榜页面
src/views/Second/SongList.vue -- 发现音乐页 / 歌单页面
-
main.js– 继续配置2级路由
一级路由path从/开始定义
二级路由往后path直接写名字, 无需/开头
嵌套路由在上级路由的children数组里编写路由信息对象
-
说明:
App.vue的router-view负责发现音乐和我的音乐页面, 切换
Find.vue的的router-view负责发现音乐下的, 三个页面, 切换
-
配置二级导航和样式(==可直接复制==) - 在Find.vue中
<template>
<div>
<!-- <p>推荐</p>
<p>排行榜</p>
<p>歌单</p> -->
<div class="nav_main">
<router-link to="/find/recommend">推荐</router-link>
<router-link to="/find/ranking">排行榜</router-link>
<router-link to="/find/songlist">歌单</router-link>
</div>
<div style="1px solid red;">
<router-view></router-view>
</div>
</div>
</template>
<script>
export default {};
</script>
<style scoped>
.nav_main {
background-color: red;
color: white;
padding: 10px 0;
}
.nav_main a {
text-align: center;
text-decoration: none;
color: white;
font-size: 12px;
margin: 7px 17px 0;
padding: 0px 15px 2px 15px;
height: 20px;
display: inline-block;
line-height: 20px;
border-radius: 20px;
}
.nav_main a:hover {
background-color: brown;
}
.nav_main .router-link-active{
background-color: brown;
}
</style>
- 配置路由规则-二级路由展示
const routes = [
// ...省略其他
{
path: "/find",
name: "Find",
component: Find,
children: [
{
path: "recommend",
component: Recommend
},
{
path: "ranking",
component: Ranking
},
{
path: "songlist",
component: SongList
}
]
}
// ...省略其他
]
- 说明:
-
App.vue, 外层的router-view负责发现音乐和我的音乐页面切换
-
Find.vue 内层的router-view负责发现音乐下的子tab对应的组件切换
- 运行 - 点击导航观察嵌套路由在哪里展示
vuex
vuex的定义
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式
处理方式:把组件的共享状态抽取出来,以一个全局单例模式管理,在这种模式下,任何组件都能获取状态
作用:解决多组件状态共享的问题
特点:
- 响应式 只要数据修改了, 所有用到该数据的地方, 自动更新 (数据驱动)
- 操作更简洁, 逻辑清晰
适用场景: 多个组件均需要共享的数据,才有必要存储在vuex中,对于某个组件中的私有数据,依旧存储在组件自身的data中
vuex创建一个仓库
- 创建项目
// 在桌面shirft+右键,s键,运行windowspowershell
md vuex-demo
cd vuex-demo
vue create demo01
// 按默认项配置
cd demo01
yarn serve
// 复制Local地址在浏览器打开
// ctrl+c终止服务
code . // 在vscode中打开
- 配置端口和自动打开浏览器
// ctrl+~打开vscode终端
// 在根目录新建vue.config.js文件
new-item vue.config.js
// 在vue.config.js中配置端口
module.exports={
devServer:{
// 端口号
port:3000,
// 自动打开浏览器
open:true
}
}
- 更改文件
删除src/assets文件夹
删除src/components文件夹下的所有文件
删除App.vue文件中的所有内容
终端命令yarn serve
开启服务成功
ctrl+c终止服务
- 安装vuex
yarn add vuex
- 在src文件夹下,新建store/index.js文件,专门存放 vuex
cd src
md store
cd store
new-item index.js
- 在store/index.js中,创建一个空仓库
store
// 导入 vue
import Vue from 'vue'
// 导入 vuex
import Vuex from 'vuex'
// vuex也是vue的插件, 需要use一下, 进行插件的安装初始化
Vue.use(Vuex)
// 创建仓库 store
const store = new Vuex.Store() // 固定语法
// 导出仓库
export default store
- 在 main.js 中导入挂载到 Vue 实例上
import Vue from 'vue'
import App from './App.vue'
import store from './store'
Vue.config.productionTip = false
new Vue({
render: h => h(App),
store
}).$mount('#app')
核心概念
state
State提供唯一的公共数据源,所有共享的数据都要统一放到Store中的State中存储
// 创建仓库 store
const store = new Vuex.Store({
// state 状态, 即数据, 类似于vue组件中的data,
// 区别在于 data 是组件自己的数据, 而 state 中的数据整个vue项目的组件都能访问到
state: {
count: 100,
num:10
}
})
组件获取state中的数据:
-
原始形式
-
插值表达式
<h1>state的数据 - {{ $store.state.count }}</h1> -
计算属性
// 把state中数据,定义在组件内的计算属性中 computed: { count () { return this.$store.state.count }, num () { return this.$store.state.num } }
-
-
mapState辅助函数
使用步骤:
-
按需导入 mapState (mapState是vuex中的一个函数)
import { mapState } from 'vuex' -
采用字符串数组形式引入state属性(简写时计算属性的名称要与 state 中数据的名称相同)
mapState(['count','num']) // 等于 { count () { return this.$store.state.count }, num () { return this.$store.state.num } } -
配合展开运算符将导出的状态映射成组件的计算属性
computed: { ...mapState(['count']), (其它非公共计算属性) }
-
mutations
mutations是一个对象,对象中存放修改state的方法
开启严格模式,state数据的修改只能通过mutations,并且mutations必须是同步的
const store = new Vuex.Store({
state: {
count: 0
},
// 定义mutations
mutations: {
// 方法里的第一个参数是当前store的state属性
// payload 载荷 运输参数 调用mutaiions的时候 可以传递参数 传递载荷
addCount (state) {
state.count += 1
}
}
})
组件中提交 mutations
this.$store.commit('addCount')
-
带参数的 mutation:
-
提供mutation函数
mutations: { ... inputCount (state, count) { state.count = count } }, -
注册事件提交
<input type="text" :value="count" @input="handleInput"> -
提交mutation
handleInput (e) { this.$store.commit('inputCount', +e.target.value) }注意:提交的参数只能是一个, 如果有多个参数要传, 可以传递一个对象
this.$store.commit('inputCount', { count: e.target.value })
-
-
mapMutations辅助函数
使用步骤:
-
按需导入 mapMutations (mapMutations是vuex中的一个函数)
import { mapMutations } from 'vuex' -
将mutations中的方法导入到methods中(简写时事件处理函数的名称要与 mutations中方法名称相同)
methods: { ...mapMutations(['addCount']) } // 等于 methods: { // commit(方法名, 载荷参数) addCount () { this.$store.commit('addCount') } } -
此时,就可以直接通过this.addCount调用了
<button @click="addCount">值+1</button>
-
actions
state是存放数据的
mutations是同步更新数据 (便于监测数据的变化, 更新视图等, 方便于调试工具查看变化),
actions则负责进行异步操作
-
定义仓库中定义actons
actions: { // mapActions中方法名称setAsyncCount与actions中方法名称与组件中事件名称相同 setAsyncCount (context, num) { // 一秒后, 给一个数, 去修改 num setTimeout(() => { // addCount为异步中调用的mutations中的方法 context.commit('addCount', num) }, 1000) } }, -
原始调用
methods:{ setAsyncCount () { this.$store.dispatch('setAsyncCount', 200) } } -
mapActions辅助函数
使用步骤:
-
按需导入 mapActions (mapActions是vuex中的一个函数)
import { mapActions } from 'vuex' -
将actions中的方法导入到methods中(简写时事件处理函数的名称要与 mapActions中方法名称相同)
methods: { ...mapActions(['setAsyncCount']) } // 等于 methods: { // dispatch(方法名, 载荷参数) setAsyncCount () { this.$store.dispatch('setAsyncCount', 200) } } -
此时,就可以直接通过this.setAsyncCount调用了
<button @click="setAsyncCount(200)">+异步</button>
-
getters
getters定义了一些从state中派生出的状态,这些状态依赖于state
就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算
-
定义
const store = new Vuex.Store({ state: { money: 100, msg: 'www', }, getters:{ // getters函数的第一个参数是 state // 必须要有返回值 moneyTip(state){ return state.money } } }) -
原始调用
computed:{ moneyTip () { return this.$store.getters.moneyTip } } -
mapGetters辅助函数
使用步骤:
-
按需导入 mapGetters(mapGetters是vuex中的一个函数)
import { mapGetters } from 'vuex' -
将getters中的方法导入到computed中(简写时事件处理函数的名称要与 mapActions中方法名称相同)
computed: { ...map.mapGetters(['moneyTip']) } // 等于 computed: { // getters.计算属性名 moneyTip () { return this.$store.getters.moneyTip } } -
此时,就可以直接通过this.moneyTip调用了
<p>{{moneyTip}}</p>
-
模块 module
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割
Vuex模块化步骤
-
在store文件夹下,新建模块文件
new-item user.js new-item setting.js -
完善各模块数据
// user.js export default { // 命令空间 namespaced: true, // 有了该代码,把mutations的方法变成该模块内的局部方法 state: { name: 'xm', age: 19, desc: '小小的小明,大大的暖男', }, mutations: { // 修改desc数据 changeDesc(state) { console.log('user 模块下的desc描述') state.desc = 'user 模块下的desc描述' }, }, actions: {}, getters: {}, } // setting.js const state = { title: '这是好网站', desc: '该网站已运行超10年', } const mutations = { changeDesc(state) { console.log('setting 模块的desc描述') state.desc = 'setting 模块的desc描述' }, } const actions = {} const getters = {} export default { namespaced: true, state, mutations, actions, getters, } -
在store/index.js中的modules下导入模块
import Vue from 'vue' import Vuex from 'vuex' // 导入user模块 import user from './user' import setting from './setting' Vue.use(Vuex) const store = new Vuex.Store({ ... modules: { /* 模块名: { state: {}, mutations: {}, actions,: {}, getters: {} }, */ user, setting, }, }) export default store -
使用模块中的数据
$store.state.模块名.xxx
命名空间 namespaced
默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的——这样使得多个模块能够对同一 mutation 或 action 作出响应
如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced: true 的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名
// modules/user.js
const state = {
userInfo: {
name: 'zs',
age: 18
},
myMsg: '我的数据'
}
const mutations = {
updateMsg (state, msg) {
state.myMsg = msg
}
}
const actions = {}
const getters = {}
export default {
namespaced: true,
state,
mutations,
actions,
getters
}
提交模块中的mutation
全局的: this.$store.commit('mutation函数名', 参数)
模块中的: this.$store.commit('模块名/mutation函数名', 参数)
提交模块中的action
全局的: this.$store.dispatch('action名字', 参数)
模块中的: this.$store.dispatch('模块名/action名字', 参数)
namespaced: true 后, 要添加映射, 可以加上模块名, 找对应模块的 state/mutations/actions/getters
computed: {
// 全局的
...mapState(['count']),
// 模块中的
...mapState('user', ['myMsg']),
},
methods: {
// 全局的
...mapMutations(['addCount'])
// 模块中的
...mapMutations('user', ['updateMsg'])
}