vue笔记

日志

  • 2023年8月20日

    尚硅谷截止 P145

    pink P196 - P300 跳过品优购、动画

  • 2023年8月21日

    pink P390

  • 2023年8月22日

    P415 京东首页 流式布局

  • 2023年8月23日

    P454

  • 2023年8月24日

    学习grid,跳过苏宁,黑马面面

  • 2023年8月25日

    结束HTML+CSS,跳过阿里百秀、bilibili

  • 2023年8月26日

    阅读SAST skill docs JavaScript部分,至异步

  • 2023年8月27日

    SAST JavaScript完结

    pink JavaScript API day2完结

  • 2023年8月29日

    API day4

  • 2023年8月29日

    API结束,未看注册案例

  • 2023年9月1日

    结束js,开始ajax,结束ajax

  • 2023年9月2日

    小满vue,启动!

  • 2023年9月3日

    跳过P10(响应式原理)

  • 2023年9月4日

    完成P15

  • 2023年9月5日

    完成P20,P20异步组件跳过

  • 2023年9月6日

    完成P30

  • 2023年9月11日

    跳过P33 p35 P37 P38(tsx, v-model)

  • 2023年9月13日

    跳过P41 自定义hooks

  • 2023年9月17日

    开始看张天禹

  • 2023年9月19日

    看完P13

  • 2023年9月20日

    看完P20

  • 2023年9月21日

    看完P30

  • 2023年9月22日

    看完P47

  • 2023年9月23日

    看完P59

  • 2023年10月1日

    看完P72

  • 2023年10月2日

    看完P89

  • 2023年10月3日

    看完P117

Vue

1
wget https://labfile.oss.aliyuncs.com/courses/1262/vue.min.js

工程化

计算、侦听与过滤器、生命周期钩子

  • 计算属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<body>
<div id="app">
<p>我名字正着写:{{name}}</p>
<!-- reverseName 计算属性 可以像绑定普通属性一样在模板中绑定计算属性-->
<p>计算出我名字倒着写:{{reverseName}}</p>
</div>
<script>
var app = new Vue({
el: "#app",
data: {
goodsList: {,
name: "实验楼",
},
computed: {
// reverseName 是一个计算属性
reverseName: function () {
return this.name.split("").reverse().join("");
},
},
// 一个只在内面挂载完毕,才开始执行的函数
mounted(){
axios.get('goodsList.json').then(val => {
this.goodsList = val.data;
})
}
});
</script>
</body>

生命周期函数是在特定时间点执行的函数,其中this的指向为vm组件

当你的计算属性的依赖数据发生改变时,你的相关计算属性也会重新计算

set与get: 给this.计算属性赋值会自动调用计算属性.set

set:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<body>
<div id="app">
<p>firstName:{{firstName}}</p>
<p>lastName:{{lastName}}</p>
<p>全名是:{{fullName}}</p>
<button v-on:click="changeName">改姓</button>
</div>
<script>
var app = new Vue({
el: "#app",
data: {
firstName: "王",
lastName: "花花",
},
methods: {
// changeName 定义一个方法改变 计算属性 fullName 的值
changeName: function () {
// 修改计算属性 fullName 等于李花花
this.fullName = "李花花";
// 上面一句等于触发了 fullName 属性的 setter
},
},
computed: {
fullName: {
// getter
get: function () {
return this.firstName + this.lastName;
},
// setter 直接改变计算属性 fullName 的值就可以触发 setter this.fullName='XX'
set: function (newName) {
var name = newName;
this.firstName = name.slice(0, 1); // 取新值的第一个字符
this.lastName = name.slice(1); // 从新值的第二个字符开始取值
},
},
// 不需要set时,可简写:
fullname :function(){
return this.firstName + this.lastName;
}
// ES6中,如果属性值是函数,则可以省略`:function`, 如:
fullname(){
return this.firstName + this.lastName;
}
},
});
</script>
</body>
  • 侦听属性

观察和响应 Vue 实例上的数据变动,侦听属性

监控msg,当msg改变是,调用对应的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<body>
<div id="app">
<p>{{msg}}</p>
<!-- v-on:click 简写为 @click -->
<button @click="handleClick('hello syl')">改变msg</button>
</div>

<script>
var app = new Vue({
el: "#app",
data: {
msg: "hello",
},
methods: {
// 改变 msg 的值
handleClick: function (val) {
this.msg = val;
},
},
// watch 监听属性
watch: {
// 监听新旧值 监听属性有两个参数,第一个新值,第二个旧值
msg: function (newVal, oldVal) {
alert("新值" + newVal + "----" + "旧值" + oldVal);
},
},
});
</script>
</body>
  • 过滤器

在 Vue 中我们有一个专门处理数据过滤的东西:过滤器。过滤器可以用在两个地方:双花括号插值和 v-bind 表达式

1
2
3
4
<p>{{msg2|getString}}</p>
<p v-bind:class="msg2|getString"></p>
// 等价于:
<p v-bind:class="getstring(msg2)"></p>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<body>
<div id="app">
<!-- toUpperCase getString 为自定义的过滤器-->
<p>小写转换大写:过滤前:{{msg}} 过滤后: {{msg|toUpperCase}}</p>
<p>去除数字:过滤前:{{msg2}} 过滤后: {{msg2|getString}}</p>
</div>
<script>
var app = new Vue({
el: "#app",
data: {
msg: "hello",
msg2: "1s2y3l",
},
// filters 过滤器选项
filters: {
// toUpperCase 定义一个字符串转大写的过滤器
toUpperCase: function (val) {
return val.toUpperCase();
},
// getString 定义一个获取去除数字的过滤器
getString: function (val) {
let newVal = "";
val.split("").map(function (item) {
if (9 >= item && item >= 0) {
return;
} else {
return (newVal += item);
}
});
return newVal;
},
},
});
</script>
</body>

组件

简介

非单文件组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<div id="app">
// 使用
<xuexiao></xuexiao>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script type="text/javascript">
// 创建
// const school = Vue.extend(options) 可简写为 const school = options
const school = Vue.extend({
template: `
<div>
<h2>学生姓名:{{studentName}} </h2>
<h2>学生年龄:{{age}} </h2>
</div>
`,// template会完全替换xuaxiao对象
data() {
return {
studentName: "张三",
age: 18,
};
},
});

const vm = new Vue({
el: "#app",
// (局部)注册
components: {
xuexiao: school,
//xuesheng: student,
},
});
</script>

单文件组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<tempalte>
<div>

</div>
</tempalte>

<script>
export default {
name: '',
data() {
return {
schoolName: '',
address: ''
}
},
methods: {
showName() {
alert(this.showName)
}
},
}
</script>

<style>
div{
color: pink;
}
</style>


// main.js 入口文件
import App from './App'

new Vue({
el:'#root',
template: `<App></App>`,
components: {App},
})
  • 全局组件:Vue.component

    1
    2
    3
    4
    <syl></syl>
    Vue.component("syl", {
    template: "<h1>实验楼全局组件</h1>",
    });
  • 局部组件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    <body>
    <div id="header">
    <syl-header></syl-header>
    </div>
    <div id="mid">
    <syl-mid></syl-mid>
    </div>
    <script>
    // 头部组件
    var childComponent = {
    template: "<h2>我是实验楼局部组件header,只有我们父级才能调用</h2>",
    };
    // 中间部分组件
    var childComponent2 = {
    template: "<h2>我是实验楼局部组件mid,只有我们父级才能调用</h2>",
    };
    // header vm
    var header = new Vue({
    el: "#header",
    // 子组件必须声明后使用,不然不能起效
    components: {
    "syl-header": childComponent,
    },
    });
    var mid = new Vue({
    el: "#mid",
    // 子组件必须声明后使用,不然不能起效
    components: {
    "syl-mid": childComponent2,
    },
    });
    </script>
    </body>

组件的优点就在于能够复用,一次代码编写,整个项目受用。

注意: 复用组件内的 data 必须是一个函数,如果是一个对象(引用类型),组件与组件间会相互影响,组件数据不能独立管理

1
2
3
4
5
6
7
8
9
10
11
12
Vue.component("button-counter", {
// data 必须是一个函数不然会影响其他组件
data() {
return {
counter: 0,
};
},
template: '<button @click="counter++">{{counter}}</button>',
});
var app = new Vue({
el: "#app",
});
通信

父子:props

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<body>
<div id="app">
<title-component post-title="syl1"></title-component>
<title-component post-title="syl2"></title-component>
<title-component post-title="syl3"></title-component>
</div>
<script>
// 注册一个 title 组件,通过传入不同的 title 值,渲染不同的东西
// 组件上 传递的 props 属性名为 kebab-case(短横线分隔命名)的要转换为驼峰命名
Vue.component("title-component", {
props: ["postTitle"], // post-title 转换为驼峰命名
template: "<p>{{postTitle}}</p>",
});
var app = new Vue({
el: "#app",
});
</script>
</body>

子父通信this.$emit('自定义事件名',参数)

子组件向父组件数据传递套路:

第一步:子组件绑定事件。

第二步:子组件绑定事件触发,使用 $emit 创建自定义事件并传入需要传值给父组件的数据。

第三步:在子组件标签上 用 v-on 绑定自定义事件,在父组件中声明自定义事件处理的方法。

第四步:父组件方法,接受自定义事件传的参数,就完成了整个由下到上的数据流。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<body>
<div id="app">
<child-component v-on:send-msg="getMsg"></child-component>
</div>
<script>
// 定义一个子组件,template 绑定 click 事件
// 当 click 事件触发就使用 emit 自定义一个事件 send-msg,传入参数 “我是子组件请求与你通信”
// $emit('send-msg','我是子组件请求与你通信')
// 子组件标签上绑定自定义事件 send-msg,并绑定上父级的方法 getMsg,即可完成了子父组件通信
// <child-component v-on:send-msg="getMsg"></child-component>
Vue.component("child-component", {
template: `<button v-on:click="$emit('send-msg','我是子组件请求与你通信')">Click me</button>`,
});
var app = new Vue({
el: "#app",
methods: {
getMsg: function (msg) {
// 弹出子组件传递的信息
alert(msg);
},
},
});
</script>
</body>
props类型检测

通常你希望每个 prop 都有指定的值类型。这时,你可以以对象形式列出 prop,这些属性的名称和值分别是 prop 各自的名称和类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<body>
<div id="app">
<child-component
id="1"
title="hello syl"
content="you are welcom"
></child-component>
</div>
<script>
// 注册一个子组件
Vue.component("child-component", {
// props 对象形式,传递属性值 进行类型检测,在脚手架环境中很有用
props: {
id: Number,
title: String,
content: String,
},
// 使用 es6 模板字符串书写格式更优美
template: `<div><p>id:{{id}}</p><p>title:{{title}}</p><p>content:{{content}}</p></div>`,
});
var app = new Vue({
el: "#app",
});
</script>
</body>
动态组件、实例生命周期

本地应用

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

<script type="text/javascript">
var vm = new Vue({
el: '#vue_det',
// 或 vm.$mount('#vue_det')
// 对象式:
data: {
site: "菜鸟教程",
url: "www.runoob.com",
alexa: "10000"
},
//函数式:
data(){
return {
site: "菜鸟教程",
}
}

methods: {
// 数据变化会触发Vue响应系统, 进而触发这个函数重新执行.
details: function() {
return this.site + " - 学的不仅是技术,更是梦想!";
}
}
})
</script>
  • 初始化:向Vue构造函数中传入一个集合
    • el: 选择器,设置Vue实例挂载(管理)的元素

    • data: 插值数据

    • method: 方法

  • vm.sute可以这样用
  • vm.$el, vm.$data 它们都有前缀 $,以便与用户定义的属性区分开来

模板语法

插值

{{ name }}

name 为Vue中data的元素

v-html、v-text

用于替换为html代码

1
2
3
4
5
6
7
8
9
10
<div id="app2">
<div v-html="message"></div>
</div>

let app2 = new Vue({
el: '#app2',
data: {
message: '<h1>菜鸟教程</h1>'
}
})

v-on

  • 绑定事件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<input v-on:click="doIt">
或:
<input @click="doIt(p1, p2)"> //自定义参数

<input @keyup.enter="sayHi"> //事件修饰符

let vm=Vue({
data:{
food: "西蓝花",
}
methods:{
doIt: function(p1, p2){
this.food
}
}
})
  • 动态的事件:

    1
    2
    3
    @[event]='doIt'

    event='click'
  • 默认有冒泡,添加.prevent可以阻止冒泡

    1
    @click.p

键盘事件:

在监听键盘事件时,我们经常需要检查详细的按键。Vue 允许为 v-on 在监听键盘事件时添加按键修饰符

  • keyup.enter

  • click.ctrl同时鼠标左击和按 ctrl 弹出提示

  • .exact 精确按键修饰符,允许你控制由精确的系统修饰符组合触发的事件。

    • click.ctrl即使 Alt 或 Shift 被一同按下时也会触发
    • click.ctrl.exact有且只有 ctrl 键 + 鼠标左键 被按下的时候才触发
  • 这些修饰符会限制处理函数仅响应特定的鼠标按钮。

    • .left

    • .right

    • .middle

v-show、v-if

根据表达式的真假,切换元素的显示和隐藏

1
2
3
4
5
6
7
v-show="isShow"
v-show="age>=18"

data:{
isShow: false,
age: 16
}

v-if: 同上,但操作dom元素(转换为注释节点),性能差

v-bind

单向数据绑定:v-bind

双向数据绑定:v-model

==属性变量化==

设置元素的属性(src, title, class)

可以多个

1
2
3
4
5
v-bind:src="imgSrc"

data:{
imgSrc=" "
}
1
2
3
4
5
6
v-bind: class="isActive ? 'active' : '' "
v-bind: class="{'active': isActive, 'red-bg': isRed}"
// active为构建好的类
data:{
isActive: true;
}
1
2
<div class='c' :class="['a', 'b']">
</div>
  • bind可以是数组

  • 可以同时有动态否认和静态的class

v-bind可以省略,如:

:src="imgSrc"

元素style绑定:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<body>
<div id="app">
<p v-bind:style="{fontSize:size,backgroundColor:bgColor}">你好,实验楼</p>
</div>
<script>
var app = new Vue({
el: "#app",
data: {
size: "26px",
bgColor: "pink",
},
});
</script>
</body>

v-for

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<ul id="app">
<li v-for="(item, index) in arr">
{{index}} {{ item }}
</li>
</ul>

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
var app=new Vue({
el:"#app",
data:{
arr:[1,2,3,4,5],
objArr:[
{name: 'jack'},
{name: 'rose'},
]
}
})
</script>
  • v-for一般和:key=''搭配使用

还是由于 JavaScript 的限制,Vue 不能检测对象属性的添加或删除,直接进行 app.userInfo.height='180cm' 这样操作是不会构成响应式,不会触发视图更新。必须使用 Vue.set(object, key, value) 方法向嵌套对象添加响应式属性

eg.

1
Vue.set(app.objArr, 'name', 'andy');

v-once

内容只渲染一次,不会改变

v-memo

1
2
3
4
5
6
7
8
9
10
11
12
<script setup lang="ts">
const arr:number[] = [1,2,3,4,5,6,7,8,9,10];
</script>

<template>
<div v-for="item in arr" v-mome="[item == 2]">
{{ item }}
</div>
</template>

<style scoped>
</style>
  • item!=2时,重新渲染
v-cloak

当vue初始化完成时,这个属性会直接消失

用例:

1
2
3
[v-cloak]{
display: none;
}

官方文档

组件基础

监听事件

子组件可以用$emit来抛出事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
<div class="blog-post">
<h4>{{ title }}</h4>
<span>{{ content }}</span>
<button @click="$emit('enlarge-text')">Rnlarge text</button>
</div>
</template>

<script setup lang='ts'>
defineProps(['title', 'content'])
defineEmits(['enlarge-text'])
</script>

<style scoped>

</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<template>
<div :style="{fontSize: postFontSize + 'em'}">
<BlogPost
v-for="post in posts"
:key="post.id"
:title="post.title"
@enlarge-text="postFontSize+=0.1">
</BlogPost>
</div>
</template>

<script setup lang='ts'>
import { ref } from 'vue';
import BlogPost from './components/BlogPost.vue';
const posts = ref([
{ id: 1, title: 'My journey with Vue' },
{ id: 2, title: 'Blogging with Vue' },
{ id: 3, title: 'Why Vue is so fun' }
])
const postFontSize=ref(1)
</script>

<style scoped>

</style>

描述:

  1. 子组件中,可以以@click="$emit('enlarge-text')"这样的形式,把点击事件绑定到enlarge-text函数上(官方描述:子组件可以通过调用内置的 $emit方法,通过传入事件名称来抛出一个事件。父组件有@enlarge-text监听,会完成这一事件的效果)
  2. 在子组件中,需要使用defineEmits宏来声明需要抛出的事件

深入组件

注册
全局注册
1
2
3
4
5
6
7
8
9
import {createApp} from 'vue'

const app=createApp({})
app.component(
'MyComponent',
{
// 实现
}
)

注册导入的单文件组件

1
2
3
import MyComponent from './App.vue'

app.component('MyComponent', MyComponent)

使用:

1
2
3
4
<!-- 这在当前应用的任意组件中都可用 -->
<ComponentA/>
<ComponentB/>
<ComponentC/>
局部注册
  • 在使用 <script setup> 的单文件组件中,导入的组件可以直接在模板中使用,无需注册

  • 如果没有使用 <script setup>,则需要使用 components 选项来显式注册:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import ComponentA from './ComponentA.js'

    export default {
    components: {
    ComponentA
    },
    setup() {
    // ...
    }
    }

关于VueComponent:

1.school组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的。*

2.我们只需要写,Vue解析时会帮我们创建school组件的实例对象,

即Vue帮我们执行的:new VueComponent(options)。

3.特别注意:每次调用Vue.extend,返回的都是一个全新的VueComponent!!!!

4.关于this指向:

(1).组件配置中:

​ data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【VueComponent实例对象】。

(2).new Vue(options)配置中:

​ data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【Vue实例对象】。

5.VueComponent的实例对象,以后简称vc(也可称之为:组件实例对象)。

Vue的实例对象,以后简称vm。*

image-20230923160822252

1
类.prototype === 对象.__proto__

他们指向同一个东西

尚硅谷

文档:https://v2.cn.vuejs.org/v2/guide/

个人笔记:

核心

挂载

1
2
3
4
5
6
7
8
9
10
11
12
13
<script>
new vue({
el: '#root',
data:{
name: '尚硅谷'
},
methods: {
func(){
// 这里this的指向是vm或者组件实例对象
}
},
})
</script>

或者:

1
2
3
4
5
6
7
8
<script>
let vm = new Vue({
data() {
return { name: "atguigu" };
},
});
vm.$mount("#root");
</script>

可以直接通过vm访问data的属性,这是因为data的所有属性都通过Object.defineProperty方法进行了数据代理,代理到了vm

Object.defineProperty方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<script type="text/javascript" >
let number = 18
let person = {
name:'张三',
sex:'男',
}

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
}

})
<script/>
计算属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

const vm = new Vue({
el:'#root',
data:{
firstName:'张',
lastName:'三',
x:'你好'
},
computed:{
fullName:{
//get有什么作用?当有人读取fullName时,get就会被调用,且返回值就作为fullName的值
//get什么时候调用?1.初次读取fullName时。2.所依赖的数据发生变化时。
get(){
console.log('get被调用了')
// console.log(this) //此处的this是vm
return this.firstName + '-' + this.lastName
},
// //set什么时候调用? 当fullName被修改时。
set(value){
console.log('set',value)
const arr = value.split('-')
this.firstName = arr[0]
this.lastName = arr[1]
}
}
}
})
</script>
  • setter:这个函数会在fullName被修改的时候执行。如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生改变(如修改firstNamelastName)。
  • getterfullName以来的数据发生变化时被调用

计算属性可以只写getter

1
2
3
4
5
computed:{
fullName2() {
return this.firstName + "-" + this.lastName;
},
}
监视属性

首先指定要监视的值,当这个值放生改变时,会自动执行指定的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="app">
<div>今天天气{{ val }}</div>
<button @click="change">change</button>
</div>
<script src="../js/vue.js"></script>
<script>
const vm = new Vue({
el: "#app",
data: {
flag: true,
},
computed: {
val() {
return this.flag ? "炎热" : "凉爽";
},
},
methods: {
change() {
this.flag = !this.flag;
},
},
watch: {
flag: {
immediate: true,
deep: true,
handler(newVal, oldVal) {
console.log("修改了!");
},
},
},
});
</script>
</body>
</html>

简写:

1
2
3
4
5
watch: {
flag(newVal, oldVal) {
console.log("修改了!");
},
},

有的功能,watch可以完成,但computed不一定能完成

  1. 所被Vue管理的函数,最好写成普通函数,这样this的指向才是vm 或 组件实例对象。

  2. 所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数等、Promise的回调函数),最好写成箭头函数,这样this的指向才是vm 或 组件实例对象。

添加一个属性
1
Vue.set(this.student, 'sex', '男')
过滤属性

要用到管道运算符

1
2
3
4
5
6
7
8
9
10
11
12
<html>
<div> {{111111 | timeFormater}} </div>
</html>
<script>
new Vue({
filters: {
timeFormater(time) {
return time / 1000;
},
},
})
</script>
自定义指令

函数式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="app">
大大大:
<span v-big="n"></span>
</div>
</body>
<script src="../js/vue.js"></script>
<script>
new Vue({
el: "#app",
data: {
n: 99,
},
directives: {
big(element, binding) {
element.innerText=binding.value *10
},
},
});
</script>
</html>

big 函数调用时机:

  1. 指令与元素成功绑定时(一上来)
  2. 指令所在的模块被重新解析时

对象式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
directives:{
fbind:{
//指令与元素成功绑定时(一上来)
bind(element,binding){
element.value = binding.value
},
//指令所在元素被插入页面时
inserted(element,binding){
element.focus()
},
//指令所在的模板被重新解析时
update(element,binding){
element.value = binding.value
}
}
}

全局指令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Vue.directive('fbind',{
//指令与元素成功绑定时(一上来)
bind(element,binding){
element.value = binding.value
},
//指令所在元素被插入页面时
inserted(element,binding){
element.focus()
},
//指令所在的模板被重新解析时
update(element,binding){
element.value = binding.value
}
})

组件基础

创建组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="root">
<school></school>
<hr>
<stu></stu>
</div>
</body>
<script src="../js/vue.js"></script>
<script>
const school = Vue.extend({
template:`
<div>
<div>姓名:{{name}}</div>
<div>地址:{{address}}</div>
</div>
`,
data(){
return {
name: '阿巴阿巴',
address: '唐宁街一号',
}
}
})
const stu = Vue.extend({
template:`
<div>
<div>姓名:{{name}}</div>
<div>性别:{{sex}}</div>
</div>
`,
data(){
return {
name: '张三',
sex: '男',
}
}
})
const vm = new Vue({
el:'#root',
// 局部注册
components:{
school,
stu
}
})
</script>
</html>

组件需要复用,故data应写成函数,并把数据包裹在返回值中

全局组件

1
2
3
4
5
6
7
8
9
10
const hello = Vue.extend({
name: '我是hello', // 指定在开发者工具中的名字
template:`<div>{{ hello }}</div>`,
data(){
return {
hello: 'hello'
}
}
})
Vue.component('hello', hello)

const hello = Vue.extend({})可以简写为:const hello = {}

props

父组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<div>
<StudentCom name="张三" sex="男" age="18"></StudentCom>
</div>
</template>

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

export default {
name: 'App',
components: {
StudentCom
}
}
</script>

<style>

</style>

子组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<template>
<div>
<h1>{{ msg }}</h1>
<h2>学生姓名:{{ name }}</h2>
<h2>学生性别:{{ sex }}</h2>
<h2>学生年龄:{{ age }}</h2>
</div>
</template>

<script>
export default {
name: 'StudentCom',
data() {
return {
msg:'我是一个普通的学生'
}
},
props: ['name', 'age', 'sex']
}
</script>

<style>

</style>

上面父组件的传参写法中,传出的只能是字符串,

1
<StudentCom :name="张三" :sex="男" :age="18"></StudentCom>

这样,传出去的是表达式

对于子组件,这些数据是只读

mixin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<template>
<div>
<h1>{{ msg }}</h1>
<h2>学生姓名:{{ name }}</h2>
<h2>学生性别:{{ sex }}</h2>
<h2>学生年龄:{{ age }}</h2>
<button @click="showName">点击展示姓名</button>
</div>
</template>

<script>
import {hunhe} from '../minin.js'
export default {
name: 'StudentCom',
data() {
return {
msg:'我是一个普通的学生'
}
},
mixins:[hunhe],
}
</script>

<style>

</style>
1
2
3
4
5
6
7
8
9
10
11
// mixin.js
export const hunhe = {
methods: {
showName() {
alert(this.msg)
}
},
mounted(){
console.log('泥嚎呀')
}
}

完成属性的复用

全局混合:

1
2
// main.js
Vue.mixin(hunhe)
插件

定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// src/plugins.js
export default {
install(Vue,x,y,z){
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
2
import plugins from './plugins'
Vue.use(plugins, 1, 2, 3)
传参

父:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<template>
<div id="root">
<div class="todo-container">
<div class="todo-wrap">
<MyHeader :addTodo="addTodo" />
<MyList :todos="todos" :deleteTodo="deleteTodo" :checkTodo="checkTodo" />
<MyFooter :todos="todos" :changeDone="changeDone" :clearAll="clearAll"/>
</div>
</div>
</div>
</template>

<script>
import MyHeader from './components/MyHeader.vue';
import MyList from "./components/MyList.vue";
import MyFooter from './components/MyFooter.vue';

export default {
name: 'App',
data() {
return {
todos: [
{ id: '001', title: '抽烟', done: true },
{ id: '002', title: '喝酒', done: false },
{ id: '003', title: '烫头', done: true },
],
}
},
methods: {
addTodo(todo) {
this.todos.unshift(todo)
},
checkTodo(id) {
this.todos.forEach((obj) => {
if (obj.id === id) obj.done = !obj.done
})
},
deleteTodo(id) {
// this.todos.splice(this.todos.forEach(todo => todo.id === id), 1)
this.todos = this.todos.filter(obj => obj.id !== id)
},
changeDone(done) {
this.todos.forEach(obj => { obj.done = done })
},
clearAll() {
this.todos = this.todos.filter(obj => !obj.done)
}
},
components: {
MyHeader,
MyList,
MyFooter,
}
}
</script>

子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<template>
<div class="todo-footer" v-show="numTodo">
<label>
<input type="checkbox" v-model="isAll"/>
</label>
<span>
<span>已完成{{ doneTodo }} </span> / 全部{{ numTodo }}
</span>
<button class="btn btn-danger" @click="claerAllDone">清除已完成任务</button>
</div>
</template>

<script>
export default {
name: 'MyFooter',
props: ['todos', 'changeDone', 'clearAll'],
methods: {
claerAllDone() {
this.clearAll()
}
},
computed: {
isAll: {
get() {
return this.doneTodo === this.numTodo && this.numTodo
},
set(value) {
this.changeDone(value)
}
},
numTodo() {
return this.todos.length
},
doneTodo() {
// return this.todos.filter(obj=>obj.done).length
// console.log(this.todos.reduce((pre, now) => { pre + (now.done ? 1 : 0) }, 1));
return this.todos.reduce((pre, now) => pre + (now.done ? 1 : 0), 0)
},
}
}
</script>
$emit传参

自定义事件

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<template>
<aSchool @atguigu="demo"/>
</template>

<script>
import aSchool from './components/aSchool.vue'
export default {
name: 'App',
data(){
return{
msg:'泥嚎',
}
},
methods: {
demo(name) {
console.log('传过来了: ', name);
}
},
components: {
aSchool,
}
}
</script>

<style>

</style>

子:aSchool.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<template>
<div>
<div>学校名称{{ name }}</div>
<div>学校地址{{ address }}</div>
<button @click="send">发送</button>
<button @click="revoke">取消</button>
</div>
</template>

<script>

export default {
name: 'aSchool',
data() {
return {
name: '张飒',
address:'纽约'
}
},
methods: {
send() {
this.$emit('atguigu', this.name)
},
// 解绑
revoke() {
this.$off('atguigu')
}
}
}
</script>

<style></style>
总线

适用于任意组件之间通信

  1. 安装:

    1
    2
    3
    4
    5
    6
    7
    // main.js
    new Vue({
    beforeCreate(){
    Vue.prototype.$bus=this
    },
    ......
    })
  2. 使用:

    1. 接收数据

      1
      2
      3
      4
      5
      6
      7
      methods(){
      demo(data){......}
      }

      mounted(){
      this.$bus.$on('xxx', this.demo)
      }
    2. 提供数据:this.$bus.$emit('xxx', 数据)

  3. 销毁:在beforeDestroy中用$off解绑当前组件所用到的事件

消息订阅与发布

使用pubsub-js

任意组件之间调用函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<template>
<div>
<div>学校名称{{ name }}</div>
<div>学校地址{{ address }}</div>
<button @click="send">发送</button>
<button @click="revoke">取消</button>
</div>
</template>

<script>
import pubsub from 'pubsub-js'
export default {
name: 'aSchool',
data() {
return {
name: '张飒',
address:'纽约'
}
},
methods: {
send() {
this.$emit('atguigu', this.name)
},
// 解绑
revoke() {
this.$off('atguigu')
}
},
mounted() {
this.pubId= pubsub.subscribe('hello', function (num,a) {
console.log('发出了订阅请求', num,a);
})
},
beforeDestroy() {
pubsub.unsubscribe(this.pubId)
}
}
</script>

<style></style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<template>
<div>
<div>学校名称{{ name }}</div>
<div>学校地址{{ address }}</div>
<button @click="send">发送</button>
<button @click="revoke">取消</button>
<button @click="sendPub">发布</button>
</div>
</template>

<script>
import pubsub from 'pubsub-js'
export default {
name: 'aSchool',
data() {
return {
name: '张飒',
address:'纽约'
}
},
methods: {
send() {
this.$emit('atguigu', this.name)
},
// 解绑
revoke() {
this.$off('atguigu')
},
sendPub() {
pubsub.publish('hello', 666)
}
},
}
</script>

<style></style>
$nextTick
  1. 语法:this.$nextTick(回调函数)
  2. 作用:在下一次DOM更新结束后执行其指定的回调
  3. 什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行
1
2
3
this.$nextTick(function(){
this.$refs.inputT
})
动画
1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<template>
<div>
<button @click="isShow = !isShow">显示/隐藏</button>
<transition name="hello" appear>
<h1 v-show="isShow">你好啊!</h1>
</transition>
</div>
</template>

<script>
export default {
name:'MyTest',
data() {
return {
isShow:true
}
},
}
</script>

<style scoped>
h1{
background-color: orange;
}

.hello-enter-active{
animation: atguigu 0.5s linear;
}

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

@keyframes atguigu {
from{
transform: translateX(-100%);
}
to{
transform: translateX(0px);
}
}
</style>
2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<template>
<div>
<button @click="isShow = !isShow">显示/隐藏</button>
<transition-group name="hello" appear>
<h1 v-show="!isShow" key="1">你好啊!</h1>
<h1 v-show="isShow" key="2">尚硅谷!</h1>
</transition-group>
</div>
</template>

<script>
export default {
name:'MyTest',
data() {
return {
isShow:true
}
},
}
</script>

<style scoped>
h1{
background-color: orange;
}
/* 进入的起点、离开的终点 */
.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);
}

</style>
  • 离开时触发:

    v-leave, v-leave-active, v-leave-to

  • 进入时触发:

    v-enter, v-enter-active, v-enter-to

Vuex

https://vuex.vuejs.org/zh/

基础

./src/store/index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);

const actions = {
// add(context, value) {
// context.commit("ADD", value);
// },
// subtract(context, value) {
// context.commit("SUBTRACT", value);
// },
addOdd(context, value) {
console.log("context: ", context);
if (context.state.sum % 2) context.commit("ADD", value);
},
addWait(context, value) {
setTimeout(() => {
context.commit("ADD", value);
}, 500);
},
};

const mutations = {
ADD(state, value) {
state.sum += value;
},
SUBTRACT(state, value) {
state.sum -= value;
},
};
const state = {
sum: 0,
};

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

export default new Vuex.Store({
actions,
mutations,
state,
getters,
});

./src/main.js

1
2
3
4
5
6
7
8
9
10
11
import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

import store from './store';

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

./src/App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template>
<div>
<MyCount></MyCount>
</div>
</template>

<script>
import MyCount from './components/MyCount.vue';

export default {
name: 'App',

components: {
MyCount,
}
}
</script>

<style>
button {
margin-left: 10px;
}
</style>

./src/components/MyCount.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<template>
<div>
<h1>当前求和为:{{ $store.state.sum }}</h1>
<h2>他的十倍为:{{ $store.getters.bigSum }}</h2>
<select v-model.number="n">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button @click="increment">+</button>
<button @click="decrement">-</button>
<button @click="incrementOdd">当前求和为奇数再加</button>
<button @click="incrementWait">等一等再加</button>
</div>
</template>

<script>

export default {
name: 'MyCount',
data() {
return {
n: 1,
}
},
methods: {
increment() {
console.log("this: ", this);
this.$store.commit('ADD', this.n)
},
decrement() {
this.$store.commit('SUBTRACT', this.n)
},
incrementOdd() {
this.$store.dispatch('addOdd', this.n)
},
incrementWait() {
this.$store.dispatch('addWait', this.n)
},
}
}
</script>

<style></style>

一个更轻松的写法,定义别名:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<template>
<div>
<h1>当前求和为:{{ $store.state.sum }}</h1>
<h2>他的十倍为:{{ $store.getters.bigSum }}</h2>
<select v-model.number="n">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button @click="increment">+</button>
<button @click="decrement">-</button>
<button @click="incrementOdd">当前求和为奇数再加</button>
<button @click="incrementWait">等一等再加</button>
<hr>
<div>{{ he }}, {{ xuexiao }}, {{ kemu }}, {{ shibei }}</div>
<div>{{ sum}}, {{ school }}, {{ subject }}, {{ bigSum }}</div>
</div>
</template>

<script>
import {mapState, mapGetters} from 'vuex'
export default {
name: 'MyCount',
data() {
return {
n: 1,
}
},
computed: {
...mapState({ he: 'sum', xuexiao: 'school', kemu: 'subject' }),
...mapState(['sum', 'school', 'subject']),
...mapGetters({shibei:'bigSum'}),
...mapGetters(['shibei']),
},
methods: {
increment() {
console.log("this: ", this);
this.$store.commit('ADD', this.n)
},
decrement() {
this.$store.commit('SUBTRACT', this.n)
},
incrementOdd() {
this.$store.dispatch('addOdd', this.n)
},
incrementWait() {
this.$store.dispatch('addWait', this.n)
},
}
}
</script>

<style></style>

MutationsActions也可以映射:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<template>
<div>
<h1>当前求和为:{{ $store.state.sum }}</h1>
<h2>他的十倍为:{{ $store.getters.bigSum }}</h2>
<select v-model.number="n">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button @click="increment(n)">+</button>
<button @click="decrement(n)">-</button>
<button @click="incrementOdd(n)">当前求和为奇数再加</button>
<button @click="incrementWait(n)">等一等再加</button>
<hr>
<div>{{ he }}, {{ xuexiao }}, {{ kemu }}, {{ shibei }}</div>
<div>{{ sum}}, {{ school }}, {{ subject }}, {{ bigSum }}</div>
</div>
</template>

<script>
import {mapState, mapGetters, mapActions, mapMutations} from 'vuex'
export default {
name: 'MyCount',
data() {
return {
n: 1,
}
},
computed: {
...mapState({ he: 'sum', xuexiao: 'school', kemu: 'subject' }),
...mapState(['sum', 'school', 'subject']),
...mapGetters({shibei:'bigSum'}),
...mapGetters(['bigSum']),
},
methods: {
...mapMutations({increment:'ADD', decrement:'SUBTRACT'}),
...mapActions({ incrementOdd: 'addOdd', incrementWait: 'addWait' })
// increment() {
// console.log("this: ", this);
// this.$store.commit('ADD', this.n)
// },
// decrement() {
// this.$store.commit('SUBTRACT', this.n)
// },
// incrementOdd() {
// this.$store.dispatch('addOdd', this.n)
// },
// incrementWait() {
// this.$store.dispatch('addWait', this.n)
// },
}
}
</script>

<style></style>
蓝桥

引入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<script src="https://unpkg.com/vuex"></script>
<script>
// 导入Vuex包
import Vuex from 'vuex'
Vue.use(Vuex)

//创建store对象
const store = new Vuex.Store({
// store中存放的就是全局共享的数据
state: {count: 0},
})

new Vue({
el:'#app',
render: h=>h(app),//渲染app和组件
router,//挂载路由
// 将创建的共享数据对象,挂载到Vue实例中
// 所有的组件,就可以直接总store中获取全局的数据
store
})
</script>

在一个模块化的打包系统中,我们必须显式地通过 Vue.use() 来安装 Vuex:

1
2
3
4
import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

Vuex 中有五个核心概念,它们分别是 StateGettersMutationsActionsModules

首先,在 main.js 文件中写入以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import Vue from "vue";
import App from "./App.vue";
import Vuex from "vuex"; // 导入 Vuex

Vue.use(Vuex); // 使用 Vuex,让 Vuex 可以访问到 Vue
Vue.config.productionTip = false;

// 创建 Store 实例
const store = new Vuex.Store({
state: {
count: 0, // 计数器的初始值
},
});

new Vue({
store, // 注入 Store
render: (h) => h(App),
}).$mount("#app");

有同学可能会问:为啥不叫 vuex 而是 store 呢?🤔

这是因为,Vuex 应用的核心就是 store(仓库)。它是一个用于存储组件共享状态(state)的容器,就像一个小型的数据仓库。它所有的功能和操作都是用于处理这个仓库中的状态而存在的,所以我们在创建 Vuex 配置的时候都是以 store 命名。

接下来,我们在 App.vue 中将计数器的状态展示出来,在文件中写入以下代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<div id="app">{{count}}</div>
</template>

<script>
export default {
name: "App",
// 通过计算属性来访问 count
computed: {
count() {
return this.$store.state.count;
},
},
};
</script>

来这里我们就可以在页面上访问到 count 的数据了,当前页面会显示 0。

接下来,我们要实现点击按钮计数的功能,每点一次按钮数据 +1。

App.vue 文件中定义一个按钮,新增代码如下:

1
2
<!--绑定一个点击事件,用 increment 来执行 count++ 的逻辑-->
<button @click="$store.commit('increment')">++</button>

我们在 main.js 文件中增加 mutations,代码如下:

1
2
3
4
5
6
7
8
const store = new Vuex.Store({
// 此处省略 ...
mutations: {
increment(state) {
state.count++; // 执行 count++ 的操作
},
},
});

计数器的功能就实现啦~ 🎉 效果如下:

图片描述

到此我们已经实现了一个最简单的 Vuex 状态管理,从上面的使用我们可以看出 state 就是用来存储和初始化状态。

通过上面简单的示例,我们知道了 Vuex 主要是用来存储并管理组件共享状态的。

有时候我们需要向后台发出一些异步请求,我们不能直接在 mutations 里进行操作,这时就可以在 actions 中定义一些异步操作。

下面我们来模拟一下异步操作,在页面上新增一个按钮,触发 count-- 的操作。在 App.vue 中新增以下代码:

1
<button @click="$store.dispatch('decrement')">--</button>

注意哦!!! Actions 是通过 store.dispatch 方法来触发 actions 更新 state 状态。

main.js 文件中新增以下内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const store = new Vuex.Store({
mutations: {
decrement(state) {
state.count--;
},
},
actions: {
decrement({ commit }) {
setTimeout(() => {
// 通过 commit 交给 mutations 去处理
commit("decrement");
}, 500);
},
},
});

到这里我们 count-- 的功能也实现了,效果如下:

图片描述

==actions 与 mutations 的区别==

actions 类似于 mutations,不同的是:

  • actions 中的更新函数最终仍是通过调用 mutations 中的函数来更新状态的,不能通过它直接变更状态。
  • mutations 不同,actions 中可以包含任意异步操作。

关于 mutationsactions 等的用法还有其它形式,这些在官网上都有详细的 API,大家可以根据官网 API 对它们进行更多更深入的了解,这里就不再一一细说了。

getters 可以帮助我们缓存数据。

我们增加一个每次计数增加两倍的功能,在 main.js 中新增以下代码:

1
2
3
4
5
getters: {
doubleCount(state) {
return state.count * 2
}
}

然后在页面上获取数据,在 App 文件中新增以下代码:

1
{{$store.getters.doubleCount}}

这样,当点击 ++ 按钮时,计数会以乘 2 的形式增加。效果如下:

Vuex官方文档

你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { createApp } from 'vue'
import { createStore } from 'vuex'

// 创建一个新的 store 实例
const store = createStore({
state () {
return {
count: 0
}
},
mutations: {
increment (state) {
state.count++
}
}
})

const app = createApp({ /* 根组件 */ })

// 将 store 实例作为插件安装
app.use(store)

现在,你可以通过 store.state 来获取状态对象,并通过 store.commit 方法触发状态变更:

1
2
3
store.commit('increment')

console.log(store.state.count) // -> 1

在 Vue 组件中, 可以通过 this.$store 访问store实例。现在我们可以从组件的方法提交一个变更:

1
2
3
4
5
6
methods: {
increment() {
this.$store.commit('increment')
console.log(this.$store.state.count)
}
}

组件之间共享数据的方式

  • 父向子传值:v-bind属性绑定
  • 子向父传值:v-on事件绑定
  • 兄弟组件之间共享数据:EventBus
    • $on接收数据的那个组件
    • $emit发送数据的按个组件
State

State提供唯一的数据源,所有的共享数据都要放到state中

1
2
3
const store = new Vuex.Store({
state: {count: 0,},
})

组件访问state中数据:

  • this.$store.state.全局数据名称 template实例中可以省掉this

路由

基础

./src/router/index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 该文件专门用于创建整个应用的路由器
import VueRouter from "vue-router";
//引入组件
import MyAbout from "../pages/MyAbout";
import MyHome from "../pages/MyHome";
import MyNews from "../pages/MyNews.vue";
import MyMessage from "../pages/MyMessage.vue";

//创建并暴露一个路由器
export default new VueRouter({
routes: [
{
bane:'guanyu'
path: "/about",
component: MyAbout,
},
{
path: "/home",
component: MyHome,
children: [
{
path: "news",
component: MyNews,
},
{
path: "message",
component: MyMessage,
},
],
},
],
});

./src/main.js

1
2
3
4
5
6
7
8
9
10
11
12
import Vue from 'vue'
import App from './App.vue'
import router from "./router";
import VueRouter from 'vue-router';

Vue.config.productionTip = false
Vue.use(VueRouter)

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

./src/pages/Myhome.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<template>
<div>
<h2>Home组件内容</h2>
<div>
<ul class="nav nav-tabs">
<li>
<router-link class="list-group-item" active-class="active" to="/home/news">News</router-link>
</li>
<li>
<router-link class="list-group-item" active-class="active" to="/home/message">Message</router-link>
</li>
</ul>
<ul>
<router-view></router-view>
</ul>
</div>
</div>
</template>

<script>
import { RouterView } from 'vue-router';

export default {
name: 'MyHome',
components: { RouterView }
}
</script>
query参数

发送:

1
2
3
4
5
6
7
<router-link :to="{
path:'/home/message/detail',
query:{
id:m.id,
title:m.title
}
}"></router-link>

接收:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<ul>
<li>消息编号:{{ $route.query.id }}</li>
<li>消息标题:{{ $route.query.title }}</li>
</ul>
</template>

<script>
export default {
name: 'MyDetail',
mounted() {
console.log(this.$route)
},
}
</script>
params参数

router/index.js

1
2
3
4
5
{
name: "xiangxi",
path: "detail/:id/:title",
component: MyDetail,
},

使用:

1
2
3
4
5
6
7
8
<router-link :to="{
// path:'/home/message/detail',
name: 'xiangxi',
params: {
id: m.id,
title: m.title
}
}">{{ m.title }}</router-link>

读取:

{{ $route.params.id }}

image-20231004162939423

image-20231004165105155

  1. this.$router.push(path): 相当于点击路由链接(可以返回到当前路由界面)
  2. this.$router.replace(path): 用新路由替换当前路由(不可以返回到当前路由界面)
  3. this.$router.back(): 请求(返回)上一个记录路由
  4. this.$router.go(-1): 请求(返回)上一个记录路由
  5. this.$router.go(1): 请求下一个记录路由
缓存: keep-alive
1
2
3
<keep-alive :include="['MyNews', 'MyMessage']">
<router-view></router-view>
</keep-alive>

新的生命周期:

1
2
3
4
5
6
7
8
9
10
11
12
activated() {
console.log('News组件被激活了')
this.timer = setInterval(() => {
console.log('@')
this.opacity -= 0.01
if(this.opacity <= 0) this.opacity = 1
},16)
},
deactivated() {
console.log('News组件失活了')
clearInterval(this.timer)
},
路由守卫

对路由进行权限控制

全局守卫
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 全局前置守卫:初始化时、每次路由切换前执行
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
2
3
4
5
6
7
8
beforeEnter(to,from,next){
console.log('beforeEnter',to,from)
if(localStorage.getItem('school') === 'atguigu'){
next()
}else{
alert('暂无权限查看')
}
}
组件内守卫
1
2
3
4
5
//进入守卫:通过路由规则,进入该组件时被调用
beforeRouteEnter (to, from, next) {... next()},

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

引入:

1
https://unpkg.com/vue-router@2.0.0/dist/vue-router.js

我们通过一个单页面应用来看看 Vue-Router 的使用,其基本步骤如下所示:

  • 使用 router-link 组件来导航,其通过 to 属性来指定跳转链接(这相当于 HTML 中的 a 标签)。
  • 使用 router-view 组件定义路由出口,路由匹配到的组件将会渲染到此处。
  • 使用 const routes = [{ path, component }] 来定义路由(路径和组件名)。
  • 使用 const router = new VueRouter({}) 来创建路由实例,在其中传入上一步定义的路由配置 routes
  • 创建和挂载根实例,在 new Vue 中挂载上一步创建的路由实例 router

步骤清楚了,我们来举个例子吧~

使用以下命令获取 Vue 和 Vue-Router 文件。

1
wget https://labfile.oss.aliyuncs.com/courses/10532/vue-router.js

新建一个 index.html 文件,在文件中写入以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="vue.min.js"></script>
<script src="vue-router.js"></script>
</head>

<body>
<div id="app">
<h1>路由的使用</h1>
<p>
<!-- 使用 router-link 组件来导航 -->
<router-link to="/home">首页</router-link>
<router-link to="/hot">热门</router-link>
<router-link to="/class">分类</router-link>
</p>
<!-- 路由出口 -->
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view></router-view>
</div>
<script>
const Home = { template: "<div>首页</div>" };
const Hot = { template: "<div>热门</div>" };
const Class = { template: "<div>分类</div>" };

// 定义路由
const routes = [
{ path: "/home", component: Home },
{ path: "/hot", component: Hot },
{ path: "/class", component: Class },
];

// 创建 router 实例,然后传 routes 配置
const router = new VueRouter({
routes,
});

// 创建和挂载根实例
const app = new Vue({
router,
}).$mount("#app");
</script>
</body>
</html>

效果如下所示:

图片描述

axios

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// vue.config.js
const { defineConfig } = require("@vue/cli-service");
module.exports = defineConfig({
transpileDependencies: true,
// devServer: {
// proxy: "http://localhost:5000",
// },
devServer: {
proxy: {
"/api": {
target: "http://localhost:5000", // 当有前缀'api'时,把请求发送到5000端口
pathRewrite: { '^/api': '' }, // 去掉前缀'api',才能正常查询5000服务器的资源
ws: true,
changeOrigin: true,
},
},
},
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!-- App.vue -->
<template>
<div>
<button @click="getStudents">点击发送请求</button>
</div>
</template>

<script>
import axios from "axios";
export default {
name: 'App',
methods: {
getStudents() {
axios.get('http://localhost:8080/api/students').then(
resopnse => {
console.log('成功了', resopnse);
},
error =>{
console.log('失败了', error);
}
)
}
}
}
</script>

  • 基于promise的网络请求库
1
2
3
4
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

axios.get("地址?key=value & key2=value2").then(function(resopnse){}, function(err){});
axios.post("地址",{key:value, key2:value2}).then(function(resopnse){}, function(err){});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<div id="app">
<input type="button" value="获取笑话" @click="getJoke">
<p> {{joke}} </p>
</div>

<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
let app=new Vue({
el: '#app',
data:{
joke: "很好笑的笑话",
},
methods:{
getJoke: function(){
let that=this; //axios回调函数中this的值已经改变,需要提前保存
axios.get("https://autumnfish.cn/api/joke").then(
function(response){
console.log(response.data);
that.joke = response.data;
}
)
}
}
})
</script>

通用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//-------------------get-----------------------//
axios({
method: "get",
url: "xxx",
cache: false,
params: {
id: 123,
},
headers: "xxx",
});
//-------------------post-----------------------//
axios({
method: "post",
url: "xxx",
data: {
firstName: "Tom",
lastName: "Sun",
},
});

其中需要注意的是,getpost 请求中向后端传递参数的配置项名字不同:get 请求的需要使用 paramspost 请求用于发送数据的为 data

Vue3

小满zs

数组更新检测

一些操作数组的方法,编译会检测,从而会促使视图更新。

  • 变异方法
  • push()
  • pop()
  • shift() 删除并返回第一个
  • unshift() 向开头添加多个元素,返回新长度
  • splice() 删除 / 删除并添加
  • sort()
  • reverse()

上面这些数组操作方法,会直接改变原始数组称为变异方法,会促使视图自动更新。

  • 替换数组

学了 JavaScript 标准对象库,都知道有些数组方法是不直接改变原数组的,这里称它们为非变异方法,例如:filter()、slice()、concat(),它们都是返回一个新数组,那么,在 Vue 中使用到这些方法,怎么样才能促使视图更新呢?我们就必须使用数组替换法,将非变异方法返回的新数组直接赋值给的旧数组。

1
this.nav = this.nav.slice(1, 4);
  • 注意

由于 JavaScript 的限制,Vue 不能检测以下变动的数组:

  1. 当你利用索引直接设置一个项时,例如:vm.items[indexOfItem] = newValue
  2. 当你修改数组的长度时,例如:vm.items.length = newLength

例子:

1
2
3
4
5
6
7
var app = new Vue({
data: {
items: ["a", "b", "c"],
},
});
app.items[1] = "x"; // 不是响应性的
app.items.length = 2; // 不是响应性的

上去直接这样改值操作是没有问题的,但是不是响应式的,并不能触发视图更新,需要用其他方法代替。

例如这样的操作 app.items[indexOfItem] = newValue ,可以用以下两种代替。

1
2
3
4
// Vue.set
Vue.set(vm.items, indexOfItem, newValue);
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 不加括号
<span v-for="number in oddNumber">{{number}}</span>
computed: {
// 计算 numberArray 中为奇数的 oddNumber 奇数数组
oddNumber: function () {
return this.numberArray.filter(function (number) {
return number % 2 === 1;
});
}
//加括号
<span v-for="number in getOddNumber()">{{number}}</span>
methods: {
// 定一个一个获取数组内奇数的方法 filter 数组对象的过滤方法
getOddNumber: function () {
return this.numberArray.filter(function (number) {
return number % 2 === 1;
});
},
}

表单处理

https://cn.vuejs.org/examples/#form-bindings

v-module
  • 只适用于input标签
  • 便捷地设置和获取表单元素的值
  • 绑定的数据会和表单元素值相关联
  • 绑定的数据 <–> 表单元素的值:双向绑定
1
2
3
4
5
6
7
8
9
10
11
<div id="app">
import {ref} from 'vue'
<input type="text" v-model="message" />
</div>

let app=new Vue({
el: '#app',
data: {
message: "黑馬程序員"
}
})
常用表单元素
元素
input[type=*] 文本输入框
textarea 多行文本
radio 单选按钮
checkbox 复选框
select 选择框

==注意==

注意一v-model 会忽略所有表单元素的 valuecheckedselected 特性的初始值而总是将 Vue 实例的数据作为数据来源。直接给元素 value 赋值不会生效的,你应该通过 JavaScript 在组件的 data 选项中声明初始值。

注意二v-model 在内部使用不同的属性为不同的输入元素并抛出不同的事件,具体体现我们在表单 修饰符小节,给大家说明:

  • text 和 textarea 元素使用 value 属性和 input 事件(内部监听 input 事件);

  • checkbox 和 radio 使用 checked 属性和 change 事件(内部监听 change 事件);

  • select 字段将 value 作为 prop 并将 change 作为事件(内部监听 change 事件)。

    说明: change 和 input 区别就是,input 实时更新数据,change 不是实时更新

单选按钮

将单选按钮绑定到同一个 picked,即可完成数据绑定

当第一个单选被选中 picked 的值为第一个单选按钮的 value,

同样当第二个单选被选中 picked 的值为第二个单选按钮的 value。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<body>
<div id="app">
<!-- 将单选按钮绑定到同一个 picked -->
<input type="radio" id="one" value="One" v-model="picked" />
<label for="one">One</label>
<br />
<input type="radio" id="two" value="Two" v-model="picked" />
<label for="two">Two</label>
<br />
<span>Picked: {{ picked }}</span>
</div>
<script>
var vue = new Vue({
el: "#app",
data() {
return {
picked: "",
};
},
});
</script>
</body>
复选框

复选框绑定的是一个布尔值(true or false),同样在复选框元素上使用 v-model 指令,在实例 data 中声明 checked,即可完成复选框数据的双向绑定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<body>
<div id="app">
<input type="checkbox" id="checkbox" v-model="checked" />
<label for="checkbox">{{ checked }}</label>
</div>
<script>
// 绑定布尔值
var vue = new Vue({
el: "#app",
data:{
checked: false,
},
});
</script>
</body>

v-module='check'表示

  • 当选中时,把check = value绑定

  • 如果不存在value,则check为布尔值

修饰符

==lazy==

开始介绍表单处理时,我们说了几点注意,不同的元素,使用的值不同,抛出的事件也不同。

可能开发中,我们不需要数据实时更新,那么,我们怎么将 input 事件与 change 事件替换,

可以使用 .lazy 修饰符,可以将抛出事件由 input 改为 change,使表单元素惰性更新,不实时更新。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<body>
<div id="app">
<!--使用 .lazy 修饰符将文本框 抛出的事件改为 change 事件,不再实时更新,只有文本框失去焦点才更新数据 惰性更新 -->
<input v-model.lazy="msg" />
<p>{{msg}}</p>
</div>
<script>
var vue = new Vue({
el: "#app",
data: {
msg: "hello",
},
});
</script>
</body>

只有文本框失去焦点才更新数据

==number==

自动将用户的输入值转为数值类型

即使在 type="number" 时,HTML 输入元素的值也会返回字符串(默认)

==trim==

过滤首尾空格

响应式修改

image-20230902171945781

Ref全家桶

ref
1
2
3
4
5
6
7
8
9
<script  lang='ts'>
export default {
data(){
return{
age:18
}
}
}
</script>

以前,只有age:18这样的才是响应式对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template>
<div>{{ Man }}</div>
<button @click="change"></button>
</template>

<script setup lang='ts'>
import { ref } from 'vue'
type M = {
name:string
}
const Man = ref<M>({ name: '小满' })

function change() {
Man.value.name = '慢慢慢'
console.log(Man);
}
</script>

<style scoped>

</style>

可以这样

  • ref()构造函数返回的对象有一个value属性,指向他的内部值

    image-20230902210547501

isRef

isRef(Man):判断一个对象是不是ref对象

  • isRef: 检查一个值是否为一个 ref 对象
  • isReactive: 检查一个对象是否是由 reactive 创建的响应式代理
  • isReadonly: 检查一个对象是否是由 readonly 创建的只读代理
  • isProxy: 检查一个对象是否是由 reactive 或者 readonly 方法创建的代理
shallowRef
1
2
3
4
5
6
7
8
9
10
11
<script setup lang='ts'>
import { shallowRef } from 'vue'
const Man = shallowRef({ name: '小满' })

function change() {
Man.value = {
name:'慢慢慢'
}
console.log(Man);
}
</script>

shallowRef是浅层的响应式,属性需要直接挂载到Man.value上,它的响应式只到.value

(内部会更新,但不会响应式地渲染到页面上)

注意:refshallowRef不可以混写,否则会影响shallowRef,造成视图的更新

1
2
3
4
5
6
7
8
9
10
11
12
<script setup lang='ts'>
import { ref, shallowRef } from 'vue'
const Man = ref({ name: '小满' })
const Man2 = shallowRef({ name: '小满' })

function change() {
Man.value.name = '慢慢慢';
Man2.value = '慢慢慢';
console.log(Man);
console.log(Man2);
}
</script>
triggerRef
1
2
3
4
5
6
7
8
9
10
<script setup lang='ts'>
import { ref, shallowRef, triggerRef } from 'vue'
const Man2 = shallowRef({ name: '小满' })

function change() {
triggerRef(Man2);
Man2.value.name = '慢慢慢';
console.log(Man2);
}
</script>

triggerRef会强制更新shallowRef对象

customRef
  • 自定义ref

  • customRef 是个工厂函数要求我们返回一个对象 并且实现 get 和 set 适合去做防抖之类的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<script setup lang='ts'>
import { ref, reactive, onMounted, shallowRef, customRef } from 'vue'

function myRef<T = any>(value: T) {
let timer:any;
return customRef((track, trigger) => {
return {
get() {
track()
return value
},
set(newVal) {
clearTimeout(timer)
timer = setTimeout(() => {
console.log('触发了set')
value = newVal
trigger()
},500)
}
}
})
}

const name = myRef<string>('小满')
const change = () => {
name.value = '大满'
}
</script>
其他

ref可以获取dom属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<div ref="div">小满Ref</div>
<hr>
<div ref="dom">我是dom</div>
<hr>
<button @click="change">修改 customRef</button>
</template>

<script setup lang='ts'>
import { ref, reactive, onMounted, shallowRef, customRef } from 'vue'
const dom = ref<HTMLDivElement>()

const change = () => {
console.log(dom.value?.innerHTML);
}

</script>
<style scoped>
</style>

ref='name'name=ref()的name要求一致

reactive全家桶

reactive
  • ref支持所有类型,reactive只支持引用类型
  • ref取值赋值都需要通过.valuereactive不需要
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<template>
<div>
<form>
<ul>
<li v-for="item in list">{{ item }}</li>
</ul>
<button @click.prevent="add">添加</button>
</form>
</div>
</template>

<script setup lang='ts'>
import {reactive} from 'vue'
let list = reactive<string[]>([])

const add = () => [
setTimeout(() => {
let res = ["EDG", 'REW'];
list = res;
console.log(list);
}, 2000)
]
</script>
<style scoped>
</style>

这样并不能把res的内容渲染到list里面。因为listreactive proxy值,不能直接复制,否则会破坏响应式对象

但是可以push

1
list.push(..res)
readonly
1
2
3
4
5
6
<script setup lang='ts'>
import {reactive, readonly} from 'vue'
let obj = reactive({ name: '小满' });
const read = readonly(obj)
// read.name='sss' // 无法为“name”赋值,因为它是只读属性。
</script>

这样可以改变readonly对象的属性

1
2
3
4
5
6
7
8
9
10
11
<script setup lang='ts'>
import {reactive, readonly} from 'vue'
let obj = reactive({ name: '小满' });
const read = readonly(obj)
// read.name='sss'

const show = () => {
obj.name = 'sss';
console.log(obj, read);
}
</script>
shallowReactive

浅层的Reactive

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<template>
<div>
<form>
<div>{{ obj }}</div>
<button @click.prevent="show">展示</button>
</form>
</div>
</template>

<script setup lang='ts'>
import {reactive, readonly, shallowReactive} from 'vue'
let obj = shallowReactive({
name: {
value: {
key: 1
}
}
})
const show = () => {
obj.name.value.key = 1111;
console.log(obj);
}
</script>
<style scoped>
</style>

shallowReactive也会被Reactive修改

to系列全家桶

toRef
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<div>
<form>
<div>{{ obj }}</div>
<button @click.prevent="change">展示</button>
</form>
</div>
</template>

<script setup lang='ts'>
import {reactive, readonly, shallowReactive} from 'vue'
let obj = {name:111}
const change = () => {
obj.name = 12334;
console.log(obj);
}
</script>
<style scoped>
</style>

这样只能修改值,修改不了视图,不是响应式的

1
2
3
4
5
6
7
8
9
<script setup lang='ts'>
import {reactive, readonly, shallowReactive, toRef} from 'vue'
let obj = { name: 111, like:'JK' }
let like = toRef(obj, 'like'); // toRef应该这样使用
const change = () => {
like.value = '百褶裙'; // 要加value才能访问属性
console.log(obj);
}
</script>

这样也不行,因为toRef只能用给响应式对象:

image-20230903113559397

这样是正确的

1
2
3
4
5
6
7
8
9
<script setup lang='ts'>
import {reactive, readonly, shallowReactive, toRef} from 'vue'
let obj = reactive({ name: 111, like: 'JK' });
let like = toRef(obj, 'like');
const change = () => {
like.value = '百褶裙';
console.log(obj);
}
</script>

image-20230903113814077

toRefs

对每个属性都调用一遍toRef

手动实现:

1
2
3
4
5
6
7
const toRefs = <T extends object>(object: T) => {
const map: any = {};
for (let key in object) {
map[key]=toRef(object, key)
}
return map;
}
toRaw

脱去代理

1
console.log(obj, toRaw(obj));

image-20230903115658432

计算属性

计算属性就是当依赖的属性的值发生变化的时候,才会触发他的更改,如果依赖的值,不发生变化的时候,使用的是缓存中的属性值。

  1. 函数形式(常用,只设置setter)

    1
    2
    3
    4
    5
    6
    7
    8
    import { computed, reactive, ref } from 'vue'
    let price = ref(0)//$0

    let m = computed<string>(()=>{
    return `$` + price.value
    })

    price.value = 500
  2. 对象形式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <template>
    <div>{{ mul }}</div>
    <div @click="mul = 100">click</div>
    </template>

    <script setup lang="ts">
    import { computed, ref } from 'vue'
    let price = ref<number | string>(1)//$0
    let mul = computed({
    get: () => {
    return price.value
    },
    set: (value) => {
    price.value = 'set' + value
    }
    })
    </script>

    <style>
    </style>

侦听属性

watch

watch 需要侦听特定的数据源,并在单独的回调函数中执行副作用

  1. watch第一个参数监听源

  2. watch第二个参数回调函数cb(newVal,oldVal)

  3. watch第三个参数一个options配置项是一个对象:

    1
    2
    3
    4
    {
    immediate:true //是否立即调用一次
    deep:true //是否开启深度监听
    }

实例:

监听一个
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { ref, watch } from 'vue'

let message = ref({
nav:{
bar:{
name:""
}
}
})

watch(message, (newVal, oldVal) => {
console.log('新的值----', newVal);
console.log('旧的值----', oldVal);
},{
immediate:true,
deep:true
})
监听多个:
1
2
3
4
5
6
7
8
9
import { ref, watch ,reactive} from 'vue'

let message = ref('')
let message2 = ref('')

watch([message,message2], (newVal, oldVal) => {
console.log('新的值----', newVal);
console.log('旧的值----', oldVal);
})
监听Reactive

使用reactive监听深层对象开启和不开启deep 效果一样

1
2
3
4
5
6
7
8
9
10
11
12
13
import { ref, watch ,reactive} from 'vue'

let message = reactive({
nav:{
bar:{
name:""
}
}
})
watch(message, (newVal, oldVal) => {
console.log('新的值----', newVal);
console.log('旧的值----', oldVal);
})
只监听reactive的一个值

需要是一个函数的返回值

1
2
3
4
5
6
7
8
9
10
import { ref, watch ,reactive} from 'vue'

let message = reactive({
name:"",
name2:""
})
watch(()=>message.name, (newVal, oldVal) => {
console.log('新的值----', newVal);
console.log('旧的值----', oldVal);
})
watchEffect

详见:小满vue-watchEffect

(页面加载的时候会立即执行一次)

立即执行传入的一个函数,同时响应式追踪其依赖,并在其依赖变更时重新运行该函数。

如果用到message 就只会监听message 就是用到几个监听几个 而且是非惰性 会默认调用一次

1
2
3
4
5
6
let message = ref<string>('')
let message2 = ref<string>('')
watchEffect(() => {
//console.log('message', message.value);
console.log('message2', message2.value);
})

组件&生命周期

引入:

1
import A form './components/aaa.vue'

A可以为任意名称

使用:

1
2
3
4
<template>
<A></A>
<A></A>
</template>

生命周期:

  1. 在setup语法糖模式下,没有beforecreated这两个生命周期,可以用setup代替

  2. onBeforeMount()
    在组件DOM实际渲染安装之前调用。在这一步中,根元素还不存在。

  3. onMounted()
    在组件的第一次渲染后调用,该元素现在可用,允许直接DOM访问
    v-if会触发这两个,但v-show不会

  4. onBeforeUpdate()
    数据更新时调用,发生在虚拟 DOM 打补丁之前。

  5. onUpdated()
    DOM更新后,updated的方法即会调用。

  6. onBeforeUnmount()
    在卸载组件实例之前调用。在这个阶段,实例仍然是完全正常的。

  7. onUnmounted()
    卸载组件实例后调用。调用此钩子时,组件实例的所有指令都被解除绑定,所有事件侦听器都被移除,所有子组件实例被卸载。

实操组件和认识less sass 和 scoped

bem架构:

他是一种css架构 oocss 实现的一种 (面向对象css) ,BEM实际上是blockelementmodifier的缩写,分别为块层、元素层、修饰符层,element UI 也使用的是这种架构

BEM 命名约定的模式是:

1
2
3
4
5
.block {}

.block__element {}

.block--modifier {}

使用sass 最小单元复刻一个bem 架构(写在./src/bem.scss中):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
$block-sel: "-" !default;
$element-sel: "__" !default;
$modifier-sel: "--" !default;
$namespace:'xm' !default;
@mixin bfc {
height: 100%;
overflow: hidden;
}

//混入
@mixin b($block) {
$B: $namespace + $block-sel + $block; //变量
.#{$B}{ //插值语法#{}
@content; //内容替换
}
}

@mixin flex {
display: flex;
}

@mixin e($element) {
$selector:&;
@at-root {
#{$selector + $element-sel + $element} {
@content;
}
}
}

@mixin m($modifier) {
$selector:&;
@at-root {
#{$selector + $modifier-sel + $modifier} {
@content;
}
}
}

全局扩充scss,(在./vite/config.ts文件中):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
css: {
preprocessorOptions: {
scss: {
additionalData: "@import './src/bem.scss';"
}
}
}
})

父子组件传参

父传子

父组件发送:

直接使用v-bind:

1
2
3
4
5
6
7
8
9
10
<template>
<waterFall :title="name"> </waterFall>
</template>

<script setup lang='ts'>
import waterFall from "./components/water-fall.vue";
let name='小满'
</script>

<style scoped></style>

子组件接收:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<div>子集</div>
<div>值:{{ title }}</div>
</template>

<script setup lang="ts">
import { ref, reactive } from 'vue';
const props = defineProps({
title: {
type: String,
default: "默认值"
}
})
console.log(props.title);
</script>

<style scoped lang="scss">
</style>

可以在template中直接使用,但在script中使用时,要加上props.

使用ts写法:

1
2
3
4
5
6
7
import { ref, reactive } from 'vue';
withDefaults(defineProps<{
title: string,
arr:number[]
}>(), {
arr: ()=>[666]
})
子传父

子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<div>子集</div>
<button @click="send">给父组件传值</button>
</template>


<script setup lang="ts">
const emit=defineEmits(['on-click'])
const send = () => {
emit('on-click', '小满')
}
</script>


<style scoped lang="scss">

</style>

使用ts:

1
2
3
4
5
6
const emit = defineEmits<{
(e:'on-click', name:string):void
}>()
const send = () => {
emit('on-click', '小满')
}

父:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<waterFall @on-click="getName"> </waterFall>
</template>

<script setup lang='ts'>
import waterFall from "./components/water-fall.vue";
const getName = (name: string) => {
console.log(name, '========>我是父组件');
}
</script>


<style scoped>

</style>
子组件变量暴露给父组件

父组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<waterFallVue ref="waterFall"> </waterFallVue>
</template>

<script setup lang='ts'>
import { ref } from "vue";
import waterFallVue from "./components/water-fall.vue";
const waterFall = ref<InstanceType<typeof waterFallVue>>() // 获取dom
console.log(waterFall.value?.list);
</script>


<style scoped>

</style>

子组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<div>子集</div>
</template>


<script setup lang="ts">
import { reactive } from 'vue';

const list = reactive<number[]>([4, 5, 6])
defineExpose({
list
})
</script>


<style scoped lang="scss">

</style>

全局组件,局部组件,递归组件

小满

注册全局组件:
1
2
3
4
5
6
7
8
9
// ./src/main.ts
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'

import waterFull from "./components/water-fall.vue";
export const app = createApp(App);
app.component('water-full',waterFull);
app.mount('#app');

注册完毕后,可以在其他地方使用,无需引入

递归组件
1
2
3
4
5
6
<template>
<div class="tree" v-for="item in data">
<input type="checkbox"> <span>{{ item.name }}</span>
<Tree v-if="item?.children?.length" :data="item.children"></Tree>
</div>
</template>

Tree可以是当前文件名

动态组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<template>
<div style="display: flex;">
<div @click="switchCom(item, index)" :class="{'active': active==index}" class="tabs" v-for="(item, index) in data">
<div>{{ item.name }}</div>
</div>
</div>
</template>

<script setup lang='ts'>
import { ref, reactive } from 'vue';
import AVue from './components/HelloWorld.vue';
import BVue from './components/HelloWorld.vue';
import CVue from './components/HelloWorld.vue';

const comId = ref(AVue);
const active = ref(0);
const data = reactive([
{
name: 'A组件',
com:AVue
},
{
name: 'B组件',
com:BVue
},
{
name: 'C组件',
com:CVue
},
]);
const switchCom = (item, index) => {
comId.value = item.com;
active.value = index;
}
</script>


<style scoped>

</style>

性能优化

1
2
3
4
5
6
7
const tab = reactive<Com[]>([{
name: "A组件",
comName: markRaw(A)
}, {
name: "B组件",
comName: markRaw(B)
}])

插槽slot

匿名插槽

在子组件放置一个插槽

1
2
3
4
5
<template>
<div>
<slot></slot>
</div>
</template>

父组件使用插槽

在父组件给这个插槽填充内容

1
2
3
4
5
<Dialog>
<template v-slot>
<div>2132</div>
</template>
</Dialog>
具名插槽

具名插槽其实就是给插槽取个名字。一个子组件可以放多个插槽,而且可以放在不同的地方,而父组件填充内容时,可以根据这个名字把内容填充到对应插槽中

1
2
3
4
5
6
<div>
<slot name="header"></slot>
<slot></slot>

<slot name="footer"></slot>
</div>

父组件使用需对应名称

1
2
3
4
5
6
7
8
9
10
11
12
13
<Dialog>
<template v-slot:header>
<div>1</div>
</template>

<template v-slot>
<div>2</div>
</template>

<template v-slot:footer>
<div>3</div>
</template>
</Dialog>
作用域插槽

在子组件动态绑定参数 派发给父组件的slot去使用

1
2
3
4
5
6
7
8
9
10
<div>
<slot name="header"></slot>
<div>
<div v-for="item in 100">
<slot :data="item"></slot>
</div>
</div>

<slot name="footer"></slot>
</div>

通过解构方式取值

(v-slot有对应的简写: #)

1
2
3
4
5
6
7
8
9
10
11
<Dialog>
<template #header>
<div>1</div>
</template>
<template #default="{ data }">
<div>{{ data }}</div>
</template>
<template #footer>
<div>3</div>
</template>
</Dialog>

简写:

1
2
3
4
5
6
7
8
9
10
11
<Dialog>
<template #header>
<div>1</div>
</template>
<template #default>
<div>2</div>
</template>
<template #footer>
<div>3</div>
</template>
</Dialog>
动态插槽

插槽可以是一个变量名

1
2
3
4
5
6
7
8
9
10
11
<template>
<Dialog>
<template #[name]>
<div>23</div>
</template>
</Dialog>
</template>

<script>
const name = ref('header')
</script>

内置组件

Teleport传送组件

[小满(https://xiaoman.blog.csdn.net/article/details/122916261)

Teleport 是一种能够将我们的模板渲染至指定DOM节点,不受父级style、v-show等属性影响,但data、prop数据依旧能够共用的技术;类似于 React 的 Portal。

主要解决的问题 因为Teleport节点挂载在其他指定的DOM节点下,完全不受父级style样式影响使用方法

通过to 属性 插入指定元素位置 to=”body” 便可以将Teleport 内容传送到指定位置

也可以自定义传送位置 支持 class id等 选择器

keep-alive缓存组件
1
<keep-alive :include="['A', 'B']" :exclude="['C', 'D']" :max=""></keep-alive>
  • include:要缓存的组件
  • exclude:不缓存的组件
  • max:缓存组件的最大数量,LRU算法

生命周期

1
2
3
4
5
6
7
8
9
10
11
12
onMounted(() => {
console.log('初始化'); //只做一次
})
onActivated(() => {
console.log('keep-alive初始化'); //每次切换时和第一次注册时都做
})
onDeactivated(() => {
console.log('keep-alive卸载'); //每次切换时都做
})
onUnmounted(() => {
console.log('卸载'); //不做
})
transition 过渡组件

Vue 提供了 transition 的封装组件,在下列情形中,可以给任何元素和组件添加进入/离开过渡:

  • 条件渲染 (使用 v-if)
  • 条件展示 (使用 v-show)
  • 动态组件
  • 组件根节点

自定义 transition 过度效果,你需要对transition组件的name属性自定义。并在css中写入对应的样式

在进入/离开的过渡中,会有 6 个 class 切换,下面的v代表templatename的值

  1. v-enter-from:定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。
  2. v-enter-active:定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。
  3. v-enter-to:定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时 v-enter-from 被移除),在过渡/动画完成之后移除。
  4. v-leave-from:定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。
  5. v-leave-active:定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。
  6. v-leave-to:离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时 v-leave-from 被移除),在过渡/动画完成之后移除。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//开始过度
.fade-enter-from{
background:red;
width:0px;
height:0px;
transform:rotate(360deg)
}
//开始过度了
.fade-enter-active{
transition: all 2.5s linear;
}
//过度完成
.fade-enter-to{
background:yellow;
width:200px;
height:200px;
}
//离开的过度
.fade-leave-from{
width:200px;
height:200px;
transform:rotate(360deg)
}
//离开中过度
.fade-leave-active{
transition: all 1s linear;
}
//离开完成
.fade-leave-to{
width:0px;
height:0px;
}

依赖注入Provide / Inject

注册一个变量,可以全局使用

image-20230911151041239

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<template>
<h1>app.vue</h1>
<label>
<input v-model="colorVal" value='red' name="color" type="radio"> 红色
</label>
<label>
<input v-model="colorVal" value='pink' name="color" type="radio"> 粉色
</label>
<label>
<input v-model="colorVal" value='green' name="color" type="radio"> 绿色
</label>
<div class="box"></div>
<hr>
<vueA></vueA>
</template>

<script setup lang='ts'>
import { provide, ref } from 'vue';
import vueA from './components/providea.vue'

const colorVal=ref<string>('red')
provide('color', colorVal)

</script>

<style scoped>
.box{
width: 100px;
height: 100px;
background-color: v-bind(colorVal);
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<div></div>
<hr>
<vueB></vueB>
</template>

<script setup lang='ts'>
import type{Ref} from 'vue'
import { inject} from 'vue'
import vueB from './provideb.vue'
const color = inject<Ref<string>>('color')
</script>

<style scoped>
div{
width: 100px;
height: 100px;
background-color: v-bind(color);
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
<div></div>
<button @click="change(111)">绿!</button>
</template>

<script setup lang='ts'>
import { inject } from "vue";
import type { Ref } from 'vue';
const color=inject<Ref<string>>('color')
const change = (num: number)=>{
console.log(num);
color!.value='green'
}
</script>

<style scoped>
div{
width: 100px;
height: 100px;
background-color: v-bind(color);
}
</style>

兄弟组件传参和bus

借助父组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<div>
<A @on-click="getFalg"></A>
<B :flag="Flag"></B>
</div>
</template>

<script setup lang='ts'>
import A from './components/A.vue'
import B from './components/B.vue'
import { ref } from 'vue'
let Flag = ref<boolean>(false)
const getFalg = (flag: boolean) => {
Flag.value = flag;
}
</script>

<style>
</style>

A 组件派发事件通过App.vue 接受A组件派发的事件然后在Props 传给B组件 也是可以实现的

缺点就是比较麻烦 ,无法直接通信,只能充当桥梁

Event Bus

我们在Vue2可以使用 $emit 传递 $on监听 emit传递过来的事件

这个原理其实是运用了JS设计模式之发布订阅模式

我写了一个简易版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
 type BusClass<T> = {
emit: (name: T) => void
on: (name: T, callback: Function) => void
}
type BusParams = string | number | symbol
type List = {
[key: BusParams]: Array<Function>
}
class Bus<T extends BusParams> implements BusClass<T> {
list: List
constructor() {
this.list = {}
}
emit(name: T, ...args: Array<any>) {
let eventName: Array<Function> = this.list[name]
eventName.forEach(ev => {
ev.apply(this, args)
})
}
on(name: T, callback: Function) {
let fn: Array<Function> = this.list[name] || [];
fn.push(callback)
this.list[name] = fn
}
}

export default new Bus<number>()
mitt库

https://xiaoman.blog.csdn.net/article/details/125453908

  1. 安装
1
npm install mitt -S
  1. main.ts 初始化
    全局总线,vue 入口文件 main.js 中挂载全局属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { createApp } from 'vue'
import App from './App.vue'
import mitt from 'mitt'

const Mit = mitt()

//TypeScript注册
// 由于必须要拓展ComponentCustomProperties类型才能获得类型提示
declare module "vue" {
export interface ComponentCustomProperties {
$Bus: typeof Mit
}
}

const app = createApp(App)

//Vue3挂载全局API
app.config.globalProperties.$Bus = Mit

app.mount('#app')

TSX

https://xiaoman.blog.csdn.net/article/details/123172735

JSX本身是一种JavaScript的语法扩展,用于在JavaScript代码中编写类似于HTML的结构。它通常与React一起使用,用于构建用户界面。

TSX是指”TypeScript JSX”,是一种使用TypeScript编写的JavaScript的扩展语法。

vite.config.ts配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue(), vueJsx()],
css: {
preprocessorOptions: {
scss: {
additionalData: "@import './src/bem.scss';"
}
}
}
})

App.vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<div>
<xiaoman></xiaoman>
</div>
</template>

<script setup lang='ts'>
import xiaoman from './App.tsx'
</script>

<style scoped>

</style>

App.tsx第一种写法:

1
2
3
4
// App.tsx
export default function () {
return (<div>小满</div>)
}

另一种:

1
2
3
4
5
6
7
8
9
10
11
12
// 返回一个渲染函数
import { defineComponent } from "vue"
export default defineComponent({
data() {
return {
age: 23
}
},
render() {
return (<div>{ this.age }</div>)
}
})

第三种:

1
2
3
4
5
6
7
8
9
// 返回一个渲染函数
// optionsAPI

import { defineComponent } from "vue"
export default defineComponent({
setup() {
return () => <div>hello</div>
},
})

TIPS tsx不会自动解包,使用ref也要加.vlaue ! ! !

  • tsx支持 v-modelv-show的使用

  • v-if不支持

  • v-for不支持(可以用.map() 模拟)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    import { ref } from 'vue'

    let arr = [1,2,3,4,5]

    const renderDom = () => {
    return (
    <>
    {
    arr.map(v=>{
    return <div>${v}</div>
    })
    }
    </>
    )
    }

    export default renderDom
  • v-bind使用:

    直接赋值即可

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import { ref } from 'vue'

    let arr = [1, 2, 3, 4, 5]

    const renderDom = () => {
    return (
    <>
    <div data-arr={arr}>1</div>
    </>
    )
    }

    export default renderDom
  • v-on绑定事件 所有的事件都按照react风格来

    1. 所有事件有on开头
    2. 所有事件名称首字母大写
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
     
    const renderDom = () => {
    return (
    <>
    <button onClick={clickTap}>点击</button>
    </>
    )
    }

    const clickTap = () => {
    console.log('click');
    }

    export default renderDom

directive自定义指令

样例简介
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<template>
<div>
<button>切换</button>
<A v-move:aaa.xiaoman="{background: 'red'}"></A>
</div>
</template>

<script setup lang='ts'>
import A from './components/A.vue'
import { ref, Directive, DirectiveBinding } from 'vue'
let flag = ref<boolean>(true)
const vMove: Directive = {
created() {
console.log('========> created');
console.log(arguments);
},
beforeMount() {
console.log('========> created');
console.log(arguments);
},
mounted() {
console.log('========> created');
console.log(arguments);
},
beforeUpdate() {
console.log('========> created');
console.log(arguments);
},
updated() {
console.log('========> created');
console.log(arguments);
},
beforeUnmount() {
console.log('========> created');
console.log(arguments);
},
unmounted() {
console.log('========> created');
console.log(arguments);
},
}
</script>

<style scoped>

</style>

A.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<div class="AAA"></div>
</template>

<script setup lang='ts'>

</script>

<style scoped>
div{
width: 100px;
height: 100px;
background-color: #888;
}
</style>

输出:

image-20230912201155664

  1. div.AAA是子组件的选择器el,类型:HTMLElement

  2. image-20230912201356552

    类型:DirectiveBinding,传过来的参数:

    • instance:使用指令的组件实例。
    • value:传递给指令的值。例如,在 v-my-directive=”1 + 1” 中,该值为 2。
    • oldValue:先前的值,仅在 beforeUpdate 和 updated 中可用。无论值是否有更改都可用。
    • arg:传递给指令的参数(如果有的话)。例如在 v-my-directive:foo 中,arg 为 “foo”。
    • modifiers:包含修饰符(如果有的话) 的对象。例如在 v-my-directive.foo.bar 中,修饰符对象为 {foo: true,bar: true}。
    • dir:一个对象,在注册指令时作为参数传递。例如,在以下指令中
  3. 当前组件的v-node,虚拟dom

  4. prevode,上一个虚拟dom

函数简写

可能想在 mountedupdated 时触发相同行为,而不关心其他的钩子函数。那么你可以通过将这个函数模式实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<template>
<div class="btns">
<button v-has-show="'shop:create'">创建</button>

<button v-has-show="'shop:edit'">编辑</button>

<button v-has-show="'shop:delete'">删除</button>
</div>
</template>

<script setup lang='ts'>
import { ref, reactive, } from 'vue'
import type {Directive} from 'vue'
//permission
localStorage.setItem('userId','xiaoman-zs')

//mock后台返回的数据
const permission = [
'xiaoman-zs:shop:edit',
'xiaoman-zs:shop:create',
'xiaoman-zs:shop:delete'
]
const userId = localStorage.getItem('userId') as string
const vHasShow:Directive<HTMLElement,string> = (el,bingding) => {
if(!permission.includes(userId+':'+ bingding.value)){
el.style.display = 'none'
}
}

</script>

<style scoped lang='less'>
.btns{
button{
margin: 10px;
}
}
</style>
图片懒加载
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<template>
<div>
<div v-for="item in arr">
<img height="500" :data-index="item" v-lazy="item" width="360" alt="">
</div>
</div>
</template>

<script setup lang='ts'>
import { ref, reactive } from 'vue'
import type { Directive } from 'vue'
const images: Record<string, { default: string }> = import.meta.globEager('./assets/images/*.*')
let arr = Object.values(images).map(v => v.default)

let vLazy: Directive<HTMLImageElement, string> = async (el, binding) => {
let url = await import('./assets/vue.svg')
el.src = url.default;
let observer = new IntersectionObserver((entries) => {
console.log(entries[0], el)
if (entries[0].intersectionRatio > 0 && entries[0].isIntersecting) {
setTimeout(() => {
el.src = binding.value;
observer.unobserve(el)
}, 2000)
}
})
observer.observe(el)
}

</script>

<style scoped lang='less'></style>

自定义attrs / hooks

https://xiaoman.blog.csdn.net/article/details/123271189

主要用来处理复用代码逻辑的一些封装

这个在vue2 就已经有一个东西是Mixins

mixins就是将这些多个相同的逻辑抽离出来,各个组件只需要引入mixins,就能实现一次写代码,多组件受益的效果。

弊端就是 会涉及到覆盖的问题

组件的data、methods、filters会覆盖mixins里的同名data、methods、filters。

自定义attrs
1
2
3
4
5
6
7
8
9
10
11
12
<template>
<A a="111" b.ccc="cscacsa"></A>
</template>

<script setup lang='ts'>
import A from './components/A.vue'

</script>

<style scoped>

</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
</template>

<script setup lang='ts'>
import {useAttrs} from 'vue';
let attr = useAttrs()
console.log(attr.a);

</script>

<style scoped>

</style>
自定义hooks

图片转base64

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import { onMounted } from 'vue'


type Options = {
el: string
}

type Return = {
Baseurl: string | null
}
export default function (option: Options): Promise<Return> {

return new Promise((resolve) => {
onMounted(() => {
const file: HTMLImageElement = document.querySelector(option.el) as HTMLImageElement;
file.onload = ():void => {
resolve({
Baseurl: toBase64(file)
})
}
})
const toBase64 = (el: HTMLImageElement): string => {
const canvas: HTMLCanvasElement = document.createElement('canvas')
const ctx = canvas.getContext('2d') as CanvasRenderingContext2D
canvas.width = el.width
canvas.height = el.height
ctx.drawImage(el, 0, 0, canvas.width,canvas.height)
console.log(el.width);
return canvas.toDataURL('image/png')
}
})
}

全局函数和变量

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<div>{{ $env }}</div>
<div>{{ $filters.format('的飞机') }}</div>
</template>

<script setup lang='ts'>
import { ref, reactive, getCurrentInstance } from 'vue';
const app = getCurrentInstance()
console.log(app?.proxy.$env);

</script>

<style scoped>

</style>

main.ts

1
2
3
4
5
6
7
8
9
10
11
12
import { createApp } from 'vue'
import App from './App.vue'

export const app = createApp(App)
app.config.globalProperties.$env = 'dev';

app.config.globalProperties.$filters = {
format<T>(str: T) {
return `小满-${str}`
}
}
app.mount('#app')
消除报错版
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import { createApp } from 'vue'
import App from './App.vue'

export const app = createApp(App)
app.config.globalProperties.$env = 'dev';

app.config.globalProperties.$filters = {
format<T>(str: T) {
return `小满-${str}`
}
}

type Filter = {
format<T>(str: T): string
}

// 声明要扩充@vue/runtime-core包的声明.
// 这里扩充"ComponentCustomProperties"接口, 因为他是vue3中实例的属性的类型.
declare module 'vue' {
export interface ComponentCustomProperties {
$filters: Filter,
$env: string
}
}

app.mount('#app')

新的组件

1.Fragment
  • 在Vue2中: 组件必须有一个根标签
  • 在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中
  • 好处: 减少标签层级, 减小内存占用
2.Teleport
  • 什么是Teleport?—— Teleport 是一种能够将我们的组件html结构移动到指定位置的技术。

    1
    2
    3
    4
    5
    6
    7
    8
    <teleport to="移动位置">
    <div v-if="isShow" class="mask">
    <div class="dialog">
    <h3>我是一个弹窗</h3>
    <button @click="isShow = false">关闭弹窗</button>
    </div>
    </div>
    </teleport>
3.Suspense
  • 等待异步组件时渲染一些额外内容,让应用有更好的用户体验

  • 使用步骤:

    • 异步引入组件

      1
      2
      import {defineAsyncComponent} from 'vue'
      const Child = defineAsyncComponent(()=>import('./components/Child.vue'))
    • 使用Suspense包裹组件,并配置好defaultfallback

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      <template>
      <div class="app">
      <h3>我是App组件</h3>
      <Suspense>
      <template v-slot:default>
      <Child/>
      </template>
      <template v-slot:fallback>
      <h3>加载中.....</h3>
      </template>
      </Suspense>
      </div>
      </template>

其他vue3新特性

1.全局API的转移
  • Vue 2.x 有许多全局 API 和配置。

    • 例如:注册全局组件、注册全局指令等。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      //注册全局组件
      Vue.component('MyButton', {
      data: () => ({
      count: 0
      }),
      template: '<button @click="count++">Clicked {{ count }} times.</button>'
      })

      //注册全局指令
      Vue.directive('focus', {
      inserted: el => el.focus()
      }
  • Vue3.0中对这些API做出了调整:

    • 将全局的API,即:Vue.xxx调整到应用实例(app)上

      2.x 全局 API(Vue 3.x 实例 API (app)
      Vue.config.xxxx app.config.xxxx
      Vue.config.productionTip 移除
      Vue.component app.component
      Vue.directive app.directive
      Vue.mixin app.mixin
      Vue.use app.use
      Vue.prototype app.config.globalProperties
2.其他改变
  • data选项应始终被声明为一个函数。

  • 过度类名的更改:

    • Vue2.x写法

      1
      2
      3
      4
      5
      6
      7
      8
      .v-enter,
      .v-leave-to {
      opacity: 0;
      }
      .v-leave,
      .v-enter-to {
      opacity: 1;
      }
    • Vue3.x写法

      1
      2
      3
      4
      5
      6
      7
      8
      9
      .v-enter-from,
      .v-leave-to {
      opacity: 0;
      }

      .v-leave-from,
      .v-enter-to {
      opacity: 1;
      }
  • 移除keyCode作为 v-on 的修饰符,同时也不再支持config.keyCodes

  • 移除v-on.native修饰符

    • 父组件中绑定事件

      1
      2
      3
      4
      <my-component
      v-on:close="handleComponentEvent"
      v-on:click="handleNativeClickEvent"
      />
    • 子组件中声明自定义事件

      1
      2
      3
      4
      5
      <script>
      export default {
      emits: ['close']
      }
      </script>
  • 移除过滤器(filter)

    过滤器虽然这看起来很方便,但它需要一个自定义语法,打破大括号内表达式是 “只是 JavaScript” 的假设,这不仅有学习成本,而且有实现成本!建议用方法调用或计算属性去替换过滤器。

  • ……


vue笔记
https://asxjdb.github.io/2023/10/09/vue笔记/
作者
Asxjdb
发布于
2023年10月9日
许可协议