Vue2笔记

本节代码可以从git clone https://gitee.com/fullstacker1024/vue2-component-demo.git下载
其中项目实例:git clone https://gitee.com/fullstacker1024/vue2-shopping.git

1、快速上手

1)概念

2)创建实例

3)插值表达式 {{}}

插值表达式是一种Vue的模板语法。
(1)作用:
利用表达式进行插值,渲染到页面中表达式:是可以被求值的代码,JS引擎会将其计算出一个结果
(2)语法:{{表达式}}

<h3>{{title}}</h3>
<p>{{nickname.toUpperCase}}</p>
<p>{{age>=18?'成年':'未成年'}}</p>
<p>{{obj.name}}</p>

(3)注意点:
①使用的数据必须存在(data)
②支持的是表达式,而非语句,比如:if for....

{{if}}//有误

③不能在标签属性中使用{{}}插值

<p title="{{username}}">我是p标签</p>//有误

3)响应式特性

(1)什么是响应式
数据改变,视图自动更新
(2)如果访问或修改数据
data中的数据,最终会被添加到实例上:
①访问数据:“实例.属性名”
②修改数据:“实例.属性名”=“值”

2、指令

Vue根据不同的【指令】,针对标签实现不同的【功能】。
指令:带有v-前缀的特殊标签属性
普通标签属性:

<div class="box">...</div>
<div title="小张">...</div>

Vue指令:

<div v-html="str">...</div>

1)v-html

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

2)v-show

(1)作用:控制元素显示隐藏
(2)语法:v-show="表达式",表达式值true显示,false隐藏
(3)原理:切换display:none控制显示隐藏
(4)场景:频繁切换显示隐藏的场景

3)v-if

(1)作用:控制元素显示隐藏(条件渲染)
(2)语法:v-if="表达式",表达式值true显示,false隐藏
(3)原理:基于条件判断,是欧服创建或移除元素节点
(4)场景:要么显示,要么隐藏,不频繁切换的场景

4)v-else、v-else-if

(1)作用:辅助v-if进行判断渲染
(2)语法:v-elsev-else-if="表达式"
(3)注意:需要紧挨着v-if一起使用

5)v-on

(1)作用:注册事件=添加监听+提供处理逻辑
(2)语法:
v-on:事件名="内嵌语句"
v-on:事件名="methods中的函数名"
(3)简写:@事件名="函数或内嵌语句"

<button v-on:click="count++">按钮</button>
<button v-on:click="fn">按钮</button>
<button @click="count++">按钮</button>

注意:methods函数内的this指向Vue实例

6)v-bind

(1)作用:动态的设置html的标签属性,如src、url、title...
(2)语法:v-bind:属性名="表达式"
(3)简写形式::属性名="表达式"

<img v-bind:src="url">
<img :src="url">

const app=new Vue({
  el:'#app',
  data:{
    url:"图片路径"
  }
})

(4)v-bind对于样式控制的增强-操作class
语法::class="对象/数组"
①对象:键就是类名,值是布尔值,如果值为true,有这个类,否则没有这个类

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

通用场景:一个类名来回切换
②数组:数组中所有的类,都会加到盒子上,本质就是一个class列表

<div :class="[类名1,类名2]"></div>

通用场景:批量添加或者删除类

(5)v-bind对于样式控制的增强-操作style
语法::style="样式对象"

<div :style="{css属性名1:css属性值1,css属性名2:css属性值2}"></div>

7)v-for

(1)作用:基于数据循环,多次渲染整个元素。(数组、对象、数字....)

<p v-for="">内容</p>

(2)遍历数组语法:
v-for="(item,index) in 数组"
item为每一项,index为下标
省略index:v-for="item in 数组"
(3)v-for中的key
①语法:key属性="唯一标识"
②作用:给列表项添加唯一标识,便于vue进行列表项的正确排序复用。

<ul>
    <li v-for="(item,index) in booksList" :key="item.id">
        <span>{{item.name}}</span>
    </li>
</ul>

③注意点:

key的值只能是字符串或数字类型;
key的值必须具有唯一性;
推荐使用id作为key,不推荐使用index(会变化,不对应)作为key;

7)v-model

(1)作用:给表单元素使用,双向数据绑定,可以快速获取或设置表单元素内容
①数据变化,视图自动更新
②视图变化,数据自动更新

(2)语法:v-model="变量"

(3)v-model应用于其他表单元素
常见的表单元素可以用v-model绑定关联,它会根据控件类型自动选取正确的方法来更新元素。

类型 关联
输入框 input:text value
文本域 textarea value
复选框 input:checkbox checked
单选框 input:radio checked
下拉菜单 select value

8)指令修饰符

通过“."指明一些指令后缀,不同后缀封装了不同的处理操作,简化了代码。
①按键修饰符:@keyup.enter键盘回车监听
②v-model修饰符:
v-model.trim去除首尾空格
v-model:number转数字
③事件修饰符
@事件名.stop阻止冒泡
@事件名.prevent阻止默认行为

3、计算属性

(1)概念:基于现有的数据,计算出来的新属性,依赖的数据变化,自动重新计算。
缓存特性(提升性能)

计算属性会对计算出来的结果缓存,再次使用直接读取缓存,依赖项变化了,会自动重新计算,并再次缓存

(2)语法:
①声明在computed配置项中,一个计算属性对应一个函数
②使用起来和普通属性一样使用{{计算属性名}}

计算属性可以将一段求值的代码进行封装

computed:{
    计算属性名(){
        //一段代码逻辑(计算逻辑)
        return 结果
    }
}

(3)computed计算属性和method方法

①computed计算属性:封装了一段对于数据的处理,求得一个结果
写在computed配置项中,作为属性直接使用:this.计算属性、{{计算属性}}
②methods方法:给实例提供 一个方法,调用以处理业务逻辑
写在methods配置项中,作为方法,需要调用:this.方法名()、{{方法名}}、@事件名="方法名"

(4)计算属性完整写法
计算属性默认的简写,只能读取访问,不能“修改”,如果要“修改”,需要写计算属性的完整写法。

computed:{
    计算属性名:{
        get(){
            //一段代码逻辑(计算逻辑)
            return 结果
        },
        set(修改的值){
            //一段代码逻辑(计算逻辑)
        }
    }
}

4、watch侦听器(监视器)

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

data:{
  words:'苹果',
  obj:{words:'苹果'}
},
watch{
  //该方法会在数据变化时,触发执行
  数据属性名(newValue,oldValue){
    //一些业务逻辑或异步操作
  },
  '对象.属性名'(newValue,oldValue){
    //一些业务逻辑或异步操作
  }
}

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

data:{
  obj:{
    words:'苹果',
    lang:'italy'
  }
},
watch:{
  数据属性名:{
    deep:true,//对obj的words和lang同时监视,任意一项变化
    handler(newValue){
      //一些业务逻辑或操作
    }
  }
}

eg.

点击查看代码
<div id="app">
    <div>
        <span>翻译成的语言:</span>
        <select v-model="lang">
            <option value="italy">意大利</option>
            <option value="english">英语</option>
        </select>
    </div>
    <div>
        <div>
            <textarea v-model="obj.words"></textarea>
        </div>
        <div>
            <div>{{result}}</div>
        </div>
    </div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.js"></script>

<script>
    //https://applet-base-api-t.itheima.net/api/translate
    const app = new Vue({
        el: "#app",
        data: {
            obj: {
                words: '小黑',
                lang: 'italy'
            },
            result: '',
        },
        watch: {
            obj: {
                deep: true,
                immediate: true,
                handler(n) {
                    clearTimeout(this.timer)
                    this.timer = setTimeout(async () => {
                        const res = await axios({
                            url: 'https://applet-base-api-t.itheima.net/api/translate',
                            params: 'n'
                        })
                        this.result = res.data.data
                        console.log(res.data.data)
                    })
                }
            }
        }
    })
</script>

5、生命周期

Vue生命周期:一个Vue实例从创建到销毁的整个过程。
生命周期四个阶段:创建、挂载、更新、销毁
image
Vue生命周期过程中,会自动运行一些函数,被称为【生命周期钩子】,让开发者可以在【特定阶段】运行自己的代码。
image

6、工程化开发入门

开发Vue的两种方式:
①核心包传统开发模式:基于html/css/js文件,直接引入核心包,开发Vue
②工程化开发模式:基于构建工具(例如:webpack)的环境中开发Vue
image

1)工程化和脚手架

Vue CLI是Vue官方提供的一个全局命令工具,可以帮助我们快速创建一个开发Vue项目的标准化基础架子。【集成了webpack配置】
好处:开箱即用,零配置;内置babel等工具;标准化;
image

使用步骤:

①全局安装一次:yarn global add @vue/cli或者npm i @vue/cli -g
②查看Vue版本:vue --version
③创建项目架子:vue create project-name
④启动项目:yarn serve或者npm run serve

2)项目运行流程

(1)脚手架目录文件结构
image

(2)main.js

//main.js :文件的核心作用:导入APP.vue,基于APP.vue创建结构渲染index.html
//1、导入Vue核心包
import Vue from 'vue'
//2、导入App.vue根组件
import App from './App.vue'
//3、提示:当前处于什么环境(prod or dev)
Vue.config.productionTip = false

//4、Vue实例化,提供render方法->基于App.vue创建结构渲染index.html
new Vue({
  //el:'#app',作用:和$mount('#app')作用一致,用于指定vue所管理的容器
  //render:h=>h(App)等价于以下结构
  render:(createElement)=>{
    return createElement(App)
  }
}).$mount('#app')

3)组件化和根组件

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

(2)组件分类:普通组件、根组件

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

(3)App.vue文件(单个组件)的三个组成部分:结构(tempate)、样式(style,可支持less,需要装包)、行为(script)

(4)让组件支持less:

①style标签,<style lang="less">,开启less公共

②装包:yarn add less less-loader

4)普通组件的注册

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

①创建.vue文件(三个组成部分)

②在使用的组件内导入并注册
image

使用:当成html标签使用,<组件名></组件名>

注意:组件命令规范,要是用大驼峰命名法,如HmHeader

(2)全局注册:所有组件内都能使用
①创建.vue文件(三个部分)
②在main.js中进行注册
image
使用同局部组件

5)组件的三大组成部分(结构、样式、逻辑)

image
(1)scoped样式冲突

默认情况下,写在组件中的样式会全局生效,因此很容易造成多个组件之间的样式冲突问题。

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

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

<style scoped>
</style>

scoped原理:

①给当前组件模板的所有元素,都会被添加上一个自定义属性data-v-hash值,以区分不同的组件
②css选择器后面,被自动处理,添加上属性选择器:div[data-v-hash值]

最终效果:必须是当前组件的元素,才会有这个自定义属性,才会被这个样式作用到。

(2)data是一个函数

一个组件的data选型必须是一个函数,保证每个组件实例,维护独立的一份数据对象。

data(){
  return{
    count:20
  }
}

7、组件通信

组件的数据是独立的,无法直接访问其他组件的数据,组件通信就是指组件与组件之间的数据传递。
(1)组件关系的分类:父子关系、非父子关系
(2)组件通信解决方案:
image
(3)父子通信流程图
①父组件通过props将数据传递给子组件
②子组件利用$emit通知父组件修改更新
image
(4)什么是prop
Prop定义:组件上注册的一些自定义属性
Prop作用:向子组件传递数据
特点:可以传递任意数量的prop,可以传递任意类型的prop
image

  • props校验:

为组件的prop指定验证要求,不符合要求,控制台就会有错误提示,可以帮助开发者快速发现错误。(类型校验、非空校验、默认值、自定义校验)

props:{
	校验的属性名:类型//Number String Boolean...
	required:true,//是否必填
	default:默认值,//默认值
	validator(value){
	return 是否通过校验//自定义校验逻辑
	}
}

(5)prop&data、单项数据流
共同点:都可以给组件提供数据
区别:

①data的数据是自己的,可以随便改
②prop的数据是外部的,不能直接改,要遵循单向数据流
单向数据流:父级prop的数据更新,会向下流动,影响子组件。

1)父传子

(1)实例
image

2)子传父

(1)实例
image

3)非父子

(1)event bus事件总线
作用:非父子组件之间,进行简易消息传递。(复杂场景使用Vuex)
image
(2)provide & inject
作用:跨层级共享数据
image

8、进阶语法

1)v-model

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

<template>
	<div id="app">
		<input v-model="msg" type="text">
		<input :value="msg" @input="msg=$event.target.value" type="text">
	</div>
</template>

(2)表单类组件封装
实现子组件和父组件数据的双向绑定
image

(3)v-model简化代码
实现子组件和父组件数据双向绑定
image

2)sync修饰符

作用:实现子组件与父组件数据的双向绑定,简化代码
特点:prop属性名可以自定义,非固定为value
场景:封装弹框类的基础组件,visible属性 true显示 false隐藏
本质:就是:属性名@update:属性名合写

//父组件
<BaseDialog :visible.sync="isShow"/>
<BaseDialog :visible="isShow" @update:visible="isShow = $event"/>
//子组件
props:{
	visible:Boolean
},

this.$emit('update:visible',false)

image

3)ref和$ref

作用:利用ref和$ref可以用于获取dom元素或组件实例
特点:查找范围是当前组件内(更精确稳定)
(1)获取dom
①目标标签-添加ref属性

<div ref="chartRef">我是渲染图表的容器</div>

②恰当的时机,通过this.$refs.xxx获取目标标签

mounted(){
	console.log(this.$refs.chartRef)
}

使用querySelector查找的是整个页面
image
(2)获取组件
①目标组件-添加ref属性

<BaseForm ref=baseForm"></BaseForm>

②恰当时机,通过this.$refs.xxx获取目标组件,就可以调用组件对象里面的方法

this.$refs.baseForm.组件方法()

image

4)$nextTick

Vue异步更新DOM的,想要在DOM更新完成之后做某事,可以使用$nextTick
$nextTick:等DOM更新后,才会触发执行此方法里的函数体
语法:this.$nextTick(函数体)

this.$nextTick(()=>{
	this.$refs.inp.focus()
})

9、自定义指令

自定义指令:自己定义的指令,可以封装一些dom操作,扩展额外功能。

1)指令注冊

(1)全局注册

Vue.directive('指令名', {
  inserted (el) {
    el.focus()
  }
})

(2)局部注册

directives: {
    指令名 : {
      inserted(el){
        el.focus()
      }
    }
  }

2)指令使用

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

3) 自定义指令 - 指令的值

(1)语法:

①v-指令名 = “指令值” ,通过等号可以绑定指令的值
②通过binging.value可以拿到指令的值
③在inserted钩子中,binding.value判断指令的值,设置默认状态
③通过update钩子,可以监听指令值的变化,进行dom更新操作

(2)实例代码:

点击查看代码
<template>
  <div>
    <h1 v-color="color1">指令的值1测试</h1>
    <h1 v-color="color2">指令的值2测试</h1>
  </div>
</template>

<script>
export default {
  data () {
    return {
     color1:'red',
     color2:'green' 
    }
  },
  directives: {
    color: {
      inserted (el, binding) {
        el.style.color=binding.value
      },
      update (el,binding) {
        el.style.color=binding.value 
      }
    }
  }
}
</script>

<style>

</style>

4)自定义指令 - v-loading指令封装

场景: 实际开发过程中,发送请求需要时间,在请求的数据未回来时,页面会处于空白状态 => 用户体验不好
需求: 封装一个v-loading指令,实现加载中的效果
(1)核心思路:

①准备类名Loading,通过伪元素提供遮罩层
②添加或移除类名,实现loading蒙层的添加移除
③ 利用指令语法,封装v-loading通用指令
④inserted钩子中,binding.value判断指令的值,设置默认状态
⑤update钩子中,binding.value判断指令的值,更新默认状态

(2)实例代码:

点击查看代码
<template>
  <div class="main">
    <div class="box" v-loading="isLoading">
      <ul>
        <li v-for="item in list" :key="item.id" class="news">
          <div class="left">
            <div class="title">{{ item.title }}</div>
            <div class="info">
              <span>{{ item.source }}</span>
              <span>{{ item.time }}</span>
            </div>
          </div>
          <div class="right">
            <img :src="item.img" alt="" />
          </div>
        </li>
      </ul>
    </div>
  </div>
</template>

<script>
// 安装axios =>  yarn add axios || npm i axios
import axios from "axios";

// 接口地址:http://hmajax.itheima.net/api/news
// 请求方式:get
export default {
  data() {
    return {
      list: [],
      isLoading: true,
      isLoading2: false,
    };
  },
  directives: {
    loading: {
      inserted(el, binding) {
        console.log('inserted',binding.value)
        if (binding.value) {
          el.classList.add("loading");
        } else {
          el.classList.remove("loading");
        }
      },
      update(el, binding) {
        console.log('update',binding.value)
        if (binding.value) {
          el.classList.add("loading");
        } else {
          el.classList.remove("loading");
        }
      },
    },
  },
  async created() {
    // 1. 发送请求获取数据
    const res = await axios.get("http://hmajax.itheima.net/api/news");

    setTimeout(() => {
      // 2. 更新到 list 中,用于页面渲染 v-for
      this.list = res.data.data;
      this.isLoading = false;

    }, 1000);

  },
};
</script>

<style scoped>
.loading:before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  background: #fff url("./loading.gif") no-repeat center;
}

.box2 {
  width: 400px;
  height: 400px;
  border: 2px solid #000;
  position: relative;
}

.box {
  width: 800px;
  min-height: 500px;
  border: 3px solid orange;
  border-radius: 5px;
  position: relative;
}
.news {
  display: flex;
  height: 120px;
  width: 600px;
  margin: 0 auto;
  padding: 20px 0;
  cursor: pointer;
}
.news .left {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding-right: 10px;
}
.news .left .title {
  font-size: 20px;
}
.news .left .info {
  color: #999999;
}
.news .left .info span {
  margin-right: 20px;
}
.news .right {
  width: 160px;
  height: 120px;
}
.news .right img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
</style>

10、插槽

1)默认插槽

(1)默认插槽
作用: 让组件内部的一些结构支持自定义
场景: 当组件内某一部分结构不确定,用slot占位封装
使用:
①先在组件内用slot占位

//MyDialog.vue
<template>
<div> <slot></slot> </div>
</template>

②使用组件时,传入具体标签内容插入

<MyDialog>
默认插槽
</MyDialog>

(2)后备内容(默认值)
封装组件时,可以为预留的"< slot >"插槽提供后备内容(默认内容)
使用:
①给插槽设置默认显示内容:在slot标签内,写好后备内容,

//MyDialog.vue
<template>
<div> <slot>我是后备内容</slot> </div>
</template>

②外部使用组件时,不传东西,则slot会显示后备内容:我是后备内容

<MyDialog>
</MyDialog>

or外部使用组件时,传东西了,则slot整体会被换掉:我是内容

<MyDialog>
我是内容
</MyDialog>

2)具名插槽

需求: 一个组件内有多处结构,需要外部传入标签,进行定制
语法:
(1)多个slot使用name属性区分名字

点击查看代码
<div class="dialog">
    <div class="dialog-header">
      <slot name="head"></slot>
    </div>
    <div class="dialog-content">
      <slot name="content"></slot>
    </div>
    <div class="dialog-footer">
      <slot name="footer"></slot>
    </div>
  </div>

(2)template配合v-slot:名字来分发对应标签,可简写为#名字

点击查看代码
<MyDialog>
      <template  v-slot:head><div>我是标题</div></template>
      <template v-slot:content><div>我是内容</div></template>
      <template #footer>
        <button>确认</button>
        <button>取消</button>
      </template>
    </MyDialog>

3)作用域插槽

定义slot插槽的同时,是可以传值的。给插值上可以绑定数据,将来使用组件时可以用
(1)给slot标签,以添加属性的方式传值
<slot :id="item.id" msg="测试文本"></slot>
(2)所有添加的属性,都会被收集到一个对象中
{id:3, msg:'测试文本'}
(3)在template中,通过 #插槽名"=obj" 接收,默认插槽名为default

<MyTable :data="list">
      <template #default="obj">
        <button @click="del(obj.row.id)">删除</button>
      </template>
</MyTable>

11、路由的基本使用

单页应用程序:SPA-Single Page Application
所有功能在一个html页面上实现,如网易云音乐
image
Vue中的路由:路径和组件的映射关系

1)VueRouter的介绍

作用:修改地址栏路径时,切换显示匹配的组件
说明:是Vue官方的一个路由插件,是一个第三方包

2)VueRouter的使用(5+2)

(1)5个基础步骤(固定)
①下载VueRouter模块到当前工程,vue2对应的版本是3.6.5

yarn add vue-router@3.6.5

②main.js引入

import VueRouter from 'vue-router'

③安装注册

Vue.use(VueRouter)

④创建路由对象

const router = new VueRouter()

⑤注入,将路由对象注入到new Vue实例中,建立关联

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

(2)2个核心步骤
①创建需要的组件(views目录),在main.js中配置路由规则

//在Vue..use(VueRouter);后
import ViewsParent from "@/views/ViewsParent.vue";
import viewsDialog from "@/views/ViewsDialog.vue";
const router = new VueRouter({
  routes: [
    { path: "/parent", component: ViewsParent },
    { path: "/dialog", component: viewsDialog },
  ],
});

②配置导航,在App.vue中配置路由出口(路由匹配的组件显示的位置)

<div id="app">
  <a href="#/parent">parent</a><br />
  <a href="#/dialog">dialog</a>
  <router-view></router-view>
</div>

3)组件存放目录问题

组件分类:.vue文件分2类,页面组件&复用组件
注意:都是.vue文件(本质无区别)
分类开来更易维护

①src/views文件夹:页面组件,用于页面展示,配合路由用
②src/components文件:复用组件,用于展示数据,常用于复用

12、路由进阶

1)路由模块封装

目标:将路由模块抽离出来。好处:拆分模块,利于维护。

新建router目录,在该目录下新建index.js,将main.js中有关路由的内容抽离到index.js中

  • main.js
点击查看代码
//main.js
import router from "@/router/index.js";

new Vue({
  render: (h) => h(App),
  router,
}).$mount("#app");
  • router/index.js
点击查看代码
//router/index.js
import Vue from "vue";
import VueRouter from "vue-router";
import ViewsParent from "@/views/ViewsParent.vue";
import viewsDialog from "@/views/ViewsDialog.vue";

Vue.use(VueRouter);

const router = new VueRouter({
  routes: [
    { path: "/parent", component: ViewsParent },
    { path: "/dialog", component: viewsDialog },
  ],
});

export default router;

2)声明式导航

(1)导航链接

  • vue-router提供了一个全局组件router-link(取代a标签)

①能跳转,配置to属性指定路径(必须)。本质还是a标签,to无需#
②能高亮,默认就会提供高亮类名,可以直接设置高亮样式

<router-link to="/find">发现</router-link>
//取代a
<a href="#/find">发现</a>
  • 说明:
    router-link自动给当前导航添加了两个高亮类名router-link-exact-active、router-link-active

①router-link-active模糊匹配:to="/my"可以匹配/my、/my/a等
②router-link-exact-active精确匹配:to="/my"仅可以匹配/my

  • 定制声明式导航的两个高亮类名:
const router=new VueRouter({
	router:[...],
	linkActiveClass:"类名1",
	linkExactActiveClass:"类名2"
})

(2)跳转传参
目标:在跳转路由时,进行传值

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

  • 配置导航链接:to="/path"?参数名=值
  • 对应页面组件接收传递过来的值:$route.query.参数名

②动态路由传参(传单个参数比较方便):

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

(3)动态路由参数可选符
/search/:words表示,必须要传参数,如果不传参数,也希望匹配,可以加个可选符“?

const router=new VueRouter({
	routes:[
		...,
		{
			path:'/search/:words?',
			component:Search
		}
	]
})

(4)路由重定向

问题 :网页打开,url默认是/路径,未匹配到组件时,会出现空白。

说明:重定向->匹配path后,强制跳转path路径
语法:{path:匹配路径,redirect:重定向到的路径}

const router=new VueRouter({
	routes:[
		{path:'/',redirect:'/'}
	]
})

(5)Vue路由-404
作用:当路径找不到匹配时,给个提示页面
位置:配在路由最后

  • 语法:path:"*"(任意路径)-前面不匹配就命中最后这个
const router=new VueRouter({
	routes:[
		{path:'*',component:NoFound}
	]
})

(6)Vue路由-模式设置

问题:路由的路径看起来不自然,有#,能否切成真正路径形式

①hash路由(默认):http://localhost:8080/#/home
②history路由(常用):http://localhost:8080/home (上线需要服务器支持)

const router=new VueRouter({
	routes,
	mode:"history"
})

3)编程式导航

即用JS代码来进行跳转

(1)基本跳转
①path路径跳转(简易方便)

this.$router.push("路由路径")
this.$router.push({
	path:'路由路径'
})

②name命名路由跳转(适合path路径长的场景)

//使用
this.$router.push({
	name:'路由名'
})
//定义
const router=new VueRouter({
	routes:[
		{
			name:'路由名',
			path:'/path/xxxx',
			component:XXXX
		}
	]
})

(2)跳转传参
两种传参方式:查询参数传参+动态路由传参,

上面两种跳转(path路径跳转、name命名路由跳转)方式,对于这两种传参方式都支持。

①path路径跳转传参

查询参数传参:

this.$router.push("/路径?参数名1=参数值")
this.$router.push({
	path:'/路径',
	query:{
		参数名1:'参数值1'
	}
})
  • 对应页面组件接收传递过来的值:$route.query.参数名

动态路由传参:

//先配置了动态路由
this.$router.push("/路径/参数值")
this.$router.push({
	path:'/路径/参数值'
})

对应页面组件接收传递过来的值:$route.params.参数名

②name命名路由跳转传参

查询参数传参:

this.$router.push({
	name:'路由名',
	query:{
	参数名1:'参数值1'
	}
})
  • 对应页面组件接收传递过来的值:$route.query.参数名

动态路由传参:

//先配置了动态路由
this.$router.push({
	name:'路由名',
	params:{
	参数名1:'参数值1'
	}
})
  • 对应页面组件接收传递过来的值:$route.params.参数名

13、ESLint

image

//settings.json
{
"editor.codeActionsOnSave": {
    "source.fixAll": "explicit"
},
"editor.formatOnSave": false
}

image

14、VueX

vuex是一个vue的状态管理工具,状态就是数据
场景:

  • 某个状态在很多个组件来使用(个人信息)
  • 多个组件共同维护一份数据(购物车)

优势:

  • 共同维护一份数据,数据集中化管理
  • 响应式变化
  • 操作简洁

官方原文:

  • 不是所有的场景都适用于vuex,只有在必要的时候才使用vuex
  • 使用了vuex之后,会附加更多的框架中的概念进来,增加了项目的复杂度 (数据的操作更便捷,数据的流动更清晰)

1)创建仓库

安装vuex与vue-router类似,vuex是一个独立存在的插件,如果脚手架初始化没有选 vuex,就需要额外安装。
(1)安装
yarn add vuex@3 或者 npm i vuex@3
(2)新建 store/index.js 专门存放 vuex
为了维护项目目录的整洁,在src目录下新建一个store目录其下放置一个index.js文件。 (和 router/index.js 类似)

点击查看代码
//store/index.js
// 导入 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

(3)在 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')

(4)测试打印Vuex

//App.vue
created(){
  console.log(this.$store)
}

2)核心概念 - state 状态

(1)提供数据
State提供唯一的公共数据源,所有共享的数据都要统一放到Store中的State中存储。在State对象中可以添加我们要共享的数据。

state状态,即数据,类似于vue组件中的data

//store/index.js
const store = new Vuex.Store({
  state: {
    count: 101,
  },
});

(2)使用数据
①通过store直接访问
Vue模板中获取:this.$store
JS文件中获取:import 导入store

模板中:{{$store.state.xxx}}
组件逻辑中:this.$store.state.xxx
JS模块中:store.state.xxx

②通过辅助函数(mapState获取 state中的数据)
mapstate是辅助函数,帮助我们把store中的数据自动映射到组件的计算属性中。
image

3)核心概念-mutations 方法

vuex同样遵循单向数据流,组件中不能直接修改仓库的数据。
(1)严格模式:strict:true
不开启严格模式不会报错,开启严格模式后直接修改会报错:
严格模式:strict:true有利于初学者,检测不规范代码,但是上线后需要移除,因为会消耗性能

image
image
(2)mutations:(含传递一个参数,多个参数使用对象或者数组)
①定义mutations对象,对象中存放修改state的方法

//store/index.js
// 创建仓库 store
const store = new Vuex.Store({
	//state省略
  mutations: {
    addCount(state) {
      state.count += 1;
    },
	//传递参数:只能传递一个参数
	addCount2(state,value) {
      state.count += value;
    },
  },
});

②组件中提交调用mutations
this.$store.commit("addCount")
this.$store.commit("addCount2",10)
(3)辅助函数:mapMutations
mapMutations和mapSate很像,它是把位于mutations中的方法提取了出来,映射组件methods中。

import {mapMutations} from 'vuex'
methods:{
 ...mapMutations(['addCount'])
}
//调用
this.addCount(10)

相当于:

methods:{
 addCount(n){
  this.$store.commit('addCount',10)
 }
}
//调用
this.addCount(10)

4)核心概念 - actions 异步操作

mutations必须是同步的(便于检测数据变化,记录调试)
(1)使用方法
不能直接操作state,如果要操作state,还是需要commit mutation

// store/index.js
mutations:{
 changeCount(state,newCount){
  state.count=newCount
 }
}
 // xxx.vue
//提供actions方法
actions:{
 setAsyncCount(context,num){
  setTimeout(()=>{//真实场景是发请求
   context.commit('changeCount',num)
  },1000)
 }
}
//调用
this.$store.dispatch('setAsyncCount',200)

(2)辅助函数 -mapActions

mapActions 是把位于 actions中的方法提取了出来,映射到组件methods

import { mapActions } from 'vuex'
methods: {
   ...mapActions(['changeCountAction'])
}
//调用
<button @click="changeCountAction(200)">+异步</button>

6)核心概念 - getters状态

Getter用于对Store中的数据进行加工处理形成新的数据。

除了state之外,有时我们还需要从state中筛选出符合条件的一些数据,这些数据是依赖state的,此时会用到getters

例如,state中定义了list,为1-10的数组:

state: {
    list: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
}

(1)定义:

组件中,需要显示所有大于5的数据,正常的方式,是需要list在组件中进行再一步的处理,但是getters可以帮助我们实现它:

//定义getters
  getters: {
    // getters函数的第一个参数是 state
    // 必须要有返回值
     filterList:  state =>  state.list.filter(item => item > 5)
  }

(2)使用

①原始方式-$store

<div>{{ $store.getters.filterList }}</div>

②辅助函数 - mapGetters

import { mapGetters } from 'vuex'
computed: {
    ...mapGetters(['filterList'])
}
//使用
 <div>{{ filterList }}</div>

7)核心概念 - module

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
这句话的意思是,如果把所有的状态都放在state中,当项目变得越来越大的时候,Vuex会变得越来越难以维护
由此,又有了Vuex的模块化。

(1)新建模块
src\store\modules中新建相应的xxx.js模块。
如:

点击查看代码
//user.js
const state = {
    userInfo: {
      name: 'zs',
      age: 18
    }
  }
  
  const mutations = {
  setUser (state, newUserInfo) {
    state.userInfo = newUserInfo
  }
}

  
  const actions = {}
  
  const getters = {
  // 分模块后,state指代子模块的state
  UpperCaseName (state) {
    return state.userInfo.name.toUpperCase()
  }
}
  
  export default {
  	namespaced: true,//命名空间
    state,
    mutations,
    actions,
    getters
  }

(2)使用模块中的state数据

①直接通过模块名访问 $store.state.模块名.xxx

$store.state.user.userInfo.name

②通过 mapState 映射:

...mapState('user', ['userInfo']),
...mapState('setting', ['theme', 'desc']),

{{userInfo.name}}

③默认根级别的映射 mapState(['xxx'])
④子模块的映射 :mapState('模块名', ['xxx']) - 需要开启命名空间 namespaced:true

(3)获取模块内的getters数据
①直接通过模块名访问 $store.getters['模块名/xxx ']

{{ $store.getters['user/UpperCaseName'] }}

②通过 mapGetters 映射

computed:{
...mapGetters('user', ['UpperCaseName'])
}

{{UpperCaseName}}

默认根级别的映射 mapGetters([ 'xxx' ])
子模块的映射 mapGetters('模块名', ['xxx']) - 需要开启命名空间

(4)获取模块内的mutations方法

默认模块中的 mutation 和 actions 会被挂载到全局,需要开启命名空间,才会挂载到子模块。

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

(5)获取模块内的actions方法
①直接通过 store 调用 $store.dispatch('模块名/xxx ', 额外参数)
②通过 mapActions 映射
默认根级别的映射 mapActions(['xxx'])
子模块的映射 mapActions('模块名', ['xxx']) - 需要开启命名空间

posted @ 2024-05-28 17:18  权杖攻城狮  阅读(27)  评论(0编辑  收藏  举报