Vue3知识学习笔录


基础知识部分

生命周期函数

概念:在生命周期中的某一时刻自动执行的函数

  • beforeCreate:在实例生成之前会自动执行的函数

  • created:在实例生成之后会自动执行

  • beforeMount:在组件内容被渲染到页面之前自动执行的函数

  • mounted:在组件内容被渲染到页面之后自动执行的函数

  • beforeUpdate:当 data 内的数据发生变化时会立即自动执行的函数

  • updated:当 data 内的数据发生变化,且已经在页面渲染后,会立即自动执行的函数

  • beforeUnmount:当页面中存在 dom 对象失效时,自动执行的函数

  • unmounted:当页面中存在 dom 对象失效时,且 dom 完全销毁后,自动执行的函数

逻辑图

1、在 beforeCreate 生命周期函数执行前,会进行事件和生命周期的初始化(init events & lifecycle);
2、在 beforeCreate 生命周期函数执行结束后,会去初始化一些数据双向绑定、依赖注入等信息(init injections & reactivity);
3、完成上述操作后,会执行 created 生命周期函数;
4、对 template 模板转换成 render 函数,如果没有定义模板,而是直接写在 html 中,也会加载转换成 render 函数;
5、执行 beforeMount 生命周期函数;
6、对 render 函数中,对节点上数据表达式引用的数据进行渲染(节点相当于 dom 节点);
7、上述的渲染结束后,会执行 mounted 生命周期函数。

Vue3 中的生命周期函数

在 Vue2 中,生命周期函数是定义在应用上的,也就是通过 Vue.createApp 创建的应用上,直接用对应mounted(){}方法即可实现,但是在 Vue3 中,使用的 ComponentAPI,没有了 Vue.createApp 的步骤,转而使用的 setup 函数,这个是有在使用生命周期函数,就需要用如下代码格式来实现。

<script setup>
import { onMounted } from "vue";

onMounted(() => {
  // 逻辑代码
});
</script>

Vue 设计模式

Vue 使用的 MVVM 设计模式:

  • M:表示 model,数据
  • V:表示 view,视图
  • VM:表示 viewModel 视图数据连接层,在代码层面,表示的是当前根组件对象,可以通过根组件对象来对其中的数据进行修改
// 创建vue应用
const app = Vue.createApp({
  data() {
    return {
      message: "hello",
    };
  },
  template: `<div>{{message}}</div>`,
});
// 这里的vm就表示id为root应用的根组件
const vm = app.mount("#root");

// 可以通过下面的表达式修改data里面的message值
// 注意这里的data前面需要加上$符号
vm.$data.message = "world";

常用指令

  • v-model:双向绑定指令

可用修饰符:

  1. lazy:表示懒加载,当输入框输入值后,并且失去焦点的时候触发
  2. number:限定输入框是数字类型,非数值字符会被自动过滤
  3. trim:将前后的空格自动去除(中间存在空格会正常保留)
<!--当输入框的值发生变化后,div标签对应的值也会发生变化-->
<input v-model="inputValue" />
<div>{{inputValue}}</div>
  • v-bind:属性的绑定操作,简写:
<!--将变量赋值给div的title属性,如果直接写title="userName"是无法实现的,
userName只会被识别为字符串,而不是变量-->
<div v-bind:title="userName"></div>
<!--简写如下-->
<div :title="userName"></div>

当在标签上将标签属性绑定动态的值时,需要使用v-bind,否则不会生效,也不能使用差值表达式。
上述代码绑定的方式,会固定属性的名称为 title,如果想属性名也是动态的,可以定义一个变量来指定属性名,如下代码:

<div id="root"></div>
<script>
  const app = Vue.createApp({
    data() {
      return {
        attrName: "title", // 指定属性名
        message: "title info", // 指定属性值
      };
    },
    template: `<div :[attrName]="message">hello world</div>`,
  });
  app.mount("#root");
</script>

注意:这种书写方式不止适用与 v-bind,同时也适用于 v-on,书写方式是一样的,但是这种方式不常用,作为冷知识了解一下。

  • v-for:循环遍历列表数据(数组数据或者对象数据)

遍历数组时对应的item是数组内的每个元素,遍历对象时,value对应的是对象内属性的值,key对应的是对象内属性名。

注意:这里涉及到在标签中添加一个:key,通过这个方式优化循环,当出现 key 相同的时候,之前遍历的标签可以复用,提高性能,key 值要保证唯一性,否则会出现覆盖的问题

const list = ["张三","李四","王五"]
const listObject = {
	"id":1,
	"name":"张三",
	"age":18
}
/*循环数组*/
<ul>
    <li v-for="(item,index) of list" :key="item">{{item}}</li>
</ul>
/*循环对象value对应字段值,key对应字段名*/
<ul>
    <li v-for="(value,key,index) of listObject" :key="value">{{value}}</li>
</ul>
  • v-show:用于判断当前标签是否显示,通过样式的 display 属性来实现,标签并没有销毁,也不会触发beforeUnmountunmounted生命周期函数
data(){
	return{
		show: true
	}
}
/*当show为true的时候显示,false的时候不显示*/
<div v-show="show"></div>
  • v-if:用于判断当前标签是否显示,通过删除和添加当前标签实现(支持v-else-ifv-else),会触发beforeUnmountUnmounted生命周期函数
data(){
	return{
		show: true,
		condition: false
	}
}
/*当show为true的时候显示,false的时候不显示*/
<div v-if="show"></div>
<div v-else-if="condition"></div>
<div v-else></div>

注意:这里需要v-ifv-else-ifelse三种标签放在一起,如果中间插入其他标签会出现报错的问题。

  • v-html:将含有html标签的变量渲染到指定的元素内
const html = "<strong>hell world</strong>"
/*此方式会直接将html作为文本内容显示在div标签内,不会渲染strong标签的效果*/
<div>{{html}}</div>
/*可以正常解析strong标签,并将hello world加粗*/
<div v-html="html"></div>
  • v-on:用来定义事件,简写@
<div v-on:click="clickBtn"></div>
<!--简写-->
<div @click="clickBtn"></div>
  • v-once:表示当前标签变量只渲染一次,即使此变量在不断的变化,也不会再重新渲染。
<input v-model="inputValue" />
<!--此时input标签内的值变化并不会引起div内的数据变化-->
<div v-once>{{inputValue}}</div>
  • v-forv-if混合使用细节
/*这种写法在Vue里面是不正确的,v-if不会生效,因为v-for优先级要高于v-if*/
template: `
	<div v-for="item in 10" :key="item" v-if="key !== 5">
        {{item}}
	</div>
`;
/*改进第一步:这种方法可以显示,但是会出现外面多包了一层div*/
template: `
	<div v-for="item in 10" :key="item">
        <div v-if="key !== 5">
            {{item}}
        </div>
	</div>
`;
/*终版:使用占位符标签:template*/
template: `
	<template v-for="item in 10" :key="item">
        <div v-if="key !== 5">
            {{item}}
        </div>
	</template>
`;

差值表达式

差值表达式中的内容只能是表达式,不能是语句。

  • 运算
{{ count * price }}
  • 调用方法
/*count是变量*/ {{ doSomething(count); }}

methods 方法

methods 里面定义方法的时候,经常会使用到 data 里面的数据,直接使用this.的方式就可以获取到 data 内数据,因为这个 this 代表的是当前的 vue 应用。但是当方法使用箭头函数的时候,this 就是指向上层结构的对象,非 vue 应用,此时使用this.就无法获取到 data 内的数据啦。

const app = Vue.createApp({
  data() {
    return {
      message: "hello world",
    };
  },
  methods: {
    // 推荐使用!!!
    handleClick() {
      // this表示当前vue应用
      console.log(this.message);
    },
    handleClick2: () => {
      // 此时这个this就是undefined
      console.log(this.message);
      //表示当前的window(窗口应用)
      console.log(this);
    },
  },
});

在 Vue3 中,不需要将方法都包裹在 methods 方法,而是直接写在 setup 中函数中。示例代码如下:

<script setup>
// 此方法可以直接用方法名调用
const handleClickMethod = () => {
  // 逻辑代码
};
</script>

计算属性 computed

在变量发生变化的时候,computed内的计算属性会自动进行重新计算并渲染到页面。(不仅限于数值的运算,可以是对象、字符串、数组等)

data(){
	return {
		count: 2,
		price: 10,
        message: '123'
	}
},
computed: {
	// 当计算属性依赖的内容发生变更时,才会重新计算
	total(){
		return this.price * this.count;
	}
},
methods:{
	// 当页面重新渲染,才会重新计算(注意:任意标签出现重新渲染,都会重新计算)
	getTotal(){
		return this.price * this.count;
	}
}
/*当动态改变message的时候,这个total值是不会变化的 */
<div>{{total}}</div>
/* 当动态改变message的时候,此div会重新渲染,重新调用getTotal方法*/
<div>{{message}} {{getTotal()}}</div>

在 Vue3 中计算属性的使用,首先需要导入 computed 才可以使用。示例如下:

<script setup>
import { computed } from "vue";

computed(() => {
  // 业务逻辑代码
});
</script>

侦听器 watcher

监听属性的变化,在侦听器的函数上有两个参数,分别是修改后和修改前的值。在实际应用中,可以通过侦听属性的变化,做异步的操作。

data(){
	return {
		count:2,
		price:5,
		total:10
	}
},
watch: {
	// current表示当前修改后的值(新值),prev表示历史值
	price(current,prev)	{
		return this.count * current;
	}
}
<div>{{total}}</div>

注意:

  • computedmethod都可以实现的时候,优先使用computed,因为computed具有缓存
  • computedwatcher都可以实现的时候,优先使用computed,因为computed更加的简洁

样式绑定

样式绑定的三种方式:

  • 字符串方式:适用于只绑定一个 class;
  • 数组方式:适用于绑定多个 class;
  • 对象方式:适用于绑定多个 class。
<style>
    .classOne {
        color :red;
    }
    .classTwo {
        font-size:100px;
    }
</style>
data(){
	return {
		classString:'classOne',
    // 对象方式
		classObject:{ classOne:true,classTwo:false },
    // 数组方式(不支持使用布尔类型动态展示样式,写定离手)
		classArray:['classOne','classTwo'],
    // 对象和数组混合模式
    classMix:['classOne',{classTwo:true}]
	}
}
template:`<div :class="classArray">hello world!</div>`

这里如果使用的是 style 属性,不是使用 class,style 对应的属性值,定义在变量里面,应该怎么去做。如下代码:

data() {
	return{
        // 字符串形式书写
		styleString:"color:red;font-size:100px;",
        // 对象形式书写(推荐写法)
		styleObject:{
			color :'red',
			font-size:100px
		}
	}
}
template:`<div :style="styleString">hello world!</div>`
template:`<div :style="styleObject">hello world!</div>`

函数的方法

数组操作函数

  • push:从尾部新增元素,可以新增多个,返回值是添加后数组元素个数,修改原数组
var arr = [1, 2, 3];
arr.push(4, 5, 6); //结果arr为:[1,2,3,4,5,6]
  • pop:从尾部删除元素,返回值是对应删除的元素,修改原数组
  • shift:从头部删除元素,返回值是对应删除的元素,修改原数组
  • unshift:从头部添加元素,可以添加多个,返回值是添加后数组元素个数,修改原数组
  • reverse:取反,返回的是取反后的数组,修改原数组
var arr = [1, 2, 3];
arr.reverse(); // arr将变为:[3,2,1]
  • sort:排序,通过函数的方式规定排序方式,修改的是原数组;(默认是升序排列)
  • splice:对应参数有 index、howmany 和 item1,item2……itemX,此操作是对原数据进行修改,具体含义如下:
参数名 含义 是否必须
index 整数,规定添加/删除项目的位置,使用负数可从数组结尾处规定位置
howmany 要删除的项目数量,如果设置为 0,则不会删除项目
item1,……,itemX 向数组添加的新项目
var arr = [1, 2, 3, 4, 5];
// 结果arr:[1,3,4,5],arr1:[2]
var arr1 = arr.splice(1, 1);
// 结果arr:[1,4,5],arr2:[2,3]
var arr2 = arr.splice(1, 2);
// 此时会将index为1的元素删除,从1开始新增两个元素6,7结果arr是:[1,6,7,3,4,5]
// 当howmany和item参数都有的时候,可以理解为替换(并非一换一,可能是一换多或者多换一)
var arr3 = arr.splice(1, 1, 6, 7);
//表示不删除元素,而是新增,最后arr为:[1,6,7,2,3,4,5]
arr.splice(1, 0, 6, 7);
  • slice:从某个已有的数组返回选定的元素,请注意,该方法并不会修改数组,而是返回一个子数组,可以通过使用负值从数组的尾部选取元素。
var arr = [1, 2, 3, 4, 5, 6];
var arr2 = arr.slice(-4, -2); //返回的arr2结果是:[3,4]
// 说明:负值从-1开始计算,slice的结果是含头不含尾
  • join:把数组的所有元素放入一个字符串中,元素通过指定的分隔符进行分割,此方法有一个参数,即指定分隔符,形成新的字符串,不修改原数组
var arr = ["a", "b", "c"];
var arrStr = arr.join("-"); // 结果是a-b-c
var arrStr = arr.join(""); // 结果是abc
  • concat:连接两个或多个数组,并返回连接后的新数组,原数组不会发生变化,此方法有一个参数,即指定需要连接的数组或数据;
var arr1 = [1, 2, 3];
var arr2 = ["a", "b", "c"];
// 对应的结果是 [1,2,3,'a','b','c']
var arr3 = arr1.concat(arr2);

在 Vue 中,实际两个数据的连接,可以简写成如下方式:

var arr1 = [1, 2, 3];
var arr2 = ["a", "b", "c"];
// 对应的结果是 [1,2,3,'a','b','c']
var arr3 = [...arr1, ...arr2];
  • filter:按照指定条件,从数组中筛选出需要的元素,筛选出后,作为一个新的数组返回。
var arr1 = ["abc", "bdc", "erd"];
//arr2的结果是:["abc"]
var arr2 = arr1.filter((item) => item === "abc");
  • find:从数组中查找指定的元素,以新的数组返回。和filter函数是类似的。
const arr = ["abc", "bcd", "erd"];
const result = arr.find((item) => item === "abc");
if (result) {
  console.log("abc");
} else {
  console.log("error");
}
  • includes:查看数组中是否包含指定的元素,包含的返回true,不包含则返回false
const whiteList = ["/login", "/home", "/404"];
const currentPath = "/home";
console.log(whiteList.includes(currentPath));

对象操作

在循环遍历对象的时候,需要向对象中添加内容,此时就要用到如下的语法:

const app = Vue.createApp({
  data() {
    return {
      user: {
        name: "张三",
        age: "23",
        weight: "120kg",
      },
      addAttr: {
        height: "177cm",
        addr: "合肥",
      },
      attrName: "height",
    };
  },
  methods: {
    changeUser() {
      // 方式一:
      this.user.height = "174cm";
      // 方式二:
      this.user[this.attrName] = "173cm";
    },
  },
  template: `<div>
        <div v-for="(value,key,index) in user" :key="key">
        {{index}}-{{key}}-{{value}}
        </div>
    </div>`,
});
const vm = app.mount("#root");

如果遇到动态添加,怎么办,就是想一个对象的所有属性添加到另一个对象中,如上代码,将 addAttr 内的属性全部添加到 user 里面,代码如下:

methods:{
    changeUser(){
        for(attr in this.addAttr){
            this.user[attr] = this.addAttr[attr];
        }
    }
}

事件绑定及修饰符

  • 在调用事件的时候需要传入参数
method:{
	clickHandle(event){
		console.log(event.target)
	}
},
// 在没有传入任何参数的时候,存在一个默认参数event,表示当前标签对象
template:`<div @click="clickHandle">hello world!</div>`
//-----------------------------
method:{
	clickHandle(num,event){
		console.log(num)
		console.log(event.target)
	}
},
// 当传入参数的时候,如果不显示写出event,则方法中无法接收,可以使用$event方式获取并作为参数传入
template:`<div @click="clickHandle(num,$event)">hello world!</div>`
  • 绑定多个事件
method:{
	clickHandle(){
		console.log(2)
	},
	clickHandle2(){
		console.log(1)
	},
},
// 错误写法
template:`<div @click="clickHandle,clickHandle2">hello world!</div>`
// 正确写法
template:`<div @click="clickHandle(),clickHandle2()">hello world!</div>`
  • 事件冒泡规则(默认)
method:{
	clickHandle(event){
		console.log(2)
	},
	divClickHandle(event){
		console.log(1)
	}
},
/*此时点击button,同时会触发clickHandle、divClickHandle两个事件,从内到外依次触发*/
template:`
<div @click="divClickHandle">
	<button @click="clickHandle">点击这里</button>
</div>
`

事件修饰符

  • prevent:阻止默认事件冒泡行为,如在form表单在action中的地址,默认会发生跳转,为了阻止跳转,可以在点击事件上增加prevent修饰符。
method:{
	clickHandle(event){
		console.log(2)
	}
},
template:`
<div>
	<button @click.prevent="clickHandle">点击这里</button>
</div>
`
  • stop:停止事件冒泡,当父标签和子标签都定义了事件,在点击子标签的时候,父标签的点击事件也会被触发,此时为了让父标签事件不触发,可用stop修饰符。
method:{
	clickHandle(event){
		console.log(2)
	},
	divClickHandle(event){
		console.log(1)
	}
},
template:`
<div @click="divClickHandle">
	<button @click.stop="clickHandle">点击这里</button>
</div>
`
  • self:父子标签都有事件,点击子标签触发子标签点击事件,点击父标签触发父标签点击事件,可以在父标签上增加self修饰符。
method:{
	clickHandle(event){
		console.log(2)
	},
	divClickHandle(event){
		console.log(1)
	}
},
template:`
<div @click.self="divClickHandle">
	<button @click="clickHandle">点击这里</button>
</div>
`

按键修饰符

  • enter:在点击enter的时候,触发事件。
template: `
<div>
	<button @keydown.enter="clickHandle">点击这里</button>
</div>
`;
  • tab:同理 enter,点击 tab 键触发
  • delete:同理 enter,点击 delete 键触发
  • esc:同理 enter,点击 esc 键触发
  • up:同理 enter,点击向上箭头键触发
  • down:同理 enter,点击向下箭头键触发

鼠标修饰符

  • left:点击左键,触发事件
template: `
<div>
	<button @click.left="clickHandle">点击这里</button>
</div>
`;
  • right:同理 left,点击右键,触发事件
  • middle:同理 left,点击中键,触发事件

精确修饰符

**exact:**比如定义了按住 ctrl,单击鼠标时触发事件,如果不用精确修饰符,就会出现按住 ctrl 及组合键,再去点击鼠标也可以触发,需要精确,就需要使用 exact 修饰符。

// 此时按住ctrl键点击鼠标即可触发,但是安装ctrl加其他组合键,在点击鼠标,也会触发
template: `<div @click.ctrl="counter+=1"></div>`;
// 只按住ctrl一个键,在点击鼠标触发,其他情况无法触发
template: `<div @click.ctrl.exact="counter+=1"></div>`;

双向绑定(表单)

  • input(text):文本输入框绑定
const app = Vue.createApp({
  data() {
    return {
      message: "",
    };
  },
  template: `<input v-model="message" />`,
});
  • textarea:文本域绑定
const app = Vue.createApp({
  data() {
    return {
      message: "",
    };
  },
  template: `<textarea v-model="message" />`,
});
  • input(checkbox):checkbox 多选框绑定
// 在多选框被选中后,value值会自动赋值给message数组,取消选中后,也会从message中去除,message是个数组
<input type="checkbox" value="id1" v-model="message"/>
<input type="checkbox" value="id2" v-model="message"/>
<input type="checkbox" value="id3" v-model="message"/>
<input type="checkbox" value="id4" v-model="message"/>
data(){
    return{
    	message:[]
    }
}

checkbox 如果没有指定 value 值,则返回的值是 true 和 false,如果指定 value 值,如果选中,返回对应的 value 值,如果未选中,返回的是空值。但是有时候会遇到在 true、false 不同情况下,返回不同的值,这个时候就可以使用下面的编写方式:

<input
  type="checkbox"
  true-value="this is true value"
  false-value="this is false value"
  v-model="message"
/>

这种方式存在一个小 bug,如果在默认情况下,指定true-value值,会出现这个选择框没有自动选择。

  • input(radio):radio 单选框绑定,和 checkbox 相同,不同在于 radio 是单选,message 使用字符串即可,无需数组

  • select-option:选择框绑定

<select v-model="message">
  <option style="color:aliceblue;" disabled value="">请选择内容</option>
  <!-- 这里的value需要用v-bind修饰,另外这个value不仅限于字符串,可以是对象等 -->
  <option v-for="item of options" :value="item.value">{{item.text}}</option>
</select>
data(){ return{ message:"", options:[{ value:"A", text:"A" },{ value:"B",
text:"B" },{ value:"C", text:"C" }] } }

☆☆☆ 组件

将一个页面拆分成多个组件,在渲染的时候,将组件拼装成一个页面,对于公用的组件,可以被多个页面使用。

  • 定义公用组件,一次定义多处使用
  • 组件内的数据是独有的,非共享的

组件可以分为局部组件全局组件,定义方式如下:

// 局部组件定义方式,需要根组件上通过声明components来引用conter局部组件
const Conter = {};
const app = Vue.createApp({
  //通过声明components来引用conter局部组件
  components: { conter: Conter },
});
// 全局组件定义方式,无论在根组件中是否使用,都会被挂载在根组件上
// 全局组件有使用简单,性能不高的特点
app.component("conter", {});

组件命名规则:

全局组件在定义组件名称的时候,采用小写字母,如果涉及多个单词,使用-来连接;
局部组件在定义名称的时候,采用首字母大写的方式,如果涉及多个单词,采用驼峰式命名。
在引用局部组件的时候,可以直接使用原名称,也可直接指定名称,如下:

components: {
  Counter, HelloWorld;
}
// 在使用的时候Vue会自动检测名称,并将Counter首字母改为小写,最终为counter
// 当时驼峰命名的时候,Vue也会自动将字母转为小写,多字母之间用中划线连接
template: `<div><conter/><hello-world/></div>`;

在 Vue3 中,组件都是以一个单独的 vue 文件存在,通过 import 方式导入即可直接使用,无需通过 components 来指定组件的名称。示例代码如下:

<template>
  <!-- 使用组件 -->
  <Counter />
</template>
<script setup>
// 导入组件
import Counter from "./components/Counter.vue";
</script>

父组件传值到子组件

  • 静态传参:通过非动态绑定的属性进行传值,传入的值提前写入,传到子组件统一转为 String 类型
const app = Vue.createApp({
  template: `<counter content="contentvalue"></counter>`,
});
  • 动态传参:通过v-bind来动态绑定属性传值,传入的值可动态改变,子组件接收的数据类型与传入的数据类型相同
const app = Vue.createApp({
    data(){
        return {
            content: "contentValue123"
        }
    }
    template:`<counter :content="content"></counter>`
});

子组件在接收值的时候可以做数据类型判断、限定等操作。如下:

// 直接接收数据
app.component("counter", {
  props: ["content"],
});
// 加入类型判断
app.component("counter", {
  props: {
    content: {
      type: Number, // 指定类型是数值类型
      required: true, // 是否必须传入
      default: 123, // 当不满足需求的时候,采用此默认值
      validator: function (value) {
        // value表示传入的数据
        // 参数校验逻辑,最终返回一个boolean类型的结果
      },
    },
  },
});

type可取的类型有:StringBooleanArrayObjectFunctionSymbol
传入 Function 的方式:

const app = Vue.createApp({
  data() {
    return {
      content: () => {
        alert(123);
      },
    };
  },
  template: `<counter :content="content"></counter>`,
});
app.component("counter", {
  props: {
    content: Function,
  },
  methods: {
    funClick() {
      this.content(); // 直接调用传过来的方法
    },
  },
  template: `{{typeof content}}<button @click="funClick()">点击操作内容</button>`,
});

在传多个数据的时候,可以使用以下两种方式:

  • 方式一:
data(){
	return {
		params:{
			a:"1",
            b:"2",
            c:"3",
		}
	}
},
template:`<div><counter v-bind="params"/></div>`
// 此时,Vue会自动将params内a、b、c赋值到props中对应的属性
app.component('counter',{
	props:['a','b','c'],
	template:`<div>{{a}}-{{b}}-{{c}}</div>`
})
  • 方式二:
data(){
	return {
		params:{
			a:"1",
      b:"2",
      c:"3"
		}
	}
},
template:`<div ><counter :params="params"/></div>`
app.component('counter',{
	props:['params'],
	template:`<div>{{params.a}}-{{params.b}}-{{params.c}}</div>`
})

由于 html 不支持驼峰规则,所有在定义标签属性的时候,涉及多个单词,需要用-连接,此时传值就存在一个问题,需要规避,如下:

data(){
	return {
		params:1234
	}
},
template:`<div ><counter :counter-params="params"/></div>` //定义采用短横线连接的方式,不支持驼峰规则
app.component('counter',{
	props:['counter-params'],
	template:`<div>{{counter-params}}</div>`
})
// 如果采用上面的方式来接收传入的值就会出现counter-params是NaN
// 规避方法:将接收值改成驼峰规则。如下:
app.component('counter',{
	props:['counterParams'],
	template:`<div>{{counterParams}}</div>`
})

在 Vue3 中,如果需要父组件传给子组件值,传入方式基本一样,使用v-bind来实现,但是在接受数据的时候存在一定的差异。示例代码如下:

<script setup>
// 导入接受参数参数的defineProps函数
import { defineProps } from "vue";

// 通过使用defineProps函数,获取所有数据,并赋值给props
const props = defineProps({
  user: {
    type: Object,
    required: true,
  },
  count: {
    type: Number,
    required: true,
  },
});

// 使用props
const countValue = props.count;
</script>

单向数据流

概念:子组件可以使用父组件传递的数据,但是不能修改传递过来的数据。

如果子组件的确需要修改传过来的数据,这个时候可以将传过来的数据复制到新的变量内,通过修改新的变量来实现。如下:

data(){
	return {
		param:1234
	}
},
template:`<div ><counter :param="param"/></div>`
app.component('counter',{
	props:['param'],
	data(){
		return{
			myCount:this.param // 复制数据
		}
	}
	template:`<div @click="myCount += 1">{{myCount}}</div>`
})

Non-props 特性

  1. 在父组件给子组件传值的时候,如果子组件没有通过props接收时,Vue 会自动将传入的属性和值添加到子组件template顶层 dom 对象中。如下:
data(){
	return {
		param:1234
	}
},
template:`<div ><counter :param="param"/></div>`
app.component('counter',{
	template:`<div>Counter</div>`
})
// 浏览器渲染后的效果如下:
<div param="1234">Counter</div>
  1. 如果此时不想 Vue 自动给顶层 dom 添加属性,可以用inheritAttrs:false来关闭此特性。如下:
data(){
	return {
		param:1234
	}
},
template:`<div ><counter :param="param"/></div>`
app.component('counter',{
	inheritAttrs:false,
	template:`<div>Counter</div>`
})
  1. 如果子组件存在多个并列的顶层 dom 对象,Vue 是无法自动实现添加属性的机制,可以手动的添加。如下:
const app = Vue.createApp({
  data() {
    return {
      param: 1234,
    };
  },
  template: `<div ><counter :param="param" message="111"/></div>`,
});

app.component("counter", {
  template: `
		<div :param="$attrs.param">Counter1</div>  //param属性
		<div :message="$attrs.message">Counter2</div> //message属性
		<div v-bind="$attrs">Counter2</div>`, // 所有属性
});
  1. 不仅如此,还可以通过生命周期函数来获取属性和属性值,如下:
app.component("counter", {
  mounted() {
    console.log(this.$attrs.param);
  },
  template: `
		<div :param="$attrs.param">Counter1</div>  //param属性
		<div :message="$attrs.message">Counter2</div> //message属性
		<div v-bind="$attrs">Counter2</div>`, // 所有属性
});

补充 Vue3 中关于 props 和 attrs 接收父组件传值的那些事

在父组件传值给子组件的时候,一般都会用 props 进行接收,但是当传入的这些值只做静态数据展示,不做任何数据相关的操作,可以不用 props 接收,直接用 attrs 在 template 中使用即可。使用的方式是$attrs.name,这种更为的简洁。但是往往应用场景较为复杂,有可能存在部分属性需要进行 js 操作,部分属性可以直接在 template 中显示。那就可以采用 props 和 attrs 混合使用的方式。示例代码如下:

<!-- 父组件 -->
<template>
  <!--父组件给子组件传值-->
  <Child :user="user" :count="count" :desc="desc" />
</template>
<script setup>
import Child from "./components/Child.vue";
const user = {
  name: "张三",
  age: 18,
};
const count = 1;
const desc = "描述信息";
</script>

<!-- 子组件 -->
<template>
  <!-- 子组件 -->
  <div>{{ $attrs.desc }}</div>
</template>
<script setup>
import { defineProps } from "vue";
const props = defineProps({
  user: {
    type: Object,
  },
  count: {
    type: Number,
  },
});
</script>

父子组件通过事件传递数据

正常的数据流是父组件将数据传给子组件,而子组件是没有修改父组件属性值的权限,只能将属性值复制到子组件内进行操作,但是可以通过事件的方式将子组件改变的数据传给父组件,由父组件进行修改操作。也就是在父组件中定义一个回调的函数,然后在子组件中调用此回调函数,最终由父组件完成修改动作。如下:

const app = Vue.createApp({
	data(){
		return {
			count:123
		}
	},
	methods:{
		addOneHandle(num){ // num是接收传递来的参数,可多个,与传入对应即可
			this.count = this.count + num;
		}
	}
  // 定义回调函数:addOneHandle
	template:`<counter :count="count" @add-one="addOneHandle"/>`
});

app.component("counter",{
	props:['count'],
	methods:{
		addOneHandle(){
      // 调用父节点的add-one事件,传入一个参数2
			this.$emit('addOne',2);
		}
	},
	template:`<div @click="addOneHandle">{{count}}</div>`
})

注意点:

  • 在使用this.$emit('addOne',2)的时候,调用事件的名称采用驼峰命名规则,@add-one父标签中命名使用中划线连接,因为 html 不支持驼峰规则规则命名
  • 在调用父组件的事件时,传入的参数个数无限制,多个参数依次向后添加即可
  • 可以通过emits属性来统一管理记录调用父组件的事件集合,如下:
app.component("counter",{
	props:['count'],
  // 这里既可以使用数组的方式,也可使用对象的方式,对象的方式可以引入函数做逻辑判断
	emits:['addOne']
	methods:{
		addOneHandle(){
      // 调用父节点的add-one事件,传入一个参数2
			this.$emit('addOne',2);
		}
	},
	template:`<div @click="addOneHandle">{{count}}</div>`
})

父子组件的双向绑定

在同一个组件里面,数据可以通过v-model进行双向绑定,在父子组件之间也能通过这种方式实现双向绑定。如下:

const app = Vue.createApp({
  data() {
    return {
      count: 123,
      other: 234,
    };
  },
  template: `<counter v-model:count="count" v-model:other="other"/>`,
});

app.component("counter", {
  props: ["count", "other"],
  methods: {
    addOneHandle() {
      this.$emit("update:count", this.count + 1);
      this.$emit("update:other", this.other + 1);
    },
  },
  template: `<div @click="addOneHandle">{{count}}-{{other}}</div>`,
});

上面这种方式可以适用于多个参数,即多个v-model:xxx="xxx",如果是单个参数,可采用下面的方式简化写成v-model="xxx",但是这种写法会影响代码的可读性,在实际开发中不建议这么写

const app = Vue.createApp({
  data() {
    return {
      count: 123,
    };
  },
  template: `<counter v-model="count" />`,
});

app.component("counter", {
  props: ["modelValue"], // 固定的名字,必须使用modelValue
  methods: {
    addOneHandle() {
      this.$emit("update:modelValue", this.modelValue + 1); // update:modelValue也是固定格式
    },
  },
  template: `<div @click="addOneHandle">{{modelValue}}</div>`,
});

在 Vue3 中使用的是 Compostion API,在 setup 中是不能使用this这个关键字的,那带来的问题就不能使用this.$emits方式回调父组件的方法。给出的解决方式如下示例代码:

<script setup>
// 导入defineEmits函数
import { defineEmits } from "vue";
// 获取回调方法
const emits = defineEmits(["update:modelValue"]);
// 使用emits回调
const handleClick = () => {
  emits("update:modelValue", "this is update value");
};
</script>

双向绑定自定义修饰符

在双向绑定的时候,如果需要子组件做一些固定的操作,就可以使用此方式,比如大小写转换。

const app = Vue.createApp({
  data() {
    return {
      content: "abc",
    };
  },
  template: `<counter v-model.uppercase="content"/>`,
});
app.component("counter", {
  props: {
    modelValue: String,
    modelModifiers: {
      // 固定写法,名称必须是modelModifiers
      default: () => ({}),
    },
  },
  methods: {
    modifier() {
      let newValue = this.modelValue + "def";
      if (this.modelModifiers.uppercase) {
        // 检查修饰符是否存在,做相应的操作
        newValue = newValue.toUpperCase();
      }
      this.$emit("update:modelValue", newValue);
    },
  },
  template: `<div>{{modelValue}}<div>
    <button @click="modifier">change</button>`,
});
app.mount("#root");

插槽分类讲解

  • 基本插槽:通过在引用子组件的时候,传递 dom 对象给子组件的一种方式,如下:
const app = Vue.createApp({
  template: `<counter>
		<div>dom</div>
    </counter>`,
});

app.component("counter", {
  template: `<div>
		<slot></slot> // 通过slot标签,将父组件counter内的所有dom引入到此位置
	</div>`,
});
  • 具名插槽:如果父组件传递多个 dom,这些 dom 需要放在子组件中的不同位置,直接采用基本插槽的全部引用,就会存在问题,这里就引入了具名组件。
const app = Vue.createApp({
  template: `<counter>
        <template v-slot:header> // 注意之而立使用冒号连接,名称无需引号包裹
			<div>header</div>
        </template>
	    <template v-slot:footer> // 使用template标签作为占位符,这里的v-slot:footer可简化为#footer
			<div>footer</div>
        </template>
    </counter>`,
});

app.component("counter", {
  template: `<div>
        <slot name="header"></slot> //将header的标签放到body前
		    <div>
            body content
        </div>
        <slot name="footer"></slot> //将footer的标签放到body后
	</div>`,
});
  • 作用域插槽:直接上代码如下:
const app = Vue.createApp({
    // 第一种写法,使用slotProps接收子组件所有的属性数据,并封装成对象赋值给slotProps
    template:`<counter v-slot="slotProps"> // 接收子组件绑定的所有属性数据
		<div>{{slotProps.index}}-{{slotProps.item}}</div>// 渲染接收到的数据
    </counter>`
    // 第二种写法,使用ES6的解构,对第一种方法的slotProps进行解构,书写格式如下:
    template:`<counter v-slot="{index,item}">
        <div>{{index}}-{{item}}</div>
    </counter>`
});

app.component("counter",{
	data(){
		return {
			list:[1,2,3],
		}
	},
	template:`<div>
		<slot v-for="(item,index) in list" :item="item" :index="index"/> //将遍历出来的数据绑定到slot上
	</div>`
})

注意:

  1. 不管是何种类型的插槽,都没办法在slot标签上绑定事件,此时可以考虑在slot标签外层添加一个span标签;
  2. 插槽内可以是 dom 对象,也可以是字符串或者其他组件;
  3. 插槽中取数的作用域,父模板调用的数据属性,使用的都是父模板里的数据,子模板调用的数据属性,使用的都是子模板里的数据;
  4. 在定义查找的时候,可以指定默认值,在父模板没有传入内容时默认显示,格式:<slot>default value</slot>

动态组件

component标签及其固定属性is来控制子组件的加载情况,实现动态组件的效果,代码如下:

const app = Vue.createApp({
  data() {
    return {
      showItem: "one-item",
    };
  },
  methods: {
    changeItem() {
      // 通过点击事件,修改需要展示的组件
      this.showItem = this.showItem === "one-item" ? "two-item" : "one-item";
    },
  },
  template: `
            <component :is="showItem"/> //通过is属性,切换显示one或者two组件
            <button @click="changeItem">切换</button>
        `,
});
app.component("one-item", {
  template: `<div>this is one-item</div>`,
});
app.component("two-item", {
  template: `<div>this is two-item</div>`,
});
app.mount("#root");

注意:如果动态组件内有类似数据框的标签,当切换的时候,原本输入框内的数据会丢失,这个时候就需要通过keep-alive标签对动态组件进行包裹。如下:(动态组件和keep-alive标签经常混合使用,以达到最好的效果)

<keep-alive>
  <component :is="showItem" />
</keep-alive>

异步组件

组件异步显示,详细见示例代码:

const app = Vue.createApp({
  template: `
        <div>this is static div</div>
        <async-item /> // 加载子组件
    `,
});
app.component(
  "async-item",
  Vue.defineAsyncComponent(() => {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve({
          template: `<div>this is async template</div>`,
        });
      }, 4000); // 4秒后加载
    });
  })
);
app.mount("#root");

ref 引用和跨子组件传值

ref 引用

  • 在 dom 对象上定义ref属性,通过this.$refs来获取 dom 对象,如下:
const app = Vue.createApp({
  mounted() {
    console.log(this.$refs.count.innerHTML()); // this.$refs.count获取count对应的dom对象
  },
  template: `
    	<div ref="count">
    		<div>this is count value<div>
    	</div>
    `,
});
  • 在子组件引用时使用,可以得到子组件对象,操作子组件内的方法
const app = Vue.createApp({
  mounted() {
    this.$refs.child.sayHello(); // this.$refs.child获取子组件child对象,通过child对象调用sayHello方法
  },
  template: `
    	<child ref="child" />
    `,
});
app.component("child", {
  methods: {
    sayHello() {
      alert("你好");
    },
  },
});

在 Vue3 中,通过 ref 获取 dom 对象和 Vue2 存在区别,主要还是体现在 Vue3 中不能使用this关键字导致的,但是在 Vue3 中获取 ref 对象更为的简洁。示例代码如下:

<template>
  <div ref="divNodeRef"></div>
</template>
<script setup>
import { ref } from "vue";
// 直接采用这种方式即可,名称和ref指定的名称保持一致
// Vue3会自动将div对应的dom对象赋值给divNodeRef
const divNodeRef = ref(null);
</script>

provide 和 inject

在组件之间值传递的时候,可能会遇到顶级组件传递数据给孙子组件,这个时候需要先传给儿子组件,再传给孙子组件,就会很冗余很麻烦,可以采用provideinject来实现,代码如下:

const app = Vue.createApp({
    data(){
        return {
            count:123
        }
    },
    provide:{
        count:123
    },
    template:`<child />`
})
app.component('child',{
    template:`<child-child />`
})
app.component('child-child',{
    inject:['count']
    template:`<div>{{count}}</div>`
})
/*
这里可以看出来一个问题,在provide里面也定义了一个count属性,
如果直接通过count=this.count能不能引用到data内的count值呢,
实际是不可以的,需要对provide进行改写,改写如下:*/
provide(){
    count:this.count
}

注意点:

  • 通过这样的传递,可以不用经过层层组件进行传递
  • provideinject配合的时候,值只会传入一次,即使后续顶层组件中count值发生变化,也不会再做同步(后续可以用 Vue3 一些新的特性实现,待学习后更新

过渡和动画效果

在 Vue 中,使用<transition>标签,能够实现过渡和动画效果。

定义过渡效果

<style>
  .v-enter-from {
    /*定义进入前的透明度 */
    opacity: 0;
  }
  .v-enter-active {
    /*定义进入过程的过渡效果 */
    transition: 2s opacity ease-out;
  }
  .v-enter-to {
    /*定义进入后的透明度 */
    opacity: 1;
  }
  .v-leave-from {
    /*定义离开前的透明度 */
    opacity: 1;
  }
  .v-leave-active {
    /*定义离开过程的过渡效果 */
    transition: 2s opacity ease-in;
  }
  .v-leave-to {
    /*定义离开后的透明度 */
    opacity: 0;
  }
</style>
<script>
  const app = Vue.createApp({
    data() {
      return {
        show: false,
      };
    },
    methods: {
      change() {
        this.show = !this.show;
      },
    },
    template: `<transition>
        <div v-show="show">Hello World</div>
        </transition>
        <button @click="change">点击切换</button>
        `,
  });
</script>

定义动画效果

<style>
  @keyframs shake {
    0% {
      transform: translateX(-100px);
    }
    50% {
      transform: translateX(-50px);
    }
    100% {
      transform: translateX(50px);
    }
  }
  ,
  .v-enter-active {
    animation: shake 3s;
  }
  .v-leave-active {
    animation: shake 3s;
  }
</style>

总结注意点

  1. 在 Vue 中,定义的的v-enter-activev-leave-active等这些 class 样式,会直接应用到transition标签上,此时必须transition标签没有指定name属性;
  2. 如果在页面中多处使用到transition标签,且对应的过渡和动画效果都不同,此时就需要给不同的transition标签定义名称,然后将 class 名称与其对应即可,如下示例:
<style>
  .my-enter-active {
    /**这里的my对应的是transition标签上的name值 */
    animation: shake 3s;
  }
</style>
<script>
  template: `<transition name="my">……</transition>`;
</script>
  1. 除第二点的自定名称方式,也可以通过在transition标签中指定 class 样式的名称,如下示例:(这种方式很实用,特别是在应用第三方动画效果时使用)
<style>
  .hello {
    animation: shake 3s;
  }
  .bye {
    animation: shake 3s;
  }
</style>
<script>
  template: `<transition 
        enter-active-class="hello"
        leave-active-class="bye"
    >……</transition>`;
</script>

其中对应的v-enter-fromv-enter-tov-leave-fromv-leave-to等都可以通过这种方式指定。

推荐一个第三方动态效果官网https://animate.style/

transition 标签的其他特性

  1. transition标签可以同时添加过渡和动画效果,但是存在过渡效果和动画效果的时长是不一样的,这个时候如果要以其中一个时间为准,可以使用type属性来解决,对应的值有transitionanimation,分别表示以过渡效果时间为准和动画效果时间为准。
  2. transition标签上可以通过duration直接指定过渡和动画效果的时长,即使对应的效果上的 CSS 已经设置时长,也会失效。示例代码如下:
const app = Vue.createApp({
  template: `
    <transition :duration="1000"></transition><!-- 表示所有的过渡和动画都是1000ms执行结束 -->
    <transition :duration="{enter:1000,leave:2000}"></transition><!-- 表示所有的过渡和动画都是进场为1000ms,出场为2000ms -->
    `,
});
  1. transition标签里面有多个元素,这多个元素使用v-if或者v-show来切换元素显示,但是实际操作中,两个标签消失效果和展示效果是同时执行的,也就是说这个时候,页面上会同时展示两个元素,但是实际需求不需要,而是想隐藏的元素根据指定效果退出后,再按指定效果将展示的元素显示出来。这个时候就可以使用model属性,指定其值为out-in,表示先消失隐藏的,再展示要显示的。
<script>
  const app = Vue.createApp({
    data() {
      return { show: false };
    },
    methods: {
      change() {
        this.show = !this.show;
      },
    },
    // 对两个div进行切换
    template: `
            <transition model="out-in" appear>
                <div v-if="show">hello world</div>
                <div v-else>bye world</div>
            </transition>
            <button @click="chage">切换</button>
        `,
  });
</script>

但是这样做了以后,在页面刷新初次加载bye world的时候,没有进场动画,这个时候可以在transition标签上添加appear属性即可。

  1. 第三点中指定的是多个单元素之间的切换,但是实际使用过程中,也可以实现多个组件之间的切换,只需将单元素换成组件即可。示例代码如下:
<script>
  const ComponentA = {
      template:`<div>hello world</div>`
  };
  const ComponentB = {
      template:`<div>bye world</div>`
  };
  const app = Vue.createApp({
      data(){
          return {show: false}
      },
      methods: {
          change(){
              this.show = !this.show;
          }
      },
      components :{
          "component-a":ComponentA,
          "component-b":ComponentB
      }
      // 两个组件之间的切换,同时这里也可以用动态组件的方式,将transition内的内容改写成如下
      // <component :is="component"/>,然后对change方法改写,并定义一个data参数component即可
      template:`
          <transition model="out-in" appear>
              <component-a v-if="show" />
              <component-b v-else/>
          </transition>
          <button @click="chage">切换</button>
      `
  });
</script>

transition-group 实现列表动画

在列表渲染的时候,在添加元素和减少元素,都可以有一个过渡和动画效果,具体代码如下:

<style>
  .list-item {
    display: inline-block;
    margin-right: 10px;
  }
  .v-move {
    transition: all 0.5s ease-in;
  }
  .v-enter-from {
    opacity: 0;
    transform: translateY(30px);
  }
  .v-enter-active {
    transition: all 0.5s ease-in;
  }
  .v-enter-to {
    opacity: 1;
    transform: translateY(0px);
  }
  .v-leave-to {
    opacity: 0;
    transform: translateY(-20px);
  }
  .v-leave-active {
    transition: all 0.5s ease-in;
  }
</style>
<script>
  const app = Vue.createApp({
    data() {
      return {
        list: [3, 2, 1],
      };
    },
    methods: {
      addItem() {
        this.list.unshift(this.list[0] + 1);
      },
      removeItem() {
        this.list.shift();
      },
    },
    template: `
            <div>
            <transition-group>
                <span class="list-item" v-for="item in list" :key="item">{{item}}</span>
            </transition-group>
            <button @click="addItem">添加元素</button>
            <button @click="removeItem">删除元素</button>
            </div>
        `,
  });
  app.mount("#root");
</script>

Mixin 基础语法

Mixin 语法在 Vue3 以后,不再推荐使用,可以考虑使用 Composition API 进行替代。

Mixin 分类

  • 局部 Mixin:只在引入的组件里面生效,子组件中是无法使用的,写法如下:
const mymixin = {
  data() {
    return {
      message: "mixin message",
    };
  },
};
const app = Vue.createApp({
  data() {
    return {
      message: "app message",
    };
  },
  mixins: [mymixin],
  template: `<div>{{message}}</div>`,
});
  • 全局 Mixin:对当前 app 下的所有组件都生效,写法如下:
const app = Vue.createApp({
  template: `
    <div>{{message}}</div>
    <child></child>
    `,
});
app.mixin({
  // 全局Mixin
  data() {
    return {
      message: "mixin message",
    };
  },
});
app.component("child", {
  template: `<div>{{message}}</div>`,
});

优先级关系

  • 当在 mixin 和组件中都定义了相同名称的 data 数据,组件中的 data 比 mixin 中的 data 优先级高
  • 当在 mixin 和组件中都定义了相同名称的 methods 数据,组件中的 methods 比 mixin 中的 methods 优先级高
  • 当在 mixin 和组件中都定义了生命周期函数,两者都会生效,且mixin 中的生命周期函数优先执行
  • 自定义属性,组件中的属性优先级高于 Mixin 属性,使用this.属性名的方式调用
const mymixin = {
    data(){
        return {
            message: "mixin message"
        }
    }
}
const app = Vue.createApp({
    data(){
        return {
            message: "app message"
        }
    },
    mixins:[mymixin],
    template:`<div>{{message}}</div>`
})
// 最终渲染效果
<div>app message</div>
const mymixin = {
  message: "mixin message",
};
const app = Vue.createApp({
  message: "app message",
  mixins: [mymixin],
  template: `<div>{{this.$options.message}}</div>`,
});

注意点:这里引入一个$options指令,在 Vue 里面,组件里面的属性都会挂载到options上面

优先级策略修改

仅支持自定义属性的优先级修改,写法如下:

const mymixin = {
  message: "mixin message",
};
const app = Vue.createApp({
  message: "app message",
  mixins: [mymixin],
  template: `<div>{{this.$options.message}}</div>`,
});
// 修改优先级策略,针对属性number
// 当mixin中有message优先展示,否则就展示组件中的message
app.config.optionMergeStrategies.number = (mixinValue, appValue) => {
  return mixinValue || appValue;
};

自定义指令

自定义指令方式

通过directive自定义指令,分为两种类型,分别是局部指令和全局指令,定义如下:

// 定义自定义局部指令
const directives = {
  focus: {
    mounted(el) {
      // 生命周期函数:表示在el标签渲染完成后执行,el表示使用此指令的标签,这里是input标签对象
      el.focus();
    },
    // 不仅支持mounted周期函数,beforeMounted、afterMounted、beforeUpdate、updated、beforeUnmount、unmounted生命周期函数都可以使用
  },
};
const app = Vue.createApp({
  directives: directives, // 引入自定义局部指令,如果名称是相同的,可以直接写:directives即可
  template: `<input v-focus />`,
});
// 在app上挂载自定义全局指令
app.directive("focus", {
  mounted(el) {
    // 生命周期函数:表示在el标签渲染完成后执行,el表示使用此指令的标签,这里是input标签对象
    el.focus();
  },
});

注意点:自定义指令里面可以实现所有的生命周期函数,针对当前使用自定义指令的 dom 对象

自定义指令传参

<style>
  .demo {
    position: absolute;
  }
</style>
<script>
  const app = Vue.createApp({
    data() {
      return {
        distance: "100px",
      };
    },
    // 这里在v-pos后增加一个left和赋值为distance,会被封装到binding参数内
    // left对应的参数名是arg,distance对应的参数名是value
    template: `<input class="demo" v-pos:left="distance" />`,
  });
  app.directive("pos", {
    // 初始加载的时候渲染
    mounted(el, binding) {
      el.style[binding.arg] = binding.value; // 通过binding获取arg和value值
    },
    // 如果在修改的时候,也需要重新渲染,则需要使用updated生命周期函数
    updated(el, binding) {
      el.style[binding.arg] = binding.value;
    },
  });
  const vm = app.mount("#root");
</script>

注意:如果这个时候在 directive 中,只有 mounted 和 updated 生命周期函数,则可以实现一下的简写方式。

app.directive("pos", (el, binding) => {
  el.style[binding.arg] = binding.value;
});

传送门 teleport

当在template内定义一个div的时候,需要将这个div放到指定的标签内,这个时候就可以使用teleport将这个div传送到指定位置。代码如下:

// 正常如果没有teleport,其内的div会按照正常顺序渲染在root标签下id为first的div标签内
// 但是如果现在需要将这个div放到body标签下,就可以使用teleport标签的to属性,将其传送到body下
//
const app = Vue.createApp({
  template: `<div id="first">
    <teleport to="body"> // 可以指定标签名,也可指定其他标签的id,如:to="#target"
    	<div>需要将此div放到body标签下</div>
    </teleport>
    </div>`,
});
const vm = app.mount("#root");

拓转 CSS 知识点:

  1. 将一个盒子水平居中放到整个页面的中央位置。
.center {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 200px;
  height: 200px;
  background: green;
}
  1. 制作整个页面背景为透明灰色。
.opacity-back {
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  bottom: 0;
  background: #000;
  opacity: 0.3;
}

render 函数

render函数在某种意义上来说就是对应template,在 Vue 底层编译的时候,是将template内容编译成render函数,形成 js 描述的 dom 对象(虚拟 dom),通过一系列的操作对虚拟 dom 设置属性、动态值等,然后转为真实 dom 渲染到页面。

render函数的应用:

// 需要根据传递参数level来确定使用h1、h2、h3、h4标签
const app = Vue.createApp({
  data() {
    return {
      level: 1,
    };
  },
  template: `<demo :level="level">slot message</demo>`,
});
app.component("demo", {
  props: ["level"],
  render() {
    const { h } = Vue; // 从Vue中获取虚拟dom对象
    // 第一个参数是标签的名称
    // 第二个参数是标签的属性,如:id="demo" name="demoName"
    // 第三个参数对应的是标签内的内容,相当于innerHTML
    return h("h" + this.level, {}, this.$slots.default()); // default表示所有的slot插槽内容
  },
});
app.mount("#root");

核心点:在上面的代码中,第三个参数可以实现多层嵌套,示例代码如下:

// 仅写第三个参数的嵌套示例
[
  this.$slots.default(),
  h("div", {}, ["innerdiv", h("span"), {}, "content"]), //这里还可以继续嵌套下去
];

核心点:从这种嵌套关系可以看出来整个页面的布局关系,不是吗?

自定义插件

  • 首先自定义插件的规则
  • 然后通过自定义插件,使用规则
  • 通过app.use方法挂载应用插件
const app = Vue.createApp({
  data() {
    return {
      age: 30,
      name: "joker",
    };
  },
  rules: {
    // 自定义校验规则
    age: {
      validate: (age) => {
        return age < 28;
      },
      message: "too young too simple",
    },
    name: {
      validate: (name) => {
        return name.length < 4;
      },
      // validate: name => name.length < 4; validate的简写方式
      message: "name too short",
    },
  },
  template: `<div>name:{{name}}-age:{{age}}</div>`,
});

const validatorPlugin = {
  // 定义插件
  install(app, options) {
    app.mixin({
      created() {
        for (let element in this.$options.rules) {
          const item = this.$options.rules[element];
          this.$watch(element, (value) => {
            // 监听器,监听element的变化,这里的element是age和name
            const result = item.validate(value);
            if (result) console.log(item.message);
          });
        }
      },
    });
  },
};
// 定义插件的另一种简写方式(推荐写法)
// const validatorPlugin1 = (app, options) => {
//     app.mixin({
//         created() {
//             for (let element in this.$options.rules) {
//                 const item = this.$options.rules[element];
//                 this.$watch(element, (value) => { // 监听器
//                     const result = item.validate(value);
//                     if (result) console.log(item.message);
//                 })
//             }
//         }
//     })
// }
app.use(validatorPlugin); //使用插件
const vm = app.mount("#root");

注意点:在使用 app.use 方法的时候,是可以传入参数的,传入方式如下:

app.use(validatorPlugin, { name: "joker", age: 20 });

Composition API 语法

Composition API 是 Vue 的新特性,目前在 Vue2.7 版本及以上是可以使用的,低的版本不兼容。

setup

在 Composition API 语法中,将内容都定义在setup内,setup是在 Vue 实例被完全初始化之前调用,因此在 setup 内不能使用this关键字,因为这个时候this内容还未形成。setup 结构如下:

const app = Vue.createApp({
  // created 实例被完全初始化之前,this还没有生成,因此在setup中不能使用this
  setup(props, context) {
    return {
      name: "joker",
    };
  },
  template: `<div>{{name}}</div>`,
});

响应式引用-ref

setup里面创建变量后,在template内使用,当此变量发生变化,template内使用的数据并不会发生改变,这是因为在setup内定义的普通变量不满足响应式引用要求,Composition API 给出了一个解决方案使用ref,但是ref 只能对基础类型数据做响应引用,如下代码:

const app = Vue.createApp({
  setup(props, context) {
    let name = "dell";
    setTimeout(() => {
      name = "joker";
    }, 2000);
    return { name };
  },
  template: `<div>{{name}}</div>`,
});
// 上述的代码在2秒后修改name为joker,但是渲染出来div内的内容依然是dell。不会随之变化。要想变化,修改如下:
const app = Vue.createApp({
  setup(props, context) {
    const { ref } = Vue; // 从Vue中引入ref函数
    let name = ref("dell");
    setTimeout(() => {
      name.value = "joker";
    }, 2000);
    return { name };
  },
  template: `<div>{{name}}</div>`,
});

总结点:在使用ref关键字后,在 Vue 底层就相当于proxy({value='dell'}),对name进行了数据封装,此时就可以实现响应式引用,在setTimeout内修改name值的时候,使用name.value也是这层封装的原因,但是在template中使用的时候不需要用name.value,直接使用name即可,因为 Vue 底层会自动将其转为name.value

响应式引用-reactive

ref只能对基础类型数据做响应式引用,如果需要对对象、数组这些数据做响应式引用,就需要使用reactive。如下代码:

const app = Vue.createApp({
    setup(props,context){
        const { reactive } = Vue; // 从Vue中引入reactive函数
        let user = reactive({name:"joker",age:20});
        setTimeout(()=>{
            user.name = 'dell';
        },2000);
        return { user };
    }
    template:`<div>{{user.name}}-{{user.age}}</div>`
})

总结点一:这里基本和ref相同,Vue 底层相当于proxy({name:'joker',age:20}),实现响应式引用

总结点二:根据官方给出的结论,在实际应用中ref带来的性能提升是远高于reactive的,因此在开发过程中,尽量使用ref

但是如果现在需要设置只读权限,此时引入了readonly关键字。代码如下:

const app = Vue.createApp({
    setup(props,context){
        const { reactive,readonly } = Vue;
        let user = reactive({name:"joker",age:20});
        const copyUser = readonly(user);
        setTimeout(()=>{
            user.name = 'dell';
            copyUser.name = 'dell'; // 此时就会报错,因为copyUser设置了只读
        },2000);
        return { user };
    }
    template:`<div>{{user.name}}-{{user.age}}</div>`
})

响应式引用-toRefs

如下代码:

const app = Vue.createApp({
    setup(props,context){
        const { reactive } = Vue;
        const user = reactive({name:"joker",age:20});
        setTimeout(()=>{
            user.name = 'dell';
        },2000);
        const { name } = user; //如果这里只要将name返回,则可以从user中取出name,解构思维
        return { name };
    }
    template:`<div>{{name}}</div>`
})

问题点:template可以正常渲染,但是在 name 改动的时候,不会重新渲染,也就是响应式引用失效,为什么呢?因为reactive代理的响应式引用是针对其内的整个对象,对对象内的属性没有单独做响应式引用。此时可以使用toRefs来解决。

const app = Vue.createApp({
    setup(props,context){
        const { reactive,toRefs } = Vue;
        const user = reactive({name:"joker",age:20});
        setTimeout(()=>{
            user.name = 'dell';
        },2000);
        const { name } = toRefs(user);
        return { name };
    }
    template:`<div>{{name}}</div>`
})

总结点:上面说过使用reactive就相当于proxy({name:'joker',age:20}),当使用toRefs后,就相当于{name:proxy({value:'joker'}),age:proxy({value:20})},将代理细化到内部的每一个属性,实现每一个属性都有单独的响应式引用。当细化到每个属性的时候,变相的看就是将对象内的基本属性转换成响应式引用。

响应式引用-toRef(一般不建议使用)

当解构的时候,如果解构对象没有对应属性,使用toRefs会出现报错,因为解构出来的这个属性不会被赋予默认值,这个使用可以使用toRef实现增加默认值。代码如下:

const app = Vue.createApp({
    setup(props,context){
        const { reactive,toRefs } = Vue;
        const user = reactive({name:"joker",age:20});
        const { phone } = toRefs(user);
        setTimeout(()=>{
            phone.value = 'dell'; // 这里会出现报错,因为phone在user里面不存在
        },2000);
        return { phone };
    }
    template:`<div>{{phone}}</div>`
})
// 修改后如下
const { reactive,toRef } = Vue;
const user = reactive({name:"joker",age:20});
// 使用toRef,就会给phone一个默认值,在对phone进行操作的时候不会报错
// 相对于toRefs来说,toRef只能解构一个属性
const  phone = toRef(user,'phone');

setup 之 context

在定义setup的时候,能够定义两个参数分别是propscontext,针对context内包含哪些数据呢。见代码如下:

const app = Vue.createApp({
    methods :{
        fatherChange(){
            alert("fatherChange")
        }
    },
    template:`<child @click="fatherChange"></child>`
})
app.component('child',{
    template:`<div @click="change">change</div>`
    setup(props,context){
        const { attrs,slots,emit } = context;
        function change(){
            emit("fatherChange");
        }
        return { change };
    }
})
  • attrs:接收的是父组件传入的 Non-Props 属性,正常子组件接收传入的参数使用props接收,如果无props,父组件传入的数据就会被称为 Non-Props 属性,这个属性就会被attrs接收;
  • slots:接收所有的插槽集合
  • emit:触发父组件的事件,之前都是使用this.$emit来触发,在setup内,直接使用此emit即可触发(另外在setup内也是不能直接使用this关键字的,因为setup优先于组件的created生命周期函数前就创建,此时this还没有形成)

Composition API 的封装思路

直接将所有的逻辑写在setup内会导致代码很拥挤,因此可以考虑采用封装的思想(类似 Java 的封装),将封装好的逻辑引入到setup内。示例代码如下:

<body>
  <div id="root">
    <div>
      <input type="text" @input="inputChange" />
      <button @click="itemChange">提交</button>
    </div>
    <div>
      <ul>
        <li v-for="(item,index) in list">{{item}}</li>
      </ul>
    </div>
  </div>
</body>
<script>
  // 封装list处理逻辑
  const listRelativeEffect = () => {
    const { reactive } = Vue;
    const list = reactive([]);
    const addItem = (item) => {
      list.push(item);
    };
    return {
      list,
      addItem,
    };
  };
  // 封装input标签处理逻辑
  const inputRelativeEffect = () => {
    const { ref } = Vue;
    const inputValue = ref("");
    const inputChange = (event) => {
      inputValue.value = event.target.value;
    };
    return {
      inputValue,
      inputChange,
    };
  };

  const app = Vue.createApp({
    setup() {
      // 将list处理逻辑引入
      const { list, addItem } = listRelativeEffect();
      // 将input标签处理逻辑引入
      const { inputValue, inputChange } = inputRelativeEffect();

      // 在点击提交的时候,将input标签内的值添加到list中
      const itemChange = () => {
        addItem(inputValue.value);
      };
      return {
        list,
        inputValue,
        inputChange,
        addItem,
        itemChange,
      };
    },
  });
  const vm = app.mount("#root");
</script>

优化点:在点击提交的时候,需要将input标签的值添加到list集合中,这里定义了一个方法作为处理,但是这里还可以采用下面的缩减方式

<!--将click事件内容写为箭头函数-->
<button @click="()=>addItem(inputValue)">提交</button>
<!--将原来itemChange相关的内容删除即可-->

Composition API 中计算属性 computed 应用

<body>
  <div id="root">
    <div @click="add">{{count}}---{{computedValue}}</div>
  </div>
</body>
<script>
  const app = Vue.createApp({
    setup() {
      const {
        ref,
        computed, //在这里引入computed
      } = Vue;
      const count = ref(0);
      const add = () => {
        count.value += 1;
      };
      // 书写方式一:在没有修改computedValue需求的时候
      // const computedValue = computed(()=>{
      //     return count.value + 10;
      // })
      // 书写方式二:在需要修改computedValue时,需要写成如下格式
      let computedValue = computed({
        get: () => {
          return count.value + 10;
        },
        // 接收其他业务逻辑代码给computedValue赋的值
        set: (param) => {
          count.value = param - 8;
        },
      });
      setTimeout(() => {
        computedValue.value = 10; // computedValue赋值
      }, 3000);
      return {
        count,
        add,
        computedValue,
      };
    },
  });
  const vm = app.mount("#root");
</script>

Composition API 内的 watch

watch具有的特性:

  • 监听的参数必须是方法、ref属性、reactive对象、数组
  • 惰性特性,在页面刚加载的时候watch是不会监听和执行其内业务代码的,只有在监听的数据发生变化才会触发
  • 可以同时监听多个数据,用数组方式书写
<body>
  <div id="root">
    <input type="text" v-model="name" />
    <input type="text" v-model="englishName" />
  </div>
</body>
<script>
  const app = Vue.createApp({
    setup() {
      const {
        reactive,
        watch, // 引入watch
        toRefs,
      } = Vue;
      const user = reactive({
        name: "张三",
        englishName: "joker",
      });
      // watch(user.name,(current,prev) => {})
      // 当我们直接用上面的方式,watch监听user.name属性的时候,是会报如下的错误信息
      // 错误信息:watch source can only be a getter/effect function, a ref, a reactive object, or an array of these types
      // 提示监听的数据只能是function、ref属性、reactive对象、数组,因此这里user.name不能被监听
      // 需要修改成watch(() => user.name,(current,prev) => {})
      // 监听多个写法如下:
      watch(
        [() => user.name, () => user.englishName],
        ([curName, curEnglishName], [prevName, prevEnglishName]) => {
          console.log(curName, prevName);
          console.log(curEnglishName, prevEnglishName);
        }
      );
      const { name, englishName } = toRefs(user);
      return {
        name,
        englishName,
      };
    },
  });
  const vm = app.mount("#root");
</script>

知识点:watch是有惰性的,但是可以通过配置修改其为即时性的,修改如下:

watch(
  [() => user.name, () => user.englishName],
  ([curName, curEnglishName], [prevName, prevEnglishName]) => {
    console.log(curName, prevName);
    console.log(curEnglishName, prevEnglishName);
  },
  {
    immediate: true, // 设置其为即时性,在页面加载的时候,就会执行监听的业务逻辑
  }
);

Composition API 内的 watchEffect

watchEffect也是一种监听器,其监听的是回调函数中的所有可监听的数据(也就是watchEffect内包含响应式变量发生变化,就会触发监听操作),且是即时性的,一般在表单提交的时候,监控当前表单属性的变化,效果很好,因为这个时候不需要提供历史值,用不到watch,监听的代码也会很简洁。代码如下:

<body>
  <div id="root">
    <input type="text" v-model="name" />
    <input type="text" v-model="englishName" />
  </div>
</body>
<script>
  const app = Vue.createApp({
    setup() {
      const {
        reactive,
        watchEffect, //引入watchEffect
        toRefs,
      } = Vue;
      const user = reactive({
        name: "张三",
        englishName: "joker",
      });
      watchEffect(() => {
        console.log(user.name); // 可监听user.name的变动
      });
      const { name, englishName } = toRefs(user);
      return {
        name,
        englishName,
      };
    },
  });
  const vm = app.mount("#root");
</script>

注意点:watchEffect对数据进行监听,只能获取最新的数据,不能获取原数据,这个是和watch的一个重要的区别。

如果需要监听器在一定时间后自动结束监听,可用如下方式实现:(此方式对watchwatchEffect都是可以适用的)

const demoWatch = watchEffect(() => {
  console.log(user.name);
});
// 这个方法可以写在监听器的回调方法内部和外部都可以
setTimeout(() => {
  demoWatch();
}, 5000);

composition 内的生命周期函数

在所有的生命周期方法前加上on,从 Vue 中引入即可正常使用。代码如下:

const app = Vue.createApp({
  setup() {
    const { onMounted } = Vue;
    onMounted(() => {
      console.log("onMounted");
    });
  },
});

注意点:

  • 针对非beforeCreatedcreated之外的生命周期函数,在前面加上on即可
  • 在 Composition API 内新增了两个生命周期函数,分别是onRenderTrackedonRenderTriggeredonRenderTracked是在页面渲染的时候,收集响应式依赖触发,初次渲染或者后期响应式依赖数据变化引发页面重新渲染都会触发;onRenderTriggered只会在响应式依赖数据变化引发页面重新渲染时触发。

Composition API 内的 provide 和 inject

在父组件给更深的子组件传递数据的时候,可以使用到provideinject,在 Composition API 中写法如下:

const app = Vue.createApp({
    setup(){
        const { provide,ref } = Vue;
        const name = ref("hello");
        provice('name',name);
    },
    template:`<child></child>`
})
app.component('child',{
    setup(){
        const { inject } = Vue;
        const name = inject('name',"defaultValue"); //如果没有接收到name,则给予默认值defaultValue
        return {name}
    }
    template:`<div>{{name}}</div>`
})

如果子组件需要修改name的值,根据 Vue 的单向数据流的规则,子组件不要直接修改值,而是告诉父组件,由父组件自行修改,同时也为了避免子组件不小心修改,将传入的值做readonly设置,代码如下:

<body>
  <div id="root">
    <child></child>
  </div>
</body>
<script>
  const app = Vue.createApp({
    setup() {
      const { provide, ref, readonly } = Vue;
      const name = ref("dell");
      // 设置传入的name为readonly,不影响上面定义的name修改,防止子孙组件误修改
      provide("name", readonly(name));
      const handleChange = (value) => {
        //value接收传入的值
        name.value = value;
      };
      // 将修改name值的方法提供给子孙组件
      provide("handleChange", handleChange);
    },
  });
  app.component("child", {
    setup() {
      const { inject } = Vue;
      //如果没有接收到name,则给予默认值defaultValue
      const name = inject("name", "defaultValue");
      // 获取父组件的提供的方法
      const handleChange = inject("handleChange");
      const change = () => {
        handleChange("joker"); // 调用父组件给定的方法
      };
      return {
        name,
        change,
      };
    },
    template: `<div @click="change">{{name}}</div>`,
  });
  const vm = app.mount("#root");
</script>

Composition API 通过 ref 获取 dom

之前在非 Composition API 语法里面通过 ref 获取 dom 对象是通过this.$refs,但是在 Composition API 语法中存在变化,具体代码如下:

<body>
  <div id="root">
    <!--这里的ref值保持和js中return的domRef相同即可-->
    <div ref="domRef">this is dom ref</div>
  </div>
</body>
<script>
  const app = Vue.createApp({
    setup() {
      const { ref, onMounted } = Vue;
      // 固定定义方法
      const domRef = ref(null);
      onMounted(() => {
        // 通过domRef.value获取当前dom对象
        console.log(domRef.value);
      });
      return {
        domRef,
      }; // 将domRef返回
    },
  });
  const vm = app.mount("#root");
</script>

VueCLI 脚手架安装和使用

VueCLI 脚手架安装

  • 安装 node,从 node 官网上下载 node 的最新版本 LTS,傻瓜式安装即可
  • 一般 node 安装结束 npm 也会同步安装成功
  • 更换源,因为默认的源是国外的,在使用的时候会很慢,而且容易出错,这里需要将源换成国内的
    • 首先:npm install nrm -g
    • 然后:nrm ls,查看有哪些国内的源可以使用
    • 再然后:nrm use taobao,选择你要使用的源
  • 如果之前使用过脚手架,需要先卸载
    • npm uninstall vue-cli -gyarn global remove vue-cli
  • 安装最新的脚手架工具,执行命令npm install -g @vue/cli,如果需要指定脚手架版本,可以在后面加上版本号@vue/cli@4.4.4

脚手架安装完成!

使用脚手架创建 Vue 工程

  • 执行vue create [appName]命令
  • 第一步选择:Manually select features
  • 第二步选择:选择 Babel 和 Linter/Formatter(上下键移动,空格选中,回车确认)
  • 第三步选择:3.X
  • 第四步选择:ESlint with error prevention only
  • 第五步选择:Lint on save
  • 第六步选择:In dedicated config files
  • 最后异步选择 N,回车后等子弹飞一会

完成 vue 工程的创建。

注意点:在第二步中,如果需要使用 vue-router、vuex,需要将其选中

vue-router

在实际项目中,会有很多的页面,当点击某个页面的时候,需要跳转到对应 vue 组件,此时就需要使用vue-router进行配置。使用vue-router生成项目后,会有对应router目录,对应的index.js文件,内容如下:

import { createRouter, createWebHashHistory } from "vue-router";
// 在这里表示同步加载,在访问首页的时候就会自动加载,这种可能导致打开首页有一定的卡顿,因为加载的内容太多
import HomeView from "../views/HomeView.vue";
import LoginView from "../views/LoginView.vue";

const routes = [
  {
    path: "/",
    name: "home",
    component: HomeView,
  },
  {
    path: "/about",
    name: "about",
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    // 表示异步加载路由,在没有打开此页面之前,此页面的内容不会提前加载,而是等待需要展示的时候才会加载(懒加载)
    // 具体选择异步加载路由还是同步加载路由,根据实际项目需求而定
    component: () => import("../views/AboutView.vue"),
  },
  {
    path: "/login",
    name: "login",
    component: LoginView,
  },
];

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

export default router;

需要在main.js文件中对vue-router插件进行使用:

import { createApp } from "vue";
import App from "./App.vue";
import router from "./router"; // 导入router相关的配置
import store from "./store";

// 使用vuex的store
// 使用vue-router的router
createApp(App).use(store).use(router).mount("#app");

在 App.vue 中配置路由标签和路由对应组件内容的展示

<template>
  <nav>
    <!-- 跳转路由的标签 -->
    <router-link to="/">Home</router-link> |
    <router-link to="/about">About</router-link> |
    <router-link to="/login">Login</router-link>
  </nav>
  <!-- 负责展示当前路由对应的组件内容 -->
  <router-view />
</template>

VueX

在 vue 里面,有很多数据需要跨页面、跨组件之间使用,如果使用父子组件之间的传递,会导致项目数据的维护困难,在 vue 中引入了 VueX 插件,让共用数据多页面使用更为方便。

在加入 VueX 后,会在项目的 src 目录下生成一个store目录,内容如下:

import { createStore } from "vuex";

// VueX 数据管理框架,可以跨页面、组件
// VueX 创建了一个全局唯一的仓库,用来存放全局的数据
export default createStore({
  state: {
    name: "joker", // 在这里定义一个属性name
  },
  getters: {},
  mutations: {},
  actions: {},
  modules: {},
});

在其他页面或者组件中使用此属性的方法:

computed: {
  myName() {
    return this.$store.state.name; // 引入store中定义的name属性
  },
}

上面写的是获取 VueX 得属性的方法,采用的是原始的方式,如果这里采用 composition 方式获取,写法如下:

export default {
  setup() {
    const store = useStore();
    // const myName = store.state.name;
    // 通过解构的方式获取name值
    const { name } = toRefs(store.state);
    return { name };
  },
};

如果需要修改这个 name 值的话,就需要进行一下的步骤:(最全步骤)

  • 首先调用dispatch方法,触发一个派发action,对应的方法是定义在actions内;
  • actions内的方法执行,使用commit调用mutations内的方法;
  • mutations内执行的方法中修改对应的值。

注意点:

  1. 根据约定mutations内一般是定义同步修改方法,不是异步修改(虽然也可写异步修改逻辑,但是不建议);
  2. 根据约定actions内一般是定义异步修改方法(虽然也可写同步修改逻辑,但是不建议);
  3. 从以上两点可以得出结论,如果需要同步修改的时候,直接调用commit方法,如果需要异步修改的时候,直接调用dispatch方法;
  4. actions中的方法,接收到的参数第一个是store,在 mutations 中的方法,接收到的第一个参数是state

示例代码:

// 在组件中使用并修改name
import { useStore } from "vuex";
import { toRefs } from "vue";
export default {
  setup() {
    const store = useStore();
    // const myName = store.state.name;
    // 通过解构的方式获取name值
    const { name } = toRefs(store.state);
    const changeName = () => {
      // 方式一:直接通过调用commit方法,会直接定位到store内mutations内的change方法
      // store.commit("change","joker-yang");
      // 方式二:调用dispatch方法,会直接定位到store内actions内的change方法
      store.dispatch("change", "yangbao");
    };
    return { name, changeName };
  },
};
// 在store中定义对应change方法
// commit与mutations做关联
// 根据约定俗成的规定,mutations里面是不可以写异步操作代码
mutations: {
    // 在mutations里面,接收的第一个参数是state,第二个是对应的值
  change(state, value) {
    state.name = value;
  }
},
// 异步代码可写在actions内(异步修改)
// dispatch与actions做关联
actions: {
  // 在actions里面,接收的第一个参数是store,第二个是对应的值
  change(store, value) {
    setTimeout(() => {
      store.commit("change", value);
    }, 2000)
  }
}

文章作者: 程序猿洞晓
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 程序猿洞晓 !
评论
 上一篇
Mac电脑上安装Homebrew工具 Mac电脑上安装Homebrew工具
你有没有遇到在mac中安装各种软件很费劲,比如安装nginx,就会因为依赖包的问题各种折磨你,但是使用Homebrew一键安装即可。随之而来的就是Homebrew安装问题,下面来一起讨论讨论……
2023-03-30
下一篇 
BIO、NIO、AIO 的区别分析 BIO、NIO、AIO 的区别分析
在网络编程里面,涉及IO读写的情况非常的多,常用的就是BIO、NIO、AIO,在不同的场景下采用何种IO既能满足效率又能满足性能,且看下文分析。
2023-03-01
  目录