教程链接

https://www.bilibili.com/video/BV1Zy4y1K7SH/

官方笔记

https://www.yuque.com/cessstudy/kak11d/hfeef2

课程笔记

el 与 data 的两种写法

el有2种写法

  • 创建Vue实例对象的时候配置el属性
  • 先创建Vue实例,随后再通过vm.$mount(‘#root’)指定el的值

data有2种写法

目前哪种写法都可以,以后到组件时,data必须使用函数,否则会报错

  • 对象式:data: { }
  • 函数式:data() { return { } }

由Vue管理的函数,一定不要写箭头函数,否则 this 就不再是Vue实例了

MVVM模型 & 数据代理

MVVM模型

MVVM模型

  • M:模型 Model,data中的数据
  • V:视图 View,模板代码
  • VM:视图模型 ViewModel,Vue实例

Object.defineproperty方法

Object.defineProperty(person, 'age', {
  // value:18,
  // enumerable:true,		// 控制属性是否可以枚举,默认值是false
  // writable:true,			// 控制属性是否可以被修改,默认值是false
  // configurable:true	// 控制属性是否可以被删除,默认值是false

  // 当有人读取person的age属性时,get函数(getter)就会被调用,且返回值就是age的值
  get() {
    console.log('有人读取age属性了')
    return number
  },

  // 当有人修改person的age属性时,set函数(setter)就会被调用,且会收到修改的具体值
  set(value) {
    console.log('有人修改了age属性,且值是', value)
    number = value
  }

})

数据代理

通过一个对象代理对另一个对象中属性的操作(读/写)

  1. Vue中的数据代理
    通过vm对象来代理data对象中属性的操作(读/写)
  2. Vue中数据代理的好处
    更加方便的操作data中的数据
  3. 基本原理
    • 通过 object.defineProperty() 把data对象中所有属性添加到vm上
    • 为每一个添加到vm上的属性,都指定一个 getter setter
    • getter setter 内部去操作(读/写)data中对应的属

20210811214920

Vue将data中的数据拷贝了一份到_data属性中,又将_data里面的属性提到Vue实例中(如name),通过defineProperty实现数据代理,这样通过geter/setter操作 name,进而操作_data中的 name。而_data又对data进行数据劫持,实现响应式

事件处理

事件的基本用法

  1. 使用v-on:xxx或@xxx绑定事件,其中 xxx 是事件名
  2. 事件的回调需要配置在methods对象中,最终会在vm上
  3. methods中配置的函数,不要用箭头函数,否则 this 就不是vm了
  4. methods中配置的函数,都是被 Vue所管理的函数,this 的指向是vm或组件实例对象
  5. @click="demo"@click="demo($event)" 效果一致,但后者可以传参

Vue中的事件修饰符

修饰符可以连续写,比如可以这么用:@click.prevent.stop="showInfo"

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

键盘事件

键盘上的每个按键都有自己的名称和编码,例如:Enter(13)。而Vue还对一些常用按键起了别名方便使用

  1. Vue中常用的按键别名
    • 回车 enter
    • 删除 delete捕获“删除”和“退格”键
    • 退出 esc
    • 空格 space
    • 换行 tab 特殊,必须配合 keydown 去使用
    • up
    • down
    • left
    • right
  2. Vue未提供别名的按键,可以使用按键原始的key值去绑定,但注意要转为 kebab-case(多单词小写短横线写法)
  3. 系统修饰键(用法特殊)ctrl alt shift meta(meta就是win键)
    • 配合 keyup 使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发
      指定 ctr+y 使用 @keyup.ctr.y
    • 配合 keydown 使用:正常触发事件
  4. 也可以使用 keyCode 去指定具体的按键(不推荐)
  5. Vue.config.keyCodes.自定义键名 = 键码,可以去定制按键别名

计算属性 & 侦听属性

计算属性

  1. 定义:要用的属性不存在,需要通过已有属性计算得来
  2. 原理:底层借助了 Objcet.defineproperty() 方法提供的 gettersetter
  3. get函数什么时候执行?
    • 初次读取时会执行一次
    • 当依赖的数据发生改变时会被再次调用
  4. 优势:与 methods 实现相比,内部有缓存机制(复用),效率更高,调试方便
  5. 备注
    • 计算属性最终会出现在 vm 上,直接读取使用即可
    • 如果计算属性要被修改,那必须写 set 函数去响应修改,且 set 中要引起计算时依赖的数据发生改变
    • 如果计算属性确定不考虑修改,可以使用计算属性的简写形式
 Vue.config.productionTip = false
  const vm = new Vue({
    el: '#root',
    data: {
      firstName:'张',
      lastName:'三',
      x:'你好'
    },
    computed: {
      //完整写法
      // fullName: {
      // 	get() {
      // 		console.log('get被调用了')
      // 		return this.firstName + '-' + this.lastName
      // 	},
      // 	set(value) {
      // 		console.log('set', value)
      // 		const arr = value.split('-')
      // 		this.firstName = arr[0]
      // 		this.lastName = arr[1]
      // 	}
      // }

      // 简写
      fullName() {
        console.log('get被调用了')
        return this.firstName + '-' + this.lastName
      }
    }
  })

侦听属性基本用法

定事件的时候:@xxx=“yyy” yyy可以写一些简单的语句

  const vm = new Vue({
    el:'#root',
    data:{
      isHot:true,
    },
    computed:{
      info(){
        return this.isHot ? '炎热' : '凉爽'
      }
    },
    methods: {
      changeWeather(){
        this.isHot = !this.isHot
      }
    }
  })

watch 监视属性

  1. 当被监视的属性变化时,回调函数自动调用,进行相关操作
  2. 监视的属性必须存在,才能进行监视,既可以监视data,也可以监视计算属性
  3. 配置项属性 immediate:false ,改为 true,则初始化时调用一次 handler(newValue,oldValue)
  4. 监视有两种写法
    • 创建Vue时传入 watch: {} 配置
    • 通过 vm.$watch() 监视
  Vue.config.productionTip = false
  const vm = new Vue({
    el: '#root',
    data: {
      isHot: true,
    },
    computed: {
      info() {
        return this.isHot ? '炎热' : '凉爽'
      }
    },
    methods: {
      changeWeather() {
        this.isHot = !this.isHot
      }
    },
    // 方式一
    /* watch:{		
			isHot:{
				immediate:true,
				handler(newValue,oldValue){
					console.log('isHot被修改了',newValue,oldValue)
				}
			}
		} */
  })
  // 方式二
  vm.$watch('isHot', {		
    immediate: true, // 初始化时让handler调用一下
    //handler什么时候调用?当isHot发生改变时
    handler(newValue, oldValue) {
      console.log('isHot被修改了', newValue, oldValue)
    }
  })

深度侦听

  1. 深度侦听
    • Vue中的 watch 默认不监测对象内部值的改变(一层)
    • watch 中配置 deep:true 可以监测对象内部值的改变(多层)
  2. 注意
    • Vue自身可以监测对象内部值的改变,但Vue提供的 watch 默认不可以
    • 使用 watch 时根据监视数据的具体结构,决定是否采用深度监视
  3. computedwatch 之间的区别
    • computed 能完成的功能, watch 都可以完成
    • watch 能完成的功能, computed 不一定能完成,例如 watch 可以进行异步操作
  4. 两个重要的小原则
    • 所有被Vue管理的函数,最好写成普通函数,这样 this 的指向才是 vm 或组件实例对象
    • 所有不被Vue所管理的函数(定时器的回调函数、 ajax 的回调函数等、 Promise 的回调函数),最好写成箭头函数,这样 this 的指向才是 vm 或组件实例对象

绑定样式 条件渲染

绑定样式

  1. 写法::class="xxx"xxx 可以是字符串、数组、对象
  2. :style="[a,b]" 其中a、b是样式对象
  3. :style="{fontSize: xxx}" 其中 xxx 是动态值
    • 字符串写法适用于 类名不确定,要动态获取
    • 数组写法适用于 要绑定多个样式,个数不确定,名字也不确定
    • 对象写法适用于 要绑定多个样式,个数确定,名字也确定,但不确定用不用
<div id="root">
  <!-- 绑定class样式--字符串写法,适用于:样式的类名不确定,需要动态指定 -->
  <div class="basic" :class="mood" @click="changeMood">{{name}}</div><br/><br/>

  <!-- 绑定class样式--数组写法,适用于:要绑定的样式个数不确定、名字也不确定 -->
  <div class="basic" :class="classArr">{{name}}</div><br/><br/>

  <!-- 绑定class样式--对象写法,适用于:要绑定的样式个数确定、名字也确定,但要动态决定用不用 -->
  <div class="basic" :class="classObj">{{name}}</div><br/><br/>

  <!-- 绑定style样式--对象写法 -->
  <div class="basic" :style="styleObj">{{name}}</div><br/><br/>

  <!-- 绑定style样式--数组写法 -->
  <div class="basic" :style="styleArr">{{name}}</div>
</div>

条件渲染

v-if

  • 写法 跟 if else 语法类似
    v-if=“表达式”
    v-else-if=“表达式”
    v-else
  • 适用于:切换频率较低的场景,因为不展示的DOM元素直接被移除
  • 注意:v-if 可以和 v-else-if v-else 一起使用,但要求结构不能被打断

v-show

  • 写法:v-show="表达式"
  • 适用于:切换频率较高的场景
  • 特点:不展示的DOM元素未被移除,仅仅是使用样式隐藏掉 display: none
  • 备注:使用 v-if 的时,元素可能无法获取到,而使用 v-show 一定可以获取到

template 标签不影响结构,页面html中不会有此标签,但只能配合 v-if ,不能配合 v-show

列表渲染 数据监视

列表渲染

v-for指令

  • 用于展示列表数据
  • 语法:<li v-for="(item, index) of items" :key="index"> ,这里key可以是index,更好的是遍历对象的唯一标识
  • 可遍历:数组、对象、字符串(用的少)、指定次数(用的少)

key 的作用与原理

index为key

id为key

面试题:react vue 中的 key 有什么作用?(key的内部原理)

  1. 虚拟 DOMkey 的作用:key 是虚拟 DOM 中对象的标识,当数据发生变化时,Vue会根据新数据生成新的虚拟 DOM ,随后Vue进行新虚拟 DOM 与旧虚拟 DOM 的差异比较,比较规则如下
  2. 对比规则
    • 旧虚拟 DOM 中找到了与新虚拟 DOM 相同的 key
      • 若虚拟DOM中内容没变, 直接使用之前的真实DOM
      • 若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM
    • 旧虚拟 DOM 中未找到与新虚拟 DOM 相同 key 创建新的真实 DOM ,随后渲染到到页面
  3. index 作为 key 可能会引发的问题
    • 若对数据进行逆序添加、逆序删除等破坏顺序操作,会产生没有必要的真实 DOM 更新 ==> 界面效果没问题,但效率低
    • 若结构中还包含输入类的 DOM :会产生错误 DOM 更新 ==> 界面有问题
  4. 开发中如何选择 key
    • 最好使用每条数据的唯一标识作为 key ,比如 id、手机号、身份证号、学号等唯一值
    • 如果不存在对数据的逆序添加、逆序删除等破坏顺序的操作,仅用于渲染列表,使用 index 作为 key 是没有问题的

数据监视

  1. vue会监视data中所有层次的数据
  2. 如何监测对象中的数据?
    通过setter实现监视,且要在new Vue()时就传入要监测的数据
    • 对象创建后追加的属性,Vue默认不做响应式处理
    • 如需给后添加的属性做响应式,请使用如下API
      Vue.set(target,propertyName/index,value)
      vm.$set(target,propertyName/index,value)
  3. 如何监测数组中的数据?
    通过包裹数组更新元素的方法实现,本质就是做了两件事
    • 调用原生对应的方法对数组进行更新
    • 重新解析模板,进而更新页面
  4. 在Vue修改数组中的某个元素一定要用如下方法
    push() pop() unshift() shift() splice() sort() reverse()这几个方法被Vue重写了
    Vue.set()vm.$set()
    特别注意:Vue.set()vm.$set() 不能给 vmvm 的根数据对象(data等)

收集表单数据 过滤器

收集表单数据

  1. 收集表单数据
    • <input type="text"/> ,则 v-mode l收集的是 value 值,用户输入的内容就是 value
    • <input type="radio"/> ,则 v-model 收集的是 value 值,且要给标签配置 value 属性
    • <input type="checkbox"/>
      • 没有配置 value 属性,那么收集的是 checked 属性(勾选 or 未勾选,是布尔值)
      • 配置了 value 属性
        • v-model 的初始值是非数组,那么收集的就是 checked (勾选 or 未勾选,是布尔值)
        • v-model 的初始值是数组,那么收集的就是 value 组成的数组
  2. v-model 的三个修饰符
    • lazy 失去焦点后再收集数据
    • number 输入字符串转为有效的数字
    • trim 输入首尾空格过滤

过滤器(Vue3 已经移除)

  • 定义:对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理)
  • 注册过滤器:
    • Vue.filter(name, callback) 全局过滤器
    • new Vue {filters: {}} 局部过滤器
  • 使用过滤器:{{ xxx | 过滤器名}}v-bind:属性 = "xxx | 过滤器名"
  • 备注:
    • 过滤器可以接收额外参数,多个过滤器也可以串联
      {{ xxx | 过滤器名(参数2)}}v-bind:属性 = "xxx | 过滤器名(参数2)" 。接收时管道参数默认在第一个,其他参数第二个依次类推。
    • 并没有改变原本的数据,而是产生新的对应的数据
  • 处理时间的库 moment 体积较大 dayjs 轻量级
new Vue({
            el:'#root',
            data:{
                time:1626750147900,
            },
			// 局部过滤器
            filters:{
                timeFormater(value, str="YYYY年MM月DD日 HH:mm:ss"){
                    return dayjs(value).format(str)
                }
            }
        })

内置指令 自定义指令

内置指令

之前学过的指令

  • v-bind 单向绑定解析表达式,可简写为:
  • v-model 双向数据绑定
  • v-for 遍历数组 / 对象 / 字符串
  • v-on 绑定事件监听,可简写为@
  • v-show 条件渲染 (动态控制节点是否展示)
  • v-if 条件渲染(动态控制节点是否存存在)
  • v-else-if 条件渲染(动态控制节点是否存存在)
  • v-else 条件渲染(动态控制节点是否存存在)

v-text 指令

  • 作用:向其所在的节点中渲染文本内容
  • 与插值语法的区别: v-text 会替换掉节点中的内容, {{xxx}} 则不会,更灵活

v-html 指令

  • 作用:向指定节点中渲染包含html结构的内容
  • 与插值语法的区别:
    • v-html 会替换掉节点中所有的内容, {{xxx}} 则不会
    • v-html 可以识别html结构
  • 严重注意 v-html 有安全性问题!!!
    • 在网站上动态渲染任意html是非常危险的,容易导致 XSS 攻击
    • 一定要在可信的内容上使用 v-html ,永远不要用在用户提交的内容上!!!

v-cloak 指令

  • 本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉 v-cloak 属性
  • 使用css配合 v-cloak 可以解决网速慢时页面展示出 {{xxx}} 的问题
<title>v-cloak指令</title>

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

<div id="root">
  <h2 v-cloak>{{ name }}</h2>
</div>

// 够延迟5秒收到vue.js
<script type="text/javascript" src="http://localhost:8080/resource/5s/vue.js"></script>

<script type="text/javascript">
  console.log(1)
  Vue.config.productionTip = false
  new Vue({
    el:'#root',
    data:{name:'cess'}
  })
</script>

v-once 指令

  • v-once 所在节点在初次动态渲染后,就视为静态内容了
  • 以后数据的改变不会引起v-once 所在结构的更新,可以用于优化性能

v-pre 指令

  • 跳过 v-pre 所在节点的编译过程
  • 可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译

自定义指令 directives

  1. 局部指令
new Vue({															
  directives:{ 
    指令名:配置对象 
  }   
})

new Vue({															
  directives:{ 
    指令名:回调函数 
  }   
})
  1. 全局指令
Vue.directive(指令名, 配置对象)
或
Vue.directive(指令名, 回调函数)


Vue.directive('fbind', {
    // 指令与元素成功绑定时(一上来)
    bind(element, binding) {	// element就是DOM元素,binding就是要绑定的
      element.value = binding.value
    },
    // 指令所在元素被插入页面时
    inserted(element, binding) {
      element.focus()
    },
    // 指令所在的模板被重新解析时
    update(element, binding) {
      element.value = binding.value
    }
})

配置对象中常用的3个回调函数

  • bind(element, binding) 指令与元素成功绑定时调用
  • inserted(element, binding) 指令所在元素被插入页面时调用
  • update(element, binding) 指令所在模板结构被重新解析时调用
  • element 就是DOM元素, binding 就是要绑定的对象,它包含以下属性: namevalueoldValueexpressionargmodifiers

备注

  • 指令定义时不加 v- ,但使用时要加 v-
  • 指令名如果是多个单词,要使用 kebab-case 命名方式,不要用 camelCase 命名
new Vue({
	el: '#root',
	data: {
		n:1
	},
	directives: {
		'big-number'(element,binding) {
			element.innerText = binding.value * 10
		}
	}
})
directives: {
      // big函数何时会被调用?
      // 1.指令与元素成功绑定时(一上来) 2.指令所在的模板被重新解析时
      // <h2>放大10倍后的n值是:<span v-big-number="n"></span> </h2>
      /* 'big-number'(element,binding){
				// console.log('big')
				element.innerText = binding.value * 10
			}, */
      big(element, binding) {
        console.log('big', this) // 🔴注意此处的 this 是 window
        // console.log('big')
        element.innerText = binding.value * 10
      },
      fbind: {
        // 指令与元素成功绑定时(一上来)
        bind(element, binding) {
          element.value = binding.value
        },
        // 指令所在元素被插入页面时
        inserted(element, binding) {
          element.focus()
        },
        // 指令所在的模板被重新解析时
        update(element, binding) {
          element.value = binding.value
        }
      }
    }

Vue生命周期

生命周期

  • 又名生命周期回调函数、生命周期函数、生命周期钩子
  • 是什么:Vue在关键时刻帮我们调用的一些特殊名称的函数
  • 生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写的
  • 生命周期函数中的 this 指向是 vm组件实例对象
Vue.config.productionTip = false
  new Vue({
    el: '#root',
    data: {
      a: false,
      opacity: 1
    },
    methods: {
    },
    // 🔴Vue 完成模板的解析并把初始的真实 DOM 元素放入页面后(挂载完毕)调用 mounted
    mounted() {
      console.log('mounted', this)
      setInterval(() => {
        this.opacity -= 0.01
        if(this.opacity <= 0) this.opacity = 1
      }, 16)
    },
  })

生命周期

总结

常用的生命周期钩子

  • mounted 发送 ajax 请求、启动定时器、绑定自定义事件、订阅消息等初始化操作
  • beforeDestroy 清除定时器、解绑自定义事件、取消订阅消息等收尾工作

关于销毁Vue实例

  • 销毁后借助Vue开发者工具看不到任何信息
  • 销毁后自定义事件会失效,但原生DOM事件依然有效
  • 一般不会在 beforeDestroy 操作数据,因为即便操作数据,也不会再触发更新流程了
beforeCreate() {console.log('beforeCreate')},

created() {console.log('created')},

beforeMount() {console.log('beforeMount')},

mounted() {console.log('mounted')},

beforeUpdate() {console.log('beforeUpdate')},

updated() {console.log('updated')},

beforeDestroy() {console.log('beforeDestroy')},

destroyed() {console.log('destroyed')},

组件化编程

模块与组件、模块化与组件化

传统编写应用模式

组件化编写应用模式

  • 模块
    • 理解:向外提供特定功能的 js 程序,一般就是一个 js 文件
    • 为什么:js 文件很多很复杂
    • 作用:复用、简化 js 的编写,提高 js 运行效率
  • 组件
    • 定义:用来实现局部功能的代码和资源的集合(html/css/js/image…)
    • 为什么:一个界面的功能很复杂
    • 作用:复用编码,简化项目编码,提高运行效率
  • 模块化
    当应用中的 js 都以模块来编写的,那这个应用就是一个模块化的应用
  • 组件化
    当应用中的功能都是多组件的方式来编写的,那这个应用就是一个组件化的应用

非单文件组件

  • 非单文件组件:一个文件中包含有 n 个组件
  • 单文件组件:一个文件中只包含有 1 个组件

基本使用

Vue中使用组件的三大步骤

  1. 定义组件
    使用 Vue.extend(options) 创建,其中 optionsnew Vue(options) 时传入的 options 几乎一样,但也有点区别
    • el 不要写,因为最终所有的组件都要经过一个 vm 的管理,由 vm 中的 el 才决定服务哪个容器
    • data 必须写成函数,避免组件被复用时,数据存在引用关系
  2. 注册组件
    • 局部注册: new Vue() 的时候 options 传入 components 选项
    • 全局注册: Vue.component('组件名',组件)
  3. 使用组件
    编写组件标签如 <school></school>
<title>基本使用</title>
<script type="text/javascript" src="../js/vue.js"></script>

<div id="root">
  <h2>{{msg}}</h2><hr>
  <!-- 第三步:编写组件标签 -->
  <school></school><hr>
  <student></student><hr>
  <hello></hello><hr>
</div>

<div id="root2">
  <hello></hello>
</div>

<script type="text/javascript">
  Vue.config.productionTip = false

  //第一步:创建school组件
  const school = Vue.extend({
    // el:'#root', //组件定义时,一定不要写el配置项,
    // 因为最终所有的组件都要被一个vm管理,由vm决定服务于哪个容器
    template: `
				<div class="demo">
					<h3>学校名称:{{schoolName}}</h3>
					<h3>学校地址:{{address}}</h3>
					<button @click="showName">点我提示学校名</button>	
  			</div>
			`,
    data() {
      return {
        schoolName: '尚硅谷',
        address: '北京昌平'
      }
    },
    methods: {
      showName() {
        alert(this.schoolName)
      }
    },
  })

  //第一步:创建student组件
  const student = Vue.extend({
    template: `
				<div>
					<h3>学生姓名:{{studentName}}</h3>
					<h3>学生年龄:{{age}}</h3>
  			</div>
			`,
    data() {
      return {
        studentName: '张三',
        age: 18
      }
    }
  })

  //第一步:创建hello组件
  const hello = Vue.extend({
    template: `
				<div>	
					<h3>你好啊!{{name}}</h3>
  			</div>
			`,
    data() {
      return {
        name: 'cess'
      }
    }
  })

  //第二步:全局注册组件
  Vue.component('hello', hello)

  //创建vm
  new Vue({
    el: '#root',
    data: {
      msg: '你好啊!'
    },
    //第二步:注册组件(局部注册)
    components: {
      school,
      student
    }
  })

  new Vue({
    el: '#root2',
  })
</script>

组件注意事项

关于组件名
  • 一个单词组成
    • 第一种写法(首字母小写): school
    • 第二种写法(首字母大写): School
  • 多个单词组成
    • 第一种写法( kebab-case 命名): my-school
    • 第二种写法( CamelCase 命名): MySchool (需要Vue脚手架支持)
  • 备注
    • 组件名尽可能回避HTML中已有的元素名称,例如: h2H2 都不行
    • 可以使用 name 配置项指定组件在开发者工具中呈现的名字
关于组件标签
  • 第一种写法: <school></school>
  • 第二种写法: <school/> (需要Vue脚手架支持)
  • 备注:不使用脚手架时, <school/> 会导致后续组件不能渲染
  • 一个简写方式: const school = Vue.extend(options) 可简写为 const school = options ,因为父组件 components 引入的时候会自动创建

组件的嵌套

组件嵌套

Vue.config.productionTip = false

  //定义student组件
  const student = Vue.extend({
    name: 'student',
    template: `
				<div>
					<h4>学生姓名:{{name}}</h4>	
					<h4>学生年龄:{{age}}</h4>	
  			</div>
			`,
    data() {return {name: '尚硅谷',age: 18}}
  })

  //定义school组件
  const school = Vue.extend({
    name: 'school',
    template: `
				<div>
					<h3>学校名称:{{name}}</h3>	
					<h3>学校地址:{{address}}</h3>	
					<student></student>
 			  </div>
			`,
    data() {return {name: '尚硅谷',address: '北京'}},
    //注册组件(局部)
    components: { student }
  })

  //定义hello组件
  const hello = Vue.extend({
    template: `<h3>{{msg}}</h3>`,
    data() {return {msg: '欢迎来到尚硅谷学习!'}}
  })

  //定义app组件
  const app = Vue.extend({
    template: `
				<div>	
					<hello></hello>
					<school></school>
  			</div>
			`,
    components: { school, hello }
  })

  //创建vm
  new Vue({
    el: '#root',
    template: '<app></app>',
    //注册组件(局部)
    components: { app }
  })

VueComponent

  • school 组件本质是一个名为 VueComponent 的构造函数,且不是程序员定义的,而是 Vue.extend() 生成的
  • 我们只需要写 <school/><school></school> ,Vue 解析时会帮我们创建 school 组件的实例对象,即Vue帮我们执行的 new VueComponent(options)
  • 每次调用 Vue.extend ,返回的都是一个全新的 VueComponent ,即不同组件是不同的对象
  • 关于 this 指向
    • 组件配置中 data 函数、 methods 中的函数、 watch 中的函数、 computed 中的函数 它们的 this 均是 VueComponent 实例对象
    • new Vue(options) 配置中: data 函数、 methods 中的函数、 watch 中的函数、 computed 中的函数 它们的 this 均是 Vue 实例对象
  • VueComponent 的实例对象,以后简称 vc (组件实例对象)Vue 的实例对象,以后简称 vm

一个重要的内置关系

image-1683443819068

  1. 一个重要的内置关系: VueComponent.prototype.__proto__ === Vue.prototype
  2. 为什么要有这个关系:让组件实例对象 vc 可以访问到 Vue原型上的属性、方法

单文件组件

  • School.vue 学校 组件
  • Student.vue 学生 组件
  • App.vue App 组件,管理其他组件
  • main.js 创建vm
  • index.html 定义#root 承载vm

Vue CLI 初始化脚手架

初始化脚手架

说明

  • Vue脚手架是Vue官方提供的标准化开发工具(开发平台)
  • 最新的版本是 4.x
  • 文档 Vue CLI

具体步骤

  1. 如果下载缓慢请配置npm淘宝镜像 npm config set registry http://registry.npm.taobao.org
  2. 全局安装 @vue/cli npm install -g @vue/cli
  3. 切换到创建项目的目录,使用命令创建项目 vue create xxx
  4. 选择使用vue的版本
  5. 启动项目 npm run serve
  6. 打包项目 npm run build
  7. 暂停项目 Ctrl+C
    Vue脚手架隐藏了所有webpack相关的配置,若想查看具体的webpack配置,请执行
    vue inspect > output.js

脚手架文件结构

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

render 函数

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

Vue.config.productionTip = false

new Vue({
  el:'#app',
  // render函数功能:将App组件放入容器中
  // 简写形式
  render: h => h(App),
  // 完整形式
  // render(createElement){
  //   return createElement(App)
  // }
})

关于不同版本的函数

  1. vue.jsvue.runtime.xxx.js 的区别
    • vue.js 是完整版的Vue,包含:核心功能+模板解析器
    • vue.runtime.xxx.js 是运行版的Vue,只包含核心功能,没有模板解析器
      esm 就是 ES6 module
  2. 因为 vue.runtime.xxx.js 没有模板解析器,所以不能使用 template 配置项,需要使用 render 函数接收到的 createElement 函数去指定具体内容

vue.config.js 配置文件

vue inspect > output.js 可以查看到Vue脚手架的默认配置
使用 vue.config.js 可以对脚手架进行个性化定制,和 package.json 同级目录,详见 配置参考 | Vue CLI

module.exports = {
  pages: {
    index: {
      entry: 'src/index/main.js' // 入口
    }
  },
  lintOnSave: false	// 关闭语法检查
}

tips:每次改完要重启 npm run serve

Vue CLI ref props mixin plugin scoped

ref 属性

ref 被用来给元素或子组件注册引用信息(id的替代者)

  • 应用在html标签上获取的是真实DOM元素,应用在组件标签上获取的是组件实例对象 vc
  • 使用方式
    • 打标识: <h1 ref="xxx"></h1>或<School ref="xxx"></School>
    • 获取: this.$refs.xxx
<template>
  <div>
    <h1 v-text="msg" ref="title"></h1>
    <button ref="btn" @click="showDOM">点我输出上方的DOM元素</button>
    <School ref="sch"/>
  </div>
</template>

<script>
  import School from './components/School'

  export default {
    name:'App',
    components:{ School },
    data() {
      return {
        msg:'欢迎学习Vue!'
      }
    },
    methods: {
      showDOM(){
        console.log(this.$refs.title)	// 真实DOM元素
        console.log(this.$refs.btn)		// 真实DOM元素
        console.log(this.$refs.sch)		// School组件的实例对象(vc)
      }
    },
  }
</script>

props 配置项

props让组件接收外部传过来的数据

  • 传递数据 <Demo name="xxx" :age="18"/> 这里 age 前加:,通过 v-bind 使得里面的 18 是数字
  • 接收数据
    第一种方式(只接收) props:['name', 'age']
    第二种方式(限制类型) props:{name:String, age:Number}
    第三种方式(限制类型、限制必要性、指定默认值)
props: {
    name: {
        type: String,	 // 类型
        required: true,// 必要性
        default: 'cess'// 默认值
    }
}

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

src/App.vue

<template>
  <div>
    <Student name="李四" sex="女" :age="18"/>
    <Student name="王五" sex="男" :age="18"/>
  </div>
</template>

src/components/Student.vue

props: {
    name: {
      type: String, 	//name的类型是字符串
      required: true, //name是必要的
    },
    age: {
      type: Number,
      default: 99, //默认值
    },
    sex: {
      type: String,
      required: true,
    },
  },

mixin 混入

  • 功能:可以把多个组件共用的配置提取成一个混入对象
  • 使用方式
    • 定义混入
const mixin = {
    data() {....},
    methods: {....}
    ....
}
  • 使用混入
    • 全局混入 Vue.mixin(xxx)
    • 局部混入 mixins:[xxx]

备注

  • 组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”,在发生冲突时以组件优先
var mixin = {
	data: function () {
		return {
    		message: 'hello',
            foo: 'abc'
    	}
  	}
}

new Vue({
  	mixins: [mixin],
  	data () {
    	return {
      		message: 'goodbye',
            	bar: 'def'
    	}
    },
  	created () {
    	console.log(this.$data)
    	// => { message: "goodbye", foo: "abc", bar: "def" }
  	}
})
  • 同名生命周期钩子将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用
var mixin = {
  	created () {
    	console.log('混入对象的钩子被调用')
  	}
}

new Vue({
  	mixins: [mixin],
  	created () {
    	console.log('组件钩子被调用')
  	}
})

// => "混入对象的钩子被调用"
// => "组件钩子被调用"
  • 如果在 main.js 创建Vue实例对象之前进行 mixin ,则会在所有组件中都会进行配置
Vue.config.productionTip = false
Vue.mixin(...) // 混合1
Vue.mixin(...) // 混合2
new Vue({
    el: '#root',
    render: h => h(App)
})

plugin 插件

  • 功能:用于增强Vue,在 install 定义的过滤器指令混入添加的方法等 全局 vm vc 都能用
  • 本质:包含 install 方法的一个对象, install 的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据
  • 定义插件(见下 src/plugin.js
  • 使用插件: Vue.use()
  1. 定义 src/plugin.js
export default {
  install(Vue,x,y,z){
    //x,y,z时Vue.use(plugins,1,2,3)传的1,2,3
    console.log(x,y,z)
    //全局过滤器
    Vue.filter('mySlice', function(value){return value.slice(0,4)})

    //定义全局指令
    Vue.directive('fbind',{
      //指令与元素成功绑定时(一上来)
      bind(element,binding){element.value = binding.value},
      //指令所在元素被插入页面时
      inserted(element,binding){element.focus()},
      //指令所在的模板被重新解析时
      update(element,binding){element.value = binding.value}
    })

    //定义混入
    Vue.mixin({
      data() {return {x:100,y:200}},
    })

    //给Vue原型上添加一个方法(vm和vc就都能用了)
    Vue.prototype.hello = ()=>{alert('你好啊')}
  }
}
  1. 引入 src/main.js
import Vue from 'vue'
import App from './App.vue'
import plugins from './plugins'	// 引入插件

Vue.config.productionTip = false

Vue.use(plugins,1,2,3)	// 应用(使用)插件

new Vue({
	el:'#app',
	render: h => h(App)
})
  1. 应用
<template>
  <div>
    <h2>学校名称:{{ name | mySlice }}</h2>
    <h2>学校地址:{{ address }}</h2>
    <input type="text" v-fbind:value="name">
  </div>
</template>

<script>
  export default {
    name:'School',
    data() {
      return {
        name:'尚硅谷atguigu',
        address:'北京',
      }
    },
    methods: {
      test(){
        this.hello() // 调用vue原型上的方法:vm.__proto__.prototype.hello()
      }
    },
  }
</script>

scoped 样式

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

Vue中的webpack并没有安装最新版,导致有些插件也不能默认安装最新版,如 npm i less-loader@7 ,而不是最新版

<style lang="less" scoped>
  .demo {
    background-color: pink;
    .atguigu {
      font-size: 40px;
    }
  }
</style>

Vue CLI Todo-List案例

组件化编码流程

  1. 拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突
  2. 实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用
    • 一个组件在用:放在组件自身即可
    • 一些组件在用:放在他们共同的父组件上(状态提升
  3. 实现交互:从绑定事件开始

props 适用于

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

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

推荐使用 nanoid 代替手动生成 id

Vue CLI 本地存储 自定义事件

WebStorage(js 本地存储)

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

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

相关API

  • xxxStorage.setItem('key', 'value') 该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值
  • xxxStorage.getItem('key') 该方法接受一个键名作为参数,返回键名对应的值
  • xxxStorage.removeItem('key') 该方法接受一个键名作为参数,并把该键名从存储中删除
  • xxxStorage.clear() 该方法会清空存储中的所有数据

备注

  • SessionStorage 存储的内容会随着浏览器窗口关闭而消失
  • LocalStorage 存储的内容,需要手动清除才会消失
  • xxxStorage.getItem(xxx) 如果 xxx 对应的 value 获取不到,那么 getItem() 的返回值是 null
  • JSON.parse(null) 的结果依然是 null

组件的自定义事件

  • 一种组件间通信的方式,适用于:子组件 ===> 父组件
  • 使用场景:子组件想给父组件传数据,那么就要在父组件中给子组件绑定自定义事件(事件的回调在A中)
  • 绑定自定义事件
    • 第一种方式,在父组件DOM中 <Demo @事件名="方法"/><Demo v-on:事件名="方法"/>
    • 第二种方式,在父组件 mountedthis.$refs.demo.$on('事件名',方法)
      方法可以写定义在 methods 中的方法名;也可以直接写函数体,但是一定要写箭头函数,否者 this 指向有问题
    • 若想让自定义事件只能触发一次,可以使用 once 修饰符,或 $once 方法
<Demo ref="demo"/>
......
mounted(){
   this.$refs.demo.$on('atguigu',this.test)
}
  • 触发自定义事件 this.$emit('事件名',数据)
  • 解绑自定义事件
    • 解绑一个 this.$off('事件名')
    • 解绑多个 this.$off(['事件1', '事件2'])
    • 解绑全部 this.$off()
  • 组件上也可以绑定原生DOM事件,需要使用 native 修饰符 @click.native="show"
  • 上面绑定自定义事件,即使绑定的是原生事件也会被认为是自定义的,需要加 native ,加了后就将此事件给组件的根元素
  • 注意:通过 this.$refs.xxx.$on('事件名',回调函数) 绑定自定义事件时,回调函数要么配置在 methods 中,要么用箭头函数,否则 this 指向会出问题

Vue CLI 全局事件总线 消息的订阅与发布

全局事件总线(GlobalEventBus)

image-1683477190153

  1. 定义全局事件总线
new Vue({
   	...
   	beforeCreate() {
   		Vue.prototype.$bus = this // 安装全局事件总线,$bus 就是当前应用的 vm
   	},
    ...
})
  1. 使用事件总线
    • 接收数据:A组件想接收数据,则在A组件中给 $bus 绑定自定义事件,事件的回调留在A组件自身
    • 提供数据: this.$bus.$emit('xxx',data)
export default {
    methods(){
        demo(data){...}
    }
    ...
    mounted() {
        this.$bus.$on('xxx',this.demo)
    }
}
  1. 最好在 beforeDestroy 钩子中,用 $off() 去解绑当前组件所用到的事件

消息的订阅与发布(pubsub)

消息订阅与发布(pubsub)消息订阅与发布是一种组件间通信的方式,适用于任意组件间通信
图片

使用步骤

  1. 安装 pubsub:npm i pubsub-js
  2. 引入: import pubsub from 'pubsub-js'
  3. 接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身
export default {
    methods: {
        demo(msgName, data) {...}
    }
    ...
    mounted() {
			this.pid = pubsub.subscribe('xxx',this.demo)
    }
}
  1. 提供数据: pubsub.publish('xxx',data)
  2. 最好在 beforeDestroy 钩子中,使用 pubsub.unsubscribe(pid) 取消订阅

Vue CLI $nextTick 过渡与动画

$nextTick

这是一个生命周期钩子

this.$nextTick (回调函数)在下一次DOM更新结束后执行其指定的回调

什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在 nextTick 所指定的回调函数中执行

过渡与动画

Vue封装的过度与动画:在插入、更新或移除DOM元素时,在合适的时候给元素添加样式类名

image-1683478210023

写法

  1. 准备好样式
    • 元素进入的样式
      • v-enter 进入的起点
      • v-enter-active 进入过程中
      • v-enter-to 进入的终点
    • 元素离开的样式
      • v-leave 离开的起点
      • v-leave-active 离开过程中
      • v-leave-to 离开的终点
  2. 使用 <transition> 包裹要过度的元素,并配置 name 属性,此时需要将上面样式名的 v 换为 name
  3. 要让页面一开始就显示动画,需要添加 appear
<transition name="hello" appear>
  <h1 v-show="isShow">你好啊!</h1>
</transition>

<style>
  .hello-enter-active{
    animation: hello 0.5s linear;
  }

  .hello-leave-active{
    animation: hello 0.5s linear reverse;
  }

  @keyframes hello {
    from{
      transform: translateX(-100%);
    }
    to{
      transform: translateX(0px);
    }
  }
</style>
  1. 备注:若有多个元素需要过度,则需要使用 <transition-group> ,且每个元素都要指定key值
<transition-group name="hello" appear>
  <h1 v-show="!isShow" key="1">你好啊!</h1>
  <h1 v-show="isShow" key="2">尚硅谷!</h1>
</transition-group>
  1. 可以不用 @keyframes 直接利用Vue提供的方式定义过渡动画
  /* 进入的起点、离开的终点 */
  .hello-enter,.hello-leave-to {
    transform: translateX(-100%);
  }
  .hello-enter-active,.hello-leave-active{
    transition: 0.5s linear;
  }
  /* 进入的终点、离开的起点 */
  .hello-enter-to,.hello-leave {
    transform: translateX(0);
  }
  1. 第三方动画库 Animate.css
<transition-group appear
          name="animate__animated animate__bounce"
          enter-active-class="animate__swing"
          leave-active-class="animate__backOutUp">
  <h1 v-show="!isShow" key="1">你好啊!</h1>
  <h1 v-show="isShow" key="2">尚硅谷!</h1>
</transition-group>

Vue中的Ajax 配置代理 slot插槽

Vue脚手架配置代理

本案例需要下载 axiosnpm install axios
配置参考文档 Vue-Cli devServer.proxy
vue.config.js 是一个可选的配置文件,如果项目的 (和 package.json 同级的) 根目录中存在这个文件,那么它会被 @vue/cli-service 自动加载。你也可以使用 package.json 中的 vue 字段,但是注意这种写法需要你严格遵照 JSON 的格式来写

方法一

vue.config.js 中添加如下配置

module.exports = {
  devServer:{
    proxy:"http://localhost:5000"
  }
}
  • 优点:配置简单,请求资源时直接发给前端(8080)即可
  • 缺点:不能配置多个代理,不能灵活的控制请求是否走代理
  • 工作方式:若按照上述配置代理,当请求了前端不存在的资源时,才会将请求会转发给服务器 (优先匹配前端资源)

方法二

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

module.exports = {
	devServer: {
      proxy: {
      '/api1': {													// 匹配所有以 '/api1'开头的请求路径
        target: 'http://localhost:5000',	// 代理目标的基础路径
        pathRewrite: {'^/api1':''},				// 代理往后端服务器的请求去掉 /api1 前缀
        ws: true,													// WebSocket
        changeOrigin: true,  // 是否改变来源
        
      },
      '/api2': {
        target: 'http://localhost:5001',
        pathRewrite: {'^/api2': ''},
        changeOrigin: true
      }
    }
  }
}
/*
   changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
   changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:8080
   changeOrigin默认值为true
*/
  • 优点:可以配置多个代理,且可以灵活的控制请求是否走代理
  • 缺点:配置略微繁琐,请求资源时必须加前缀

slot 插槽

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

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

使用方式

  • 默认插槽
<Category>
  <div>html结构1</div>
</Category>
子组件中:Category
<template>
  <div>
    <!-- 定义插槽 -->
    <slot>插槽默认内容...</slot>
  </div>
</template>
  • 具名插槽
    父组件指明放入子组件的哪个插槽 slot="footer" ,如果是 template 可以写成 v-slot:footer
父组件中:
        <Category>
            <template slot="center">
              <div>html结构1</div>
            </template>

            <template v-slot:footer>
               <div>html结构2</div>
            </template>
        </Category>
子组件中:
        <template>
            <div>
               <!-- 定义插槽 -->
               <slot name="center">插槽默认内容...</slot>
               <slot name="footer">插槽默认内容...</slot>
            </div>
        </template>
  • 作用域插槽
    scope 用于父组件往子组件插槽放的html结构接收子组件的数据

理解:数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定

必须要用 <template> 来包裹定义变量

  • scope="scopeData"
  • slot-scope="scopeData"

(games数据在Category组件中,但使用数据所遍历出来的结构由App组件决定)

父组件中:
        <Category>
            <template scope="scopeData">
                <!-- 生成的是ul列表 -->
                <ul>
                  <li v-for="g in scopeData.games" :key="g">{{g}}</li>
                </ul>
            </template>
        </Category>

        <Category>
            <template slot-scope="scopeData">
                <!-- 生成的是h4标题 -->
                <h4 v-for="g in scopeData.games" :key="g">{{g}}</h4>
            </template>
        </Category>
子组件中:
        <template>
            <div>
                <slot :games="games"></slot>
            </div>
        </template>
		
        <script>
            export default {
                name:'Category',
                props:['title'],
                //数据在子组件自身
                data() {
                    return {
                        games:['红色警戒','穿越火线','劲舞团','超级玛丽']
                    }
                },
            }
        </script>

注意:关于样式,既可以写在父组件中,解析后放入子组件插槽;也可以放在子组件中,传给子组件再解析

Vuex

理解 Vuex

Vuex 是什么

  • 概念:专门在Vue中实现集中式状态(数据)管理的一个Vue插件,对Vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信
  • Vuex Github地址

20210811221043

20210811221049

什么时候使用 Vuex

  • 多个组件依赖于同一状态
  • 来自不同组件的行为需要变更同一状态

Vuex 工作原理图

image-1683557952835

搭建 Vuex 环境

  1. 下载安装 vuex npm i vuex
  2. 创建 src/store/index.js 该文件用于创建 Vuex 中最为核心的 store
import Vue from 'vue'
import Vuex from 'vuex'	// 引入Vuex

Vue.use(Vuex)	// 应用Vuex插件

const actions = {}		// 准备actions——用于响应组件中的动作
const mutations = {}	// 准备mutations——用于操作数据(state)
const state = {}			// 准备state——用于存储数据

// 创建并暴露store
export default new Vuex.Store({
	actions,
	mutations,
	state,
})
  1. src/main.js 中创建 vm 时传入 store 配置项
import Vue from 'vue'
import App from './App.vue'
import store from './store'	// 引入store

Vue.config.productionTip = false

new Vue({
	el: '#app',
	render: h => h(App),
	store,										// 配置项添加store
	beforeCreate() {
		Vue.prototype.$bus = this
	}
})

getters 配置项

  • 概念:当 state 中的数据需要经过加工后再使用时,可以使用 getters 加工,相当于全局计算属性
  • store.js 中追加 getters 配置
......

const getters = {
	bigSum(state){
		return state.sum * 10
	}
}

// 创建并暴露store
export default new Vuex.Store({
	......
	getters
})
  1. 组件中读取数据 $store.getters.bigSum
<h3>当前求和的10倍为:{{ $store.getters.bigSum }}</h3>

四个 map 方法的使用

  1. mapState 方法:用于帮助映射 state 中的数据为计算属性
computed: {
  	// 借助mapState生成计算属性:sum、school、subject(对象写法一)
  	...mapState({sum:'sum',school:'school',subject:'subject'}),

  	// 借助mapState生成计算属性:sum、school、subject(数组写法二)
  	...mapState(['sum','school','subject']),
},
  1. mapGetters 方法:用于帮助映射 getters 中的数据为计算属性
computed: {
    //借助mapGetters生成计算属性:bigSum(对象写法一)
    ...mapGetters({bigSum:'bigSum'}),

    //借助mapGetters生成计算属性:bigSum(数组写法二)
    ...mapGetters(['bigSum'])
},
  1. mapActions 方法:用于帮助生成与 actions 对话的方法,即包含 $store.dispatch(xxx) 的函数
methods:{
    //靠mapActions生成:incrementOdd、incrementWait(对象形式)
    ...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})

    //靠mapActions生成:incrementOdd、incrementWait(数组形式)
    ...mapActions(['jiaOdd','jiaWait'])
}
  1. mapMutations 方法:用于帮助生成与 mutations 对话的方法,即包含 $store.commit(xxx) 的函数
methods:{
    //靠mapActions生成:increment、decrement(对象形式)
    ...mapMutations({increment:'JIA',decrement:'JIAN'}),
    
    //靠mapMutations生成:JIA、JIAN(对象形式)
    ...mapMutations(['JIA','JIAN']),
}

注意: mapActionsmapMutations 使用时,若需要传递参数需要:在模板中绑定事件时传递好参数,否则参数是事件对象

模块化+命名空间

  • 目的:让代码更好维护,让多种数据分类更加明确
  • 修改 store.js
    为了解决不同模块命名冲突的问题,将不同模块的 namespaced: true ,之后在不同页面中引入 getteractionsmutations 时,需要加上所属的模块名
const countAbout = {
  namespaced: true,	// 开启命名空间
  state: {x:1},
  mutations: { ... },
  actions: { ... },
  getters: {
    bigSum(state){ return state.sum * 10 }
  }
}

const personAbout = {
  namespaced: true,	// 开启命名空间
  state: { ... },
  mutations: { ... },
  actions: { ... }
}

const store = new Vuex.Store({
  modules: {  // 分包
    countAbout,
    personAbout
  }
})
  • 开启命名空间后,组件中读取 state 数据
// 方式一:自己直接读取
this.$store.state.personAbout.list
// 方式二:借助mapState读取modules属性,用countAbout.sum
...mapState('countAbout'),
// 方式三:借助mapState读取,用 sum
...mapState('countAbout',['sum','school','subject']),
  • 开启命名空间后,组件中读取 getters 数据
//方式一:自己直接读取
this.$store.getters['personAbout/firstPersonName']
//方式二:借助mapGetters读取:
...mapGetters('countAbout',['bigSum'])
  • 开启命名空间后,组件中调用 dispatch
//方式一:自己直接dispatch
this.$store.dispatch('personAbout/addPersonWang',person)
//方式二:借助mapActions:
...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
  • 开启命名空间后,组件中调用 commit
//方式一:自己直接commit
this.$store.commit('personAbout/ADD_PERSON',person)
//方式二:借助mapMutations:
...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),

Vue Router 相关理解 基本路由 多级路由

相关理解

  • vue-router 的理解
    • vue的一个插件库,专门用来实现SPA应用
  • 对SPA应用的理解
    • 单页Web应用( single page web application,SPA
    • 整个应用只有一个完整的页面
    • 点击页面中的导航链接不会刷新页面,只会做页面的局部更新
    • 数据需要通过 ajax 请求获取

image-1683572083263

路由的理解

  • 什么是路由?
    • 一个路由就是一组映射关系( key - value
    • key 为路径, value 可能是 functioncomponen
  • 路由分类
    • 后端路由
      • 理解: valuefunction ,用于处理客户端提交的请求
      • 工作过程:服务器接收到一个请求时,根据请求路径找到匹配的函数来处理请求,返回响应数据
    • 前端路由
      • 理解: valuecomponent ,用于展示页面内容
      • 工作过程:当浏览器的路径改变时,对应的组件就会显示

基本路由

  1. 安装 vue-router ,命令 npm i vue-router
    vue3 ===> vue-router@4
    vue2 ===> vue-router@3
  2. 应用插件 Vue.use(VueRouter)
  3. 编写 router 配置项
import VueRouter from 'vue-router'			// 引入VueRouter
import About from '../components/About'	// 路由组件
import Home from '../components/Home'		// 路由组件

// 创建router实例对象,去管理一组一组的路由规则
const router = new VueRouter({
	routes:[
		{
			path:'/about',
			component:About
		},
		{
			path:'/home',
			component:Home
		}
	]
})

//暴露router
export default router
  1. 实现切换
    <router-link></router-link> 浏览器会被替换为 a 标签
    active-class 可配置高亮样式
<router-link active-class="active" to="/about">About</router-link>
  1. 指定展示位 <router-view></router-view>

几个注意事项

  • 路由组件通常存放在 pages 文件夹,一般组件通常存放在 components 文件夹
    比如上一节的案例就可以修改为
    src/pages/Home.vue
    src/pages/About.vue
    src/router/index.js
    src/components/Banner.vue
    src/App.vue
  • 通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载
  • 每个组件都有自己的 $route 属性,里面存储着自己的路由信息
  • 整个应用只有一个 router ,可以通过组件的 $router 属性获取到

多级路由

  1. 配置路由规则,使用 children 配置项
routes:[
	{
		path:'/about',
		component:About,
	},
	{
		path:'/home',
		component:Home,
		children:[ 					// 通过children配置子级路由
			{
				path:'news', 		// 此处一定不要带斜杠,写成 /news
				component:News
			},
			{
				path:'message',	// 此处一定不要写成 /message
				component:Message
			}
		]
	}
]
  1. 跳转(要写完整路径)
<router-link to="/home/news">News</router-link>

路由的 query 参数

  • to 的字符串写法
  • to 的对象写法
<!-- 跳转并携带query参数,to的字符串写法 -->
<router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`">跳转</router-link>
				
<!-- 跳转并携带query参数,to的对象写法(推荐) -->
<router-link 
	:to="{
		path:'/home/message/detail',
		query:{
		   id: m.id,
       title: m.title
		}
	}"
>跳转</router-link>

接收参数

$route.query.id
$route.query.title

命名路由

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

如何使用

  1. 给路由命名
{
	path:'/demo',
	component:Demo,
	children:[
		{
			path:'test',
			component:Test,
			children:[
				{
          name:'hello' // 给路由命名
					path:'welcome',
					component:Hello,
				}
			]
		}
	]
}
  1. 简化跳转
<!--简化前,需要写完整的路径 -->
<router-link to="/demo/test/welcome">跳转</router-link>

<!--简化后,直接通过名字跳转 -->
<router-link :to="{name:'hello'}">跳转</router-link>

<!--简化写法配合传递参数 -->
<router-link 
	:to="{
		name:'hello',
		query:{
		    id:666,
        title:'你好'
		}
	}"
>跳转</router-link>

路由的 params 参数

  1. 配置路由,声明接收 params 参数
{
	path:'/home',
	component:Home,
	children:[
		{
			path:'news',
			component:News
		},
		{
			component:Message,
			children:[
				{
					name:'xiangqing',
					path:'detail/:id/:title', // 🔴使用占位符声明接收params参数
					component:Detail
				}
			]
		}
	]
}
  1. 传递参数
    特别注意:路由携带 params 参数时,若使用 to 的对象写法,则不能使用 path 配置项,必须使用 name 配置!
<!-- 跳转并携带params参数,to的字符串写法 -->
<router-link :to="/home/message/detail/666/你好">跳转</router-link>
				
<!-- 跳转并携带params参数,to的对象写法 -->
<router-link 
	:to="{
		name:'xiangqing',
		params:{
		   id:666,
       title:'你好'
		}
	}"
>跳转</router-link>
  1. 接收参数
$route.params.id
$route.params.title

路由的 props 配置

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

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

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

	//第二种写法:props值为布尔值,为true时,则把路由收到的所有params参数通过props传给Detail组件
	// props:true
	
	//第三种写法:props值为函数,该函数返回的对象中每一组key-value都会通过props传给Detail组件
	props($route){
		return {
			id: $route.query.id,
			title: $route.query.title
		}
	}
    // 或者 结构
    props({query}){
		return {
			id: query.id,
			title: query.title
		}
	}
}

Vue Router replace 编程式导航 缓存路由组件

路由跳转的 replace 方法

  • 作用:控制路由跳转时操作浏览器历史记录的模式
  • 浏览器的历史记录有两种写入方式: pushreplace
    • push 是追加历史记录
    • replace 是替换当前记录,路由跳转时候默认为 push 方式
  • 开启replace模式
    <router-link :replace="true" ...>News</router-link>
    简写 <router-link replace ...>News</router-link>

总结:浏览记录本质是一个栈,默认 push ,点开新页面就会在栈顶追加一个地址,后退,栈顶指针向下移动,改为 replace 就是不追加,而将栈顶地址替换

编程式路由导航(不用 <router-link>

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

  • this.$router.push({}) 内传的对象与 <router-link> 中的to相同
  • this.$router.replace({})
  • this.$router.forward() 前进
  • this.$router.back() 后退
  • this.$router.go(n) 可前进也可后退, n 为正数前进 n ,为负数后退
this.$router.push({
	name:'xiangqing',
  params:{
    id:xxx,
    title:xxx
  }
})

this.$router.replace({
	name:'xiangqing',
  params:{
    id:xxx,
    title:xxx
  }
})

缓存路由组件

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

<keep-alive include="News"><router-view></router-view></keep-alive>

<keep-alive :include="['News', 'Message']"><router-view></router-view></keep-alive>

// 缓存一个路由组件
<keep-alive include="News"> // include中写想要缓存的组件名,不写表示全部缓存
    <router-view></router-view>
</keep-alive>

// 缓存多个路由组件
<keep-alive :include="['News','Message']"> 
    <router-view></router-view>
</keep-alive>

activated deactivated

activateddeactivated路由组件所独有的两个钩子,用于捕获路由组件的激活状态
具体使用

  • activated 路由组件被激活时触发
  • deactivated 路由组件失活时触发

路由守卫

作用:对路由进行权限控制
分类:全局守卫、独享守卫、组件内守卫

  1. 全局守卫
    meta 路由源信息
// 全局前置守卫:初始化时、每次路由切换前执行
router.beforeEach((to,from,next) => {
	console.log('beforeEach',to,from)
	if(to.meta.isAuth){ // 判断当前路由是否需要进行权限控制
		if(localStorage.getItem('school') === 'atguigu'){ // 权限控制的具体规则
			next()	// 放行
		}else{
			alert('暂无权限查看')
		}
	}else{
		next()	// 放行
	}
})

// 全局后置守卫:初始化时、每次路由切换后执行
router.afterEach((to,from) => {
	console.log('afterEach',to,from)
	if(to.meta.title){ 
		document.title = to.meta.title //修改网页的title
	}else{
		document.title = 'vue_test'
	}
})
  1. 独享守卫
beforeEnter(to,from,next){
	console.log('beforeEnter',to,from)
    if(localStorage.getItem('school') === 'atguigu'){
        next()
    }else{
        alert('暂无权限查看')
    }
}
  1. 组件内守卫
beforeRouteEnter (to, from, next) {... next()},

//离开守卫:通过路由规则,离开该组件时被调用
beforeRouteLeave (to, from, next) {... next()},

meta 配置信息

在定义路由的时候可以在 meta 选项中填入配置信息,后面可以直接通过 to.meta.auth 获取

{
  name:'xiangqing',
  path:'detail',
  component:Detail,
  meta:{auth:true},
}
router.afterEach((to,from)=>{
	console.log('后置路由守卫',to,from)
	document.title = to.meta.auth || '硅谷系统'
})

路由器的两种工作模式

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

Vue UI 组件库

移动端常用UI组件库

PC端常用UI组件库

element-ui按需引入

element-ui

babel-plugin-component