Vue 笔记(未整理)

vuedemo20230801

Project setup

npm install

Compiles and hot-reloads for developments

npm run serve

Compiles and minifies for production

npm run build

Lints and fixes files

npm run lint

Customize configuration

See Configuration Reference.

Vue 是用来构建用户界面的渐进式框架
渐进式:如果应用很简单,就引用一个轻量小巧的核心库
如果需要更多,则可以引用很多复杂的Vue第三方库
允许用户从简单向复杂按需引用
Vue 特点
组件化:提高代码复用率,跟容易维护
申明式编码:让编码人员无需直接操作DOM,提高开发效率

Vue 文档
API,相当于一个字典,有问题查询API
Awesome Vue 万能的Vue,官方给出的好用的第三方库
网站:awesomejs.dev/for/vue

引入vue
<script type="text/javascript" src="../js/vue.js"></script>
引入Jquery,代码里可以通过$或者jQuery调用封装方法
引入Vue,代码里就多了一个Vue 构造函数

开发建议浏览器安装DEV tools: vue.js.devtools

VSCode 安装 LiveServer 辅助开发

Vue3 代码插件推荐
Vue 3 Snippets

=======================================================
1.想让Vue工作,就必须创建一个Vue实例,且要传入一个配置对象
2.root 容器里的代码依然符合html规范,只不过混入了一些特殊的vue语法
3.root 容器里的代码被称为 Vue模板
4.Vue实例和容器是一一对应的关系
5.真是开发中只有一个Vue实例,并且会配合着组件一起使用
6.{{XXX}} 插值模板中的XXX 是js 表达式,XXX 会自动读取到data中对应的属性
7.一旦data的数据发生变化,那么模板中用到该数据的地方也会自动更新

注意区分 js 表达式 和 js 语句

  1. 表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方
    (1)变量a
    (2)运算表达式,a + b
    (3)方法表达式,demo(1)
    (4)三元表达式
    ...
  2. js 代码(语句)
    (1)if() {}
    (2)for() {}

code 示例

<div id="root"></div>

new Vue({
el: "#root",
data: {
name: 'tom'
}
})
===============================================================
<a v-bind:href="url">链接地址</a>
把Vue对象data里的url值绑定到 a 标签上,v-bind简写方式为:
<a :href="url">链接地址</a>
<a v-bind:href="url">链接地址</a>

插值语法一般用于标签体里面的内容的绑定
指令用于解析标签属性
v-bind 一般用于标签属性的绑定,简写为:
v-model 双向数据绑定,但是v-model只能应用于输入元素(元素有value标签)
简写为v-model
=================================================================
const v = new Vue({
el: "#root",
data: {
name: "tome"
}

cosnole.log(v); 查看v对象

})

v对象原型有个mount方法,可以绑定vue对象到元素上,写法如下 v.mount('#root'),此写法跟new vue 是传入el: "#root" 效果一样
但是v.$mount 更灵活,比如设置定时器,在页面加载一秒钟以后再绑定模板
mount 是挂载的意思

data绑定还可以用函数时
data: function() {
return{
name: "tom"
}
}
data() {
return{
name: "tom"
}
}

data函数必须是普通函数,不支持箭头函数,如果使用箭头函数,调用this不再是Vue实例,而是window
箭头函数没有自己的this,只能往外找,找到window对象
==================================================================================
MVVM模型
M: 对象Vue对象data里的数据,
V: 页面模板
VM: Vue实例对象
Vue实例对象通过Dom Listeners 监听页面数据变化绑定到模型
通过Data Bindings 把data 数据绑定到页面模板

data 中所有的属性最终都会出现在vm身上
vm 身上的所有属性以及vm原型的所有属性,都可以在模板里直接使用
=================================================================================
Object.defineproperty方法

let person = {
name: "tom"
}

给person 添加age属性
Object.defineproperty(person, 'age', {
value: 18
})

通过Object.keys(person) 遍历属性名只能拿到 name 属性,通过 Object.defineproperty 添加的属性age遍历不到

想要age 可遍历(或者叫可枚举),用下面这种写法
Object.defineproperty(person, 'age', {
value: 18,
enumerable: true, // 控制属性是否可枚举,默认false
writable: true, // 控制属性是否可以被修改,默认false
configurable: true, // 控制属性是否可以被删除(通过 delete person.age 来删除属性),默认false
get() { // 当有人读取age 属性的值的时候,get 属性就会被调用,且返回值就是age 的值
},
set(value) { // 当有人修改age 值的时候会调用set方法,且回收厚道修改的value值
}
})

数据代理技术
通过一个对象代理另一个对象的读写操作
let obj1 = {x: 100}
let obj2 = {y: 200}
让obj2 代理obj1
Object.defineproperty(obj2, 'x', {
get() {
return obj1.x;
},
set(value) {
obj1.x = value;
}
})

vm._data == vue options 里传的data

==============================================================

  1. Vue中的数据代理
    通过vm对象来代理data对象中的属性操作
  2. Vue中的数据代理好处
    更加方便的操作data中的数据
  3. 基本原理
    通过Object.defineproperty() 把data对象中的属性加到vm上
    为每一个添加到vm上的属性都指定 一个 getter/setter
    在getter/setter内部读写 data对应的属性
    ============================================================
    v-on:click 点击事件 简写 @click
    如果方法想要得到event, 方法调用必须加上$event 占位符

<button v-on:click="showInfo1"></button> // 不传参
<button v-on:click="showInfo2(66, $event)"></button> // 传参
showInfo方法 不能用箭头函数,否则this对象就会执行window
const vm = new Vue({
el: "#root",
data: {
name: "tom"
},
methods: {
showInfo1(event){
// console.log(event.target.innertext);
// console.log(this); // 此处的this是vm
// alert('同学你好');
},
showInfo2(number, event){
// console.log(event.target.innertext);
// console.log(this); // 此处的this是vm
// alert('同学你好');
}
}
})
=======================================================
a标签, 接收event对象,用 e.preventDefault() 阻止原有事件
<a href="http://www.baidu.com" @click="showInfo">点击</a>
showInfo(e) {
e.preventDefault();
alert("Hello");
}
也可以这么写
<a href="http://www.baidu.com" @click.prevent="showInfo">点击</a>
showInfo 方法里就不用写了

还有其他事件修饰符
@Click.prevent 阻止默认事件,点击a标签,链接就不会跳走
@Click.stop 阻止事件冒泡,事件不会被父元素捕获
@Click.once 事件只出发一次,点第一次弹窗,再点无效
@Click.capture 在之间捕获的时候处理,先捕获,再冒泡
@Click.self 只有当e.event.target 是当前元素的时候才触发事件,也能用于实现阻止冒泡事件
@Click.passive 事件的默认行为立即执行,无需等待回调执行
比如@Wheel 出发回调,回调执行很复杂允许,如果不加paasive,则也买你会出现卡顿(等回调执行完,滚动条才会滚动),加了passive 即使回调有复杂运算,也会立即滚动滚动条,同时复杂计算慢慢算去

小技巧:既阻止冒泡,又阻止默认事件,可以这么写
@Click.stop.prevent

滚动事件有两种写法
@Scroll
滚动条滚动事件,当滚动条滚到头就不再出发事件
@Wheel
即使滚动条滚到头,只要滚轮继续滚动,还能出发事件
==================================================================
键盘事件:
@keydown 按下去立即出发回调
@keyup 按下去松手再出发回调

<input type="text" @keyup="showInfo">

showInfo(e) {
// 通过e.keyCode 可以获取键盘编码
if(e.keyCode == "13") {
console.log("回车了");
}
}
上面捕获回车事件还可以这么些,不用再回调里判断keyCode
<input type="text" @keyup.enter="showInfo">
showInfo(e) {
console.log("回车了");
}
常用按键别名有enter delete(退格和删除) esc space tab up down left ritht
监听tab键更适合用keydown去监听,因为tab键会切换焦点,当keyup 的时候焦点已经切换到了别的元素
还有几个系统按键也应当使用keydown,ctrl,alt, shift, meta
举例:如果ctrl 使用keyup,如果按了Contrl C 那么等C键抬起来才会触发事件

不推荐使用按键编号 (如:@keyup.13) 去监听回车事件,因为有的键盘事件编号不统一,将来web协议会废弃,不支持
Vue 支持自己定义按键别名,比如定义回车:
Vue.config.keyCodes.huiche = 13;

小技巧:监听Contrl + y 事件可以这么写 ====> @keydown.ctrl.y

Vue 计算属性:用data里的属性去计算加工,产生一个新的属性叫做计算属性

插值语法调用方法必须要带小括号,表示调用方法,否则就会把方法本身打印到界面
事件语法里可以省略小括号

data 里的数据发生变化,模板要重新解析
所以某个方法A使用了data里的值,并且该方法A在插值语法里有调用,每次data数据发生变化,方法A必然会被调用

computed: {
fullName {
// 当有人读取fullname 时,get属性就会被调用,底层实现用的就是 Object.defineproperty里的getter
// get 方法调用时机
// 1. 初次读取fullname
// 2. 所依赖属性发生变化时
get() {
return this.FirstName + '-' + this.LastName; // 此处 this指向vm
},
// 修改fullname时候会被调用,如果fullname 确定不会被修改,就不用写set
set(value) {
...
}
}
// 简写形式
fullName() {
return this.FirstName + '-' + this.LastName;
}
}
fullName 也会绑定到vm对象上
假如界面上同时读取四次 fullName,fullName getter 方法也只会执行一次,fullname 值Vue 会帮我们缓存

计算属性比Method实现的优势

  1. 计算属性多次使用fullName 之会调用一次get方法,而method 执行多少次就调用多少次
    ======================================================================================
    @XXX绑定事件的时候,事件后面可以写简单语句去执行
    @XXX="YYY" YYY 可以是简单语句比如: isHot = !isHot; i ++

监视属性
watch: {
// 计算属性也可以被监视
isHot: {
// 初始化时rang handler调用一些
immediate: true,
// 当被监听属性isHot发生变化时,handler会被调用
handler(newvalue, oldvalue) {

    }
}

}
也可以在Vue 对象外面监视属性
vm.$watch('isHot', {
immediate: true,
handler(newvalue, oldvalue) {
}
});
// 深度监测
data: {
numbers: {
a: 1,
b: 2
}
},
// 监测多级属性 numbers 下面的a属性
watch: {
'numbers.a': {
// 初始化时rang handler调用一些
immediate: true,
// 当被监听属性isHot发生变化时,handler会被调用
handler(newvalue, oldvalue) {

    }
}

}
假如想监测整个numbers的变化,直接再watch写numbers: {...}是不起作用的,
因为这种情况下vue监测的是numbers对象的地址,除非numbers 重新被复制
正确的写法是开启deep属性
watch: {
numbers: {
// 当numbers下面任何属性发生变化都能监测到
deep: true,
// 当被监听属性isHot发生变化时,handler会被调用
handler(newvalue, oldvalue) {
}
}
}
如果不考虑deep 和 immediate 配置,watch 属性可以简写为
watch: {
isHot(newvalue, oldvalue) {
console.log(newvalue, oldvalue);
}
}
vm.watch 简写 vm.watch('isHot', function(newvalue, oldvalue) {
console.log(newvalue, oldvalue);
})

计算属性靠返回值完成任务,没法使用setTimeout,因为setTimeout 是把结果返回给了异步方法,而不是返回给了计算属性
Watch 可以完美的实现异步任务

setTimeout(() => {
console.log('123');
}, 1000)

定时器所指定的回调是由浏览器定时器管理模块控制的,到点了由JS引擎调用的,不是由Vue管理的

  1. computed能完成的watch 一定能完成,

  2. watch 能完成的computed不一定能完成,例如:watch 可以进行异步操作

  3. 所有被Vue管理的函数最好写成普通函数,这样的话this的指向才是vm

  4. 所有不被Vue管理的函数(定时器回调函数,ajax函数,promise的回调函数),最好写成箭头函数
    这样this的指向才是vm,可以访问vue属性
    比如定时器是由window 调用的,如果申明成普通函数,this一定指向window
    如果是箭头函数,则没有指定this,this向外寻找,找到了vm对象
    =============================================================
    vue style

  5. 绑定class样式
    适用于样式的类名不确定,需要动态绑定
    不变的样式还是跟在html里面一样
    变化的样式用:class来绑定,vue 会把变化和不变的样式组合到一起生效
    绑定字符串,场景:类名不确定,需要动态指定
    <div class="basic" :class="mod" @Click="changeMod"></div>
    绑定数组, 多个类组合到一起生效,场景:类名不确定,样式也不确定
    <div class="basic" :class="arr" @Click="changeMod"></div>
    绑定对象, 应用样式atguigu1, atguigu3,atguigu2 是false,所以不生效
    场景:类名确定,应用哪个需要动态决定
    <div class="basic" :class="classObj" @Click="changeMod"></div>

直接绑定内联样式
<div class="basic" :style="styleObj" @Click="changeMod"></div>
数组形式绑定内联样式
<div class="basic" :style="[styleObj, styleObj2]" @Click="changeMod"></div>
new Vue({
data: {
mod: "normal",
arr: ["atguigu1", "atguigu2", "atguigu3"],
classObj: {
atguigu1: true,
atguigu2: false,
atguigu3: true
},
styleObj: {
fontSize: '40px' // 所有有中划线的css属性名都要去掉中划线,使用驼峰命名
},
styleObj2: {
color: 'red';
}
},
methods: {
changeMod(){
this.mod = "happy"
}
}
})

================================================================
v-show 是通过display:none 来隐藏元素
后面的表达式不一定是true或false,能够解析为布尔值的表达式都可以
<div v-show="true">hello</div>

v-if 值为false直接会删除对应的dom节点,
同时还有v-else-if 和 v-else,跟js 里的if else 逻辑一模一样
要使用v-else-if 和 v-else,所有dom元素必须是连续的,中间不能打断插入别的元素
<div v-if="true">hello</div>

如果dom切换频率很高,则使用v-show,动态控制隐藏和显示,效率更高

想要同时控制多个元素的隐藏和显示可以使用 template 元素
template 元素渲染的时候会消失,不影响原来dom,但是template只能配合v-if 使用
<template v-if="true">
<h1>Angular</h1>
<h1>React</h1>
<h1>Vue</h1>
</template>
=======================================================
v-for
// 遍历数组
<ul>
// key 也可以绑定index,只要在ul里不重复就行
<li v-for="(person, index) in persons" :key="person.id">
{{p.name}} - {{p.age}}
</li>
</ul>

// 遍历对象
<ul>
// v 是属性值,p是属性名称
<li v-for="(v, p) in car" :key="p">
{{v}} - {{p}}
</li>
</ul>

// 遍历字符串
<ul>
// c是字符,index是下标
<li v-for="(c, index) in car" :key="index">
{{c}} - {{index}}
</li>
</ul>

// 遍历指定次数
<ul>
// i 是1,2,3,4,5 index 是下标,0,1,2,3,4
<li v-for="(i, index) of 5" :key="index">
{{i}} - {{index}}
</li>
</ul>

new Vue({
data: {
persons: [
{id: '001', name: '张三', age: 18}
{id: '002', name: '张五', age: 19}
{id: '003', name: '张六', age: 20}
],
car: {
name: "Audi A8",
price: "18 W",
color: "黑色"
},
str: "hello"
},
methods: {
}
})

=======================================================================
v-for key 的原理
// 遍历数组
<ul>
// key 也可以绑定index,只要在ul里不重复就行
<li v-for="(person, index) in persons" :key="person.id">
{{p.name}} - {{p.age}}
</li>
</ul>

new Vue({
data: {
persons: [
{id: '001', name: '张三', age: 18},
{id: '002', name: '张五', age: 19},
{id: '003', name: '张六', age: 20}
]
}
})

react-vue 循环渲染,key的作用

虚拟dom的作用,key是dom的标识,当数据发生变化时,vue 会根据新数据生成新的虚拟dom
新的虚拟dom和旧的虚拟dom的差异对比,比较规则如下:

  1. 如果旧的虚拟dom中找到了与新的虚拟dom相同的key
    如果虚拟dom内容没有变,则直接使用之前的真实dom
    如果虚拟dom内容变了,则生成新的真实dom,随后替换掉页面上之前的真实dom
  2. 如果旧的dom中没有找到与新的虚拟dom相同的key
    根据新的虚拟dom直接创建真实dom
  3. 如果旧的虚拟dom中的key在新的虚拟dom中不存在
    删除旧的虚拟dom对应的真实dom

用index下标作为key可能引发的问题:

  1. 如果对数据进行逆序添加,逆序删除等操作
    会产生没有必要的真实dom更新,界面没有问题,效率低下
    一旦结构中有输入类dom,会产生错误的dom更新,界面会有问题

如果使用v-for没有指定key,vue默认会把下标作为key

开发中选择key时,最好使用真实数据的唯一标识:比如数据主键
如果紧紧是对数据的渲染,不会逆序删除,添加数据,则使用 index 没有问题
=====================================================================
列表的过滤:

<ul>
<li v-for="(person, index) in filPersons" :key="person.id">
{{p.name}} - {{p.age}} - {{p.gender}}
</li>
</ul>

new Vue({
data: {
keyWord: ''
persons: [
{id: '001', name: '马冬梅', age: 18, gender: "女"},
{id: '002', name: '周冬雨', age: 19, gender: "女"},
{id: '003', name: '周杰伦', age: 20, gender: "男"},
{id: '003', name: '温兆伦', age: 20, gender: "男"}
]
},
computed: {
filPersons() {
return this.persons.filter(p => {
return p.name.indexOf(this.keyword) != -1;
});
}
}
})

=====================================================================
Vue 修改数据信息但是不会更新到界面的Demo

<ul>
<li v-for="(person, index) in persons" :key="person.id">
{{p.name}} - {{p.age}} - {{p.gender}}
</li>
</ul>
<button @Click="updateMei">更新第一条数据</button>

const vm = new Vue({
data: {
keyWord: ''
persons: [
{id: '001', name: '马冬梅', age: 18, gender: "女"},
{id: '002', name: '周冬雨', age: 19, gender: "女"},
{id: '003', name: '周杰伦', age: 20, gender: "男"},
{id: '003', name: '温兆伦', age: 20, gender: "男"}
]
},
methods: {
updateMei() {
// 下面修改方法会更新到页面,没有问题
this.persons[0].Name = "马老师";
this.persons[0].age = "34";
this.persons[0].gender = "男";
// 下面修改方法不生效,有问题
this.persons[0] = { name = "马老师", age = 34, gender = "男" }
}
}
})

Vue 监测数据改变原理分析

  1. 我们传入的data 会保存到 vm._data属性下面,具体流程如下:
    加工传入的data,给每个属性添加 gettter/setter
    加工后的data 赋值给vm._data
    加工之后有啥用?
    比如修改data 的name属性,就会引起set_name方法的调用
    set_name 调用就会重新解析模板生成新的虚拟dom,虚拟dom对比,更新真实dom
    ==========================================================
    简单模拟Vue监测data对象改变:

let data = {
name: "马老师"
}

// 创建一个监视的实例对象, 用于监视data中属性的变化
// Vue其实就是在setter方法里添加了动态更新页面的操作
const obs = new Observer(data);
let vm = {}
vm._data = data = obs

function Observer(obj) {
const keys = Object.keys(obj);

keys.foreach((k) => {
    Object.defineproperty(this, k, {
        get(){
            return obj[key]
        },
        set(val) {
            // 此处可以添加动态更新页面的操作
            console.log(k + "被修改了");
            obj[k] = val
        }
    })
})

}

明白了上面代码,假如在代码运行中新添加一个属性,那么这个属性是不是就没有被加工,没有添加getter/setter?答案是肯定的,
既然新添加属性没有被添加getter/setter,那么新添加属性改变就不会被更新到Dom上

那如果我就像在代码运行中给data 添加属性并且动态绑定到页面要咋整呢?
别急,Vue提供了专门的方法来干这事儿

Vue.set(vm._data.student, 'sex', '男') //
还可以写成vm.student,因为vm.student 这个方法就是通过数据代理绑定了vm._data.Student

这么写页面就能监测到属性变化了
还有另一个方法,vm对象的set方法: vm.set(vm._data.student, 'sex', '男')

注意!!!
在vue里面调用set 可以直接写
Vue.set(this.student, 'sex', '男')
或 Vue. $set(this.student, 'sex', '男')
注意2!!!
Vue 的set方法只能给data下面的某一个对象添加属性,不能直接给data添加属性

==================================================
Vue 的数组中的元素是没有添加getter/setter 来监视数组元素变化的
但是Vue 会监测以下方法调用,当以下方法调用了,vue认为你的数组就变了,并更新到页面
push最后面新增,
pop删除最后一个元素,
shift删除第一个,
unshift在前面加一个,
splice指定位置插入/删除/替换元素,
sort,
reverse反转
Vue是如何监测这些方法的呢,很简单,Vue 重写了这些方法,你在vue里面调用的push等这些方法其实是vue自己实现的
既然是自己实现的,那还不是想咋写就咋写

另一种实现方式是使用Vue.set, Vue 的set方法也可以操作数组
Vue.set(this.student.hobby, 1, '捣蛋')
Vue set方法操作了student 的hobby数组中下标为1的元素

直接替换整个数组也是可以的,因为数组属性也是普通属性,有getter/setter

!!! 数据劫持啥意思?
就是修改data中的某个属性,修改的动作会被setter 劫持到
================================================================
from 表单
假如想让点击label的时候,label后面的输入组件获得标签,
那就要通过for属性把labe和输入组件做绑定,操作如下
<label for="demo">账号:</label>
<input type="text" id="demo">

双向绑定radio 单选按钮的时候,radio 元素一定要设置value值,不然属性拿不到值

双向绑定CheckBox的时候默认绑定checked属性拿到true/false,
如果要绑定checkbox具体的值,必须指定value属性value
!!!绑定元素的初始值能够影响绑定model
假如绑定model一开始是bool值,后面只能接收check属性
如果绑定model一开始是数组,那么就会把勾选的value值加到数组里

表单提交绑定事件可以用 @submit来绑定,用prevent阻止默认事件
<form @submit.prevent="submit">
...
</form>

双向绑定接收数据默认是字符串,想要接收进来的数据是数字可以加.number,这么写,type=number 和 v-model.number 一般一起使用
年龄:<input type="number" v-model.number="age">

v-model.trim 可以接收输入时去掉前后空格

v-model.lazy 不是实时收集输入,等输入控件失去焦点的时候,才会更新绑定model

vue 过滤器
日期处理第三方包:moment.js, day.js
var fromatRes = dayjs(new Date()).format('yyyy-mm-dd');

1690780406197
// 无参过滤器
<h3>现在是:{{time | timeFormater}}</h3>
// 有参过滤器
<h3>现在是:{{time | timeFormater2('YYYY-MM-DD')}}</h3>
// 多个过滤器写法,先格式化日期,再截取前四位
<h3>现在是:{{time | timeFormater2('YYYY-MM-DD') | mySlice}}</h3>
// 动态绑定属性也可使使用过滤器
<h3 :x="msg | mySlice"></h3>
const vm = new Vue({
data: {
time: 1690780406197,
msg: '大哥你好, 哈哈哈'
},
filters: {
timeFormater(val) {
return dayjs(val).format('YYYY-MM-DD HH:mm:ss')
},
timeFormater2(val, str){
return dayjs(val).format(str)
},
mySlice(val) {
return val.slice(0, 4);
}
}
})

过滤器的功能也可以通过计算属性computed和methods来实现

全局过滤器实现,直接把过滤器注册到Vue全局,所有Vue组件都能调用
Vue.filter('mySlice', function(value){
return value.slice(0, 4);
})

过滤器只支持插值语法和属性绑定

v-text指令,向其所在标签插入text文本
v-html指令,支持html解析
<h3 v-text="title"></h3> // 输入任何值以文本形式展示到节点
<h3 v-html="htmlStr"></h3> // 会把html 解析到页面

const vm = new Vue({
data: {
title: '大哥你好, 哈哈哈',
htmlStr: '<h1>哈哈</h1>'
}
})

cookie 加上http only 属性,那么这个cookie 只能被http协议拿到,没法通过document.getCookie() 方法拿到

v-html 在页面直接渲染html 有可能会导致XSS攻击
一定在可信的内容渲染上使用v-html
用户输入的内容一定不要直接使用v-html 来渲染
=============================================================
js 阻塞
js 头文件如果加载延迟,那么后面的内容渲染,js代码指向都会被阻塞
直到js 头文件加载完成
如果把引入的js脚本放在body最后面去加载,则不会阻塞

v-cloak 标签用途
当网速过慢的时候,未经过解析的Vue模板会被展示到页面上
比如直接展示:{{name}} 这种插值语法
如果不想让页面闪一下,那么就使用v-clock

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

<h1 v-cloak>{{name}}</h1>

vue 执行之前由于插值元素加了v-cloak,所以会被display:none隐藏掉元素,
啥也不展示,当vue创建实例接管容器后,Vue会删除所有v-clock属性,一切有Vue接管,恢复正常
=================================================
v-once 指令
v-once所在的节点在初次动态渲染之后,就会被视为静态内容了
以后绑定之变后,不会再引起v-once所在节点的更新
<h2 v-once>N的初始值:{{n}}</h2>
<h2 >N的当前值:{{n}}</h2>
========================================================
v-pre指令
跳过所在节点的编译过程,
可以利用它跳过没有指令语法,没有使用插值语法的节点,加快编译
<button v-pre @Click="n++">点我n+1</button>
<h2 v-pre>{{name}}</h2>
=====================================================
Vue自定义属性
自己写一个v-big指令,
会把绑定的数值放大10 倍
自己写一个v-fbind,
和v-bind功能类似,到那时可以让所绑定的输入控件获得焦点

<div id="root">
<h2>当前值为:<span v-text="n"></span></h2>
<h2>放大十倍的值为:<span v-big="n"></span></h2>
</div>

const vm = new Vue({
el: "#root",
data: {
n: 10
},
directives: {
// big 函数调用时机:
// 指令与元素被成功绑定时调用一次(一上来就调用);
// 指令所在的模板被重新解析时调用
// element是真实dom元素
// binding是绑定对象
big(element, binding) {
console.log(this); // 此处的this是window
element.innerText = binding.value * 10;
},
// fbind 错误实现方式
fbindError(element, binding) {
element.value = binding.value;
element.focus();
// 上面的focus 会在页面打开时自动把焦点放到绑定元素上面吗???
// 答案是不会的,因为element是属于Vue模板,Vue模板经过解析后才会被真正渲染到页面上
// 当第一次打开页面,元素成功绑定意味着再内存里建立了指令与元素的绑定关系,整个模板还未被渲染,此时调用focus当然无效
},
// fbind 正确实现方式,不能简写,只能采用完整的指令申明方法
fbind:{
// 调用顺序:bind>inserted>update
// bind就是上面简写形式的方法
bind(element, binding){
element.value = binding.value;
},
// inserted 所在元素在被插入页面时调用
inserted(element, binding){
element.focus();
},
//指令所在模板在被重新解析时调用
update(element, binding){
}
}
}
})

总结:指令语法就是用于解析标签(包括:标签属性,标签内容,绑定事件...)
简写语法相当于同时写了:bind和update,没有写inserted
指令名称的坑,指令名称多个单词用中划线分隔,不能使用驼峰命名
指令定义则么写
'bind-number': function(element, binding){
}

定义指令的方法里的this都是指向window

定义全局指令:
Vue.directive('fbind', {
// 调用顺序:bind>inserted>update
// bind就是上面简写形式的方法
bind(element, binding){
element.value = binding.value;
},
// inserted 所在元素在被插入页面时调用
inserted(element, binding){
element.focus();
},
//指令所在模板在被重新解析时调用
update(element, binding){
}
});
===============================================================
vue 生命周期

  1. 生命周期回调函数是Vue在关键的时刻帮我们调用的一些特殊名称的函数
  2. 生命周期函数的名字不可更改,但是函数内容可以根据具体需求编写
  3. 生命周期函数中的this指向是vm,或 组件实例对象

--veu对象创建,先会去初始化生命周期,事件等,但是数据代理还没开始

===beforeCreate() {} 钩子函数
此时还不能访问methods 和 data,因为数据代理还没开始

--此阶段进行数据监测,和数据代理

===created() {} 钩子函数
此时可以通过vm访问到methods 和 data,因为数据代理已经完成

--此阶段Vue开始解析模板,生成虚拟DOM,页面还不能显示解析好的内容

===beforeMount() {}
页面呈现的是未经Vue编译的DOM结构 所有对DOM的操作都不奏效!!!(不奏效的原因是因为下一步插入真实DOM 会覆盖掉现在的所有操作)

--此阶段将内存中的虚拟DOM转为真实DOM插入页面,Vue会把虚拟DOM往Vue对象$el 属性上存一份

===mounted() {} 钩子函数
此时,页面中呈现的都是经过Vue编译的DOM
对DOM的操作均有效,直至此初始化过程结束,一般在此时进行:开启定时器,发送网络请求,订阅消息,绑定自定义事件等初始化操作

===beforeUpdate() {} 钩子函数
此时,数据是新的,但是页面是旧的,即:页面尚未和数据保持同步

--此阶段会根据新数据生成新的虚拟DOM,并且比较新旧虚拟DOM,并更新真实DOM,完成了Model -> View 的更新

===updated() {} 钩子函数
此时数据是新的,页面也是新的,页面和数据保持同步

===beforeDestory() {} 钩子函数
此时vm中的data,methods,指令等等都处于可用状态,即将要执行销毁操作
一般在这时候:关闭定时器,取消订阅消息,解绑自定义事件等等
在beforeDestory 再去操作数据,数据不会被更新到界面上,因为到了这个阶段,不会再走到 beforeUpdate 和 updated 方法里面去了

===destoryed() {} 钩子函数
没啥屌用
=====================================================================
Vue中清理定时器,定时器的Id不知道往哪儿放???放到外面有点不合适,其实可以放在vm对象上
比如在mounted上开启一个定时器:
mounted(){
console.log('mounted');
this.timerId = setInterval(() => {
this.n ++;
}, 16)
}
销毁的是就可以这么清理
beforeDestory() {
clearInterval(this.timerId);
}

======================================================================
Vue 组件
定义Vue组件不能写 el 配置项,因为最终所有的组件都要被一个vm管理,由VM决定服务于哪个模板
定义Vue组件时,data一定要写成函数式,返回一个data对象
为什么一定要这么写?
直接定义成data对象,那么多个地方引用组件时他们就共享同一份data,互相影响
写成函数式则不一样,每次被引用都是返回一个新的data对象,互相直接没有关联

使用组件拢共分三步:

  1. 创建组件
  2. 注册组件
  3. 调用组件

创建一个非单文件schoo组件
const school = Vue.extend({
template: <div> <h3>学习名称:{{schoolName}}</h3> <h3>地址:{{address}}</h3> </div>,
data() {
return {
schoolName: "英才",
address: "定西市安定区"
}
}
})
创建非单文件student组件
const student = Vue.extend({
template: <div> <h3>学生名称:{{name}}</h3> <h3>年龄:{{age}}</h3> </div>,
data() {
return {
name: "张三",
age: 18
}
}
})

<body>
<div id="root">
<h1>{{message}}</h1>
// 使用组件
<xuexiao></xuexiao>
<xuesheng></xuesheng>
</div>
</body>

new Vue({
el: "#root",
data: {
message: "hello"
},
// 注册组件
components: {
xuexiao: school,
xuesheng: student
}
})

全局注册组件,所有vm都可以使用
Vue.component("xuexiao", school)
Vue.component("xuesheng", student)
=======================================================
组件名称命名:多个单词,用中划线'my-school'
不要用html已经使用的元素名,如h2,div
可以在name属性指定组件在开发者工具中显示的名字
const student = Vue.extend({
name: '[name-in-dev-tools]'
})
简写 可以省略Vue.extend()
const student = {
name: '[name-in-dev-tools]'
}
Vue 自己会调用Vue.extend()
====================================================
嵌套组件

// 定义school的子组件student
const student = Vue.extend({
template: <div> <h3>学生名称:{{name}}</h3> <h3>年龄:{{age}}</h3> </div>,
data() {
return {
name: "张三",
age: 18
}
}
})

const school = Vue.extend({
template: <div> <h3>学习名称:{{schoolName}}</h3> <h3>地址:{{address}}</h3> <student></student> </div>,
data() {
return {
schoolName: "英才",
address: "定西市安定区"
}
},
components: {
student: student // 把student注册给school,成为school组件的子组件
}
})
创建非单文件student组件

<body>
<div id="root">
<h1>{{message}}</h1>
// 使用schoo组件,组件内部还有个student组件
<xuexiao></xuexiao>
</div>
</body>

new Vue({
el: "#root",
data: {
message: "hello"
},
// 注册组件
components: {
xuexiao: school
}
})
=========================================================
VueComponent

  1. school 组件本质是一个VueComponent的构造函数,不是程序员定义的,是由Vue.extend 生成的
  2. 我们只要写<school></school>,Vue解析时就会帮我们创建school组件的实例对象
    即Vue帮我们执行new VueComponent(options)
  3. 特别注意,每次调用Vue.extend, 返回的都是一个全新的VueComponent
  4. 关于this指向
    组件配置中,data,methods,watch,computed中的函数,它们的this均指向VueComponent示例对象
    new Vue 配置中的data,methods,watch,computed中的函数,它们的this均指向Vue实例对象
  5. VueComponent 的实例对象,以后简称VC
    Vue的实例对象,简称cm

vm 可以传入el,vc不行

function demo(){
this.a = 1
this.b = 2
}
const d = new Demo()

demo身上有个属性叫 prototype 显式原型属性
demo.prototype
d 身上有个属性叫 proto (隐式原型属性)
d.proto
demo.prototype 和 d.proto 都指向同一个原型对象

// 程序员通过显示原型属性,给demo追加一个x属性
demo.prototype.x = 99
d.proto.x 就能拿到值,这么写也能拿到d.x
=================================================
一个重要的内置关系,VueComponent.prototype.proto === Vue.prototype
为什么有这个关系:让组件实例对象vc可以访问Vue原型上的属性和方法
比如:Vue.prototype.x = 99
Vue下的组件school 也可以访问到x,vc.x
===================================================
单文件组件 以.vue结尾
.vue 文件可以被vue脚手架编译成js文件
文件结构
定义School组件
<template>
<div>
<h2>学习名称:{{schoolName}}</h2>
</div>
</template>
<script>
// 把school对象暴露出去,让别的地方可以调用
export default Vue.extend({
name: "School"
});

// 简写形式
export default {
    name: "School"
};

</script>
<style>
.demo {
background-color: red;
}
</style>

定义App.Vue 组件领导所有组件
<template>
<div>
<school></school>
</div>
</template>
<script>
import School from './School'
export default {
name: 'App',
components: {
School: School
}
}
</script>

创建main.js
import App from './App.vue'
new Vue({
el: '#root',
template: '<app></app>' // 把root div用APP替换掉
components: {
App: App
}
})

创建index.html

<body>
<div id="root"></div>
<script type="text/javascript" src="./js/vue.js"></script>
<script type="text/javascript" src="./main.js"></script>
</body>

以上代码放到vue脚手架里就可以运行

Vue 脚手架是Vue官方提供的开发平台
babel es6 => es5
eslint 语法检查

npm run serve 运行项目

脚手架结构:

new Vue({
render: h => h(App)
}).amount('#app'); 另一种写法 new Vue({ el: '#app' render: h => h(App) }); el切换路由其实就是调用destory销毁一个组件,再通过$amount 挂载一个新的组件

vue 主要包含两个部分

vue.js 与vue.runtime.xxx.js 的区别
vue.js 是完整版vue,包含: 核心功能+模板解析器
vue.runtime.xxx.js 是运行版本vue,只包含 核心功能

因为vue.runtime.xxx.js 没有模板解析器,所以不能使用tempplate配置项
需要使用render函数接收createElement函数去指定具体内容
=============================================================
vue 脚手架依赖webpack,所有配置都在webpack.config.js 中
想要查看配置,运行以下命令:
vue inspect > output.js,所有配置都会被生成到output.js 中
入口文件就在entry 下面配置

想要自定义配置,可以在根目录添加vue.config.js,可以调整脚手架工作模式
此配置最终有webpack接手,采用common.js 的暴露方式,因为webapck就用的是common.js

可以在vue.config.js 设置关闭语法检查
lintOnSave: false
================================================================
vue 获取dom元素
ref 用于给元素或者子组件注册引用信息
应用在html标签上获取的是真实的DOM元素,应用在组件标签上获取的是组件实例对象 VC
<template>
<h1 v-text="msg" ref="title"></h1>
<School ref="school"/>
</template>
<script>
export default {
name: "App",
components: {School},
data() {
return {
msg: "hello"
}
},
methods: {
showDom() {
// 获取h1 dom 元素
console.log(this.refs.title); // this.refs.school // 获取组件实例对象
}
}
}
</script>
============================================================

props 用于向子组件传参

父组件调用:
<Student name="马保国" :age="45" gender="男"></Student>
子组件:
<template>
<div>
<h1>{{name}}</h1>
<h1>{{age}}</h1>
<h1>{{gender}}</h1>
</div>
</template>
<script>
export default({
name: "Student",
data(){
return {
msg: "哈喽啊,大家好",
// name: "张三",
// age: 18,
// gender: "女"
}
},
// 外部调用传递进来
// props: ['name', 'age', 'gender']
// 对接收参数作类型限制
// props: {
// name: String,
// age: Number,
// gender: String
// }
// 更完善的写法如下,显示类型和是否必填
props: {
name: {
type: String,
required: true
},
age: {
type: Number,
required: false,
default: 20
},
gender: {
type: String,
required: false
}
}
})
</script>

====================================================================
props 是只读的
如果业务一定要改,先把传进来的属性赋值给,data属性,然后随便改
如下,myname 随便改
data(){
return {
msg: "哈喽啊,大家好",
myName: this.name
}
}
props: {
name: {
type: String,
required: true
}
}

=====================================================================
假如两个vue组件用了相同的配置 methods,可以通过以下方法实现代码复用
mixins 属性,就是用来复用配置的
假如混合传入一个x属性,组件本身也有一个x属性,那么就以组件本身为准
但是注意!!!!!
混合引入的生命周期钩子是 【都要!!!】两个都生效
写一个mixin.js

export const hunhe = {
methods: {
showName() {
alert(this.name)
}
}
}

使用的时候 引入
import {mixin} from '../mixin'

export default {
name: "school",
data(){
return {
name: "zhangsan"
}
},
// 此处的hunhe是外部引入的,下面的节点可以直接被当前组件使用
// 你在引入的hunhe里配置的data,methods,mounted 等任何节点都可以共用
mixins: [hunhe]
}
===============================================
全局引入混合:
在main.js 引入mixin.js,并注册

import main from './main.js'
Vue.mixin(hunhe)
以后项目下的所有子组件都会引入hunhe 里的配置
=================================================

插件就是一个包含instal方法的一个对象,install的第一个参数是Vue,
第二个及以后的参数是插件使用者传递的数据
新建一个plugins.js

export default {
install(Vue) {
// 注册全局过滤器
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
element.focus()
}
});
// 定义全局混入
Vue.mixin({
data() {
return {
x: 99
}
}
})

    // 给Vue全局上添加方法
    Vue.prototype.hello = () => {
        alert("你好啊");
    }
}

}

在main.js里引入并注册
import plugins from './plugins.js'

Vue.use(plugins)

=======================================================
组件样式类名如果冲突,后来的就会覆盖前面的
比如:先引入School, 再引入Student,那么Student的同名样式会覆盖School的
想要 样式不冲突,给style添加scoped属性,style 的样式之在当前组件生效
<style scoped>
...
</style>
实现原理:Vue给样式加了scoped属性的组件生成一个随机属性
用的时候会通过类名 + 随机属性来匹配样式
<div data-v-256434>...</div>
.demo-style[data-v-256434] {
...
}
!!!!!!App组件不应该加scoped
App里写的样式一般是项目里通用的,加上的话子组件就用不了了

========================================================
// 这么配置写样式可以用less, 不屑默认是css
less 可以嵌套着写,css不允许

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

=======================================================
平级组件之间传递参数
最初级方式:子组件 把数据传给父组件,父组件再把数据传给 另一个子组件
父组件App把addToDo方法传给子组件header
header 里的输入事件调用addToDo方法,往父组件添加数据
这里有个原则:数据在哪里,操作数据的方法就在哪里
toDoList 数据又传给+了子组件list
这样 子组件header 就把数据通过父组件header传给了自己的兄弟组件 list

父组件 App
<template>
<header :addToDo="addToDo"></header>
<list :toDoList=toDoList></list>
</template>
<script>
import header from './header'
import list from './list'
export default ({
data() {
return {
toDoList: []
}
},
methods: {
addToDo(toDoObj){
// 给数组前面加一个元素
this.toDoList.unshift(toDoObj);
}
}
})
</script>

子组件header
<template>
<input type="text" @keyup.enter="add"/>
</template>
<script>
export default ({
data() {
return {
toDoList: []
}
},
props: [addToDo]
methods: {
add(e) {
const toDoObj = {id: '001', title='e.target.value', done: false };
this.addToDo(toDoObj);
}
}
})
</script>

子组件list
<template>
<ul>
<li v-for="let toDoObj in toDoList" :key="toDoObj.id">{{toDoObj.title}}</li>
</ul>
</template>
<script>
export default ({
data() {
return {
toDoList: []
}
},
props: [toDoList]
methods: {
addToDo(toDoObj) {
// 给数组前面加一个元素
this.toDoList.unshift(toDoObj);
}
}
})
</script>

使用v-model时候切记,v-model 绑定的值不能是props 传递过来的,因为props是不可以修改的
如果props 传过来的是对象属性,v-model绑定其中的某一个属性是可以修改的,但是不推荐
=================================================

统计数据函数,reduce
pre 初始值为0,对象的done为true,统计+1
list.reduce((pre, current) => pre + (current.done ? 1 : 0), 0)

计算属性允许套娃,也就是说计算属性 可以用别的计算属性来计算

浏览器本地存储,能存储5M左右的字符串
localStorage: 浏览器关了再打开,存储的数据还在
localStorage.setItem("", "")
localStorage.getItem("")
localStorage.removeItem("")
localStorage.clear()
sessionStorage: 浏览器已关闭,存储数据就没了
sessionStorage.setItem("", "")
sessionStorage.getItem("")
sessionStorage.removeItem("")
sessionStorage.clear()
=============================================================
组件的自定义事件:
父组件实现
父组件使用Student组件,往Student组件身上绑定一个myevent事件,事件触发就调用testMethod方法
<Student v-on:myevent="testMethod"></Student>
// 只触发一次
<Student @myevent.once="testMethod"></Student>
methods: {
testMethod(name) {
console.log("收到name:" + name);
}
}

子组件
点击按钮调用emit来触发myevent事件 <button @click="btnClick">点击按钮触发myevent事件</button> methods: { btnClick(){ this.emit('myevent', this.name);
}
}


父组件还可以通过ref属性给子组件绑定事件
这种事件灵活性强,可以异步实现
<Student ref="student"></Student>

mounted: {
setTimeout(() => {
this.refs.student.on('myEvent', this.testMethod)
// 只触发一次
// this.refs.student.once('myEvent', this.testMethod)
}, 3000)
},
methods: {
testMethod(name) {
console.log(name);
}
}
================================================
解绑自定义事件:
Student 组件定义方法
methods: {
unbindEvent(){
this.off("myEvent"); // 解绑一个 this.off(["myEvent", "myEvent2"]); // 解绑多个
this.$off(); // 解绑所有
}
}

this.$destory() 方法调用后,组件和子组件会被销毁,Student的自定义事件全都不奏效,原生事件不受影响

=============================================
给属性绑定原生事件:
Student组件所在的div一点击就能触发click事件
不写.netive Vue默认click是组件自定义事件,点点点是没有作用的
这也是vue模板根元素只能由一个的原因之一,不然写了多个根元素,组件的原生click事件不知道绑给谁
<Student ref="student" @click.native="show"></Student>

==============================================
全局事件总线:可以实现任意组件间的通信
全局事件总线又以下要求
1. 全局访问
2. 可以调用 on,emit, off,once方法
所以,可以使用Vue原型对象来作为载体
实现方式如下:

  1. 在Main.js 安装全局事件总线

new Vue({
el: "#app",
render: h => h(App),
// 在Vue启动之之前在Vue的原型对象上 添加事件总线
// bus 就是当前应用的vm beforeCreate() { Vue.prototype.bus = this
}
})

组件School里面注册一个事件
mounted: {
this.bus.on('hello', (data) => {
console.log('我是school组件,收到了数据', data);
});
},
// 组件销毁之前,关闭hello事件监听
beforeDestory() {
this.bus.off('hello');
}

组件Student里触发事件,发送数据
methods: {
sendStudentName() {
this.bus.emit('hello', this.name);
}
}

====================================================

消息订阅与发布:

  1. 订阅消息:消息名
  2. 发布消息:消息内容

订报纸:

  1. 订阅报纸:住址
  2. 邮递员送报纸:报纸

安装pubsub.js库(publish,subscribe)
npm i pubsub-js

在School中订阅消息
import pubsub from 'pubsub-js'

mounted:{
this.pubId = pubsub.subscribe('hello', function(msgName, data) {
console.log('有人发布了hello消息', data);
})
}
// 销毁前取消订阅
beforeDestory() {
pubsub.unsubscribe(this.pubId);
}

在Student中发布消息
methods: {
publishStudentName() {
pubsub.publish('hello', 666)
}
}

==============================
判断tudo对象是否具有isEdit属性,用以下方法
todo.hasOwnProperty('isEdit')

有这样一个功能
点击编辑按钮,数据显示到一个输入框里,并且自动获取焦点

e.target.focus() 这句话不会生效!!!
为什么??? 因为showEdit改为true之后,界面上不会立即显示<input>框
要等到handelEdit方法执行完采取更新界面,那么e.target.focus() 在input显示出来之前就去获取焦点,肯定是失败的
可以使用timeout函数,正确的做法是用 nextTicknextTick 在下一次DOM更新结束后执行其指定的回调函数
什么时候用?当改变数据后,要基于更新后的DOM进行某些操作时,就要在netTick指定的回调函数中执行

<input v-show="showEdit" type="text" :value="todo.title" ref="inputTitle"/>

handelEdit(e){
this.showEdit = true;
// this.refs.inputTitle.focus(); // 不生效 // 等下一轮界面渲染完毕,再去执行nextTick里面的回调 // 所以,回调实在方法中执行完成,去渲染页面,页面显示了 input之后,就会调用focus() this.nextTick(function() {
this.$refs.inputTitle.focus();
})
}

======================================
Animate.js // 第三方动画库

npm install animate.css
import 'animate.css'

使用:
<transition-group
appear
name="animate__animated animate_bounce"
enter-active-class="animate__swing"
leave-active-class="animate__backOutUp"
>
<h1 v-show-"!isShow" key = "1">你好啊!</h1>
<h1 v-show="isShow" key="2">尚硅谷!</h1>
</transition-group>

Vue 封装的过渡与动画

  1. 作用:在插入,更新,或移除DOM元素时,在合适的时候给元素添加各种样式
  2. 进入:v-enter-active,退出:v-leave-active
  3. 写法:
    • 准备好样式
      • v-enter: 进入的起点
      • v-enter-active: 进入的过程
      • v-enter-to:进入的终点
    • 元素离开的样式
      • v-leave:退出的七点
      • v-leave-active:离开的过程
      • v-leave-to:离开的终点
    • 使用
      <transition name="hello">
      <h1 v-show="isShow">你好啊</h1>
      </transition>
    • 如果又多个元素需要过度,则需要使用<transition-group>,且每个元素都要指定key
      =======================================================================
      解决跨域问题,配置代理服务器

配置方式1:
在vue.config.js 中添加
devServer: {
// 代理服务器地址
proxy: 'http://localhost:5000'
}

使用的时候调用请求8080,代理服务器会把请求转发给5000

配置方式2:
请求8080/api/[path]
会转发给5000/[path]
devServer: {
proxy: {
'/api': {
target: 'http://localhost:5000',
// 转发的时候把API替换成空
pathRewrite: {'/api':''},
// 支持websocket
ws: true,
// 修改HttpRequest中的Host为被代理服务器Host
// 为true host是5000,为false,host为8080
changeOrigin: true
},
// 可以配置多个代理
// '/api2': {
// ...
// }
}
}

=======================================================================
引入静态资源
在src下创建assets/css文件夹
在assets/css下添加bootstrap.css

在App.vue中引入
// 用import方式引入css文件,假如css使用了不存在的资源,脚手架会做严格检查
import './assets/css/bootstrap.css'
// 想要不报错,可以换一种引入方式
在public创建css文件夹,在index.html中引入
<link res="stylesheet" href="<% BASE_URL %>css/bootstrap.css">

================================================================
在组件Category内部定义一个插槽,挖个坑,等待使用者填充

  1. 默认插槽
    <div>
    <h1>标题</h1>
    <slot>不给插槽传递东西,这句话就会显示</slot>
    </div>
    调用者给插槽传入一个图片,图片就会展示在组件插槽的位置上
    <category>
    <img src="XXX" alt="">
    </category>

  2. 具名插槽
    有多个插槽,需要指定插槽名称
    <div>
    <h1>标题</h1>
    <slot name="center"></slot>
    <slot name="bottom"></slot>
    </div>
    调用者需要需要传插槽名称
    <category>
    <img slot="center" src="XXX" alt="">
    <a slot="bottom" href="http://www.baidu.com">我是超链接</a>
    </category>
    调用插槽还有一种写法
    通过template包裹需要传入的东西,给template添加标签 v-slot:bottm

<category>
<img slot="center" src="XXX" alt="">
<template v-slot:bottm>
<div>
<a href="http://www.baidu.com">我是超链接</a>
</div>
<h4>欢迎光临</h4>
</template>
</category>

  1. 作用域插槽
    在组件定义一个插槽,不一样的地方在于插槽有个绑定参数,插槽会把参数传给调用者
    <div>
    <slot :games="games"></slot>
    </div>

调用者使用scope(名字随便起)变量接收插槽传过来的数据,然后渲染
<category>
<template scope="scope">
<ul>
<li v-for="(g, index) in scope.games" :key="index">{{g}}</li>
</ul>
</template>
</category>
也可以通过解构赋值获取games,这么写
<category>
<template scope="{games}">
<ul>
<li v-for="(g, index) in games" :key="index">{{g}}</li>
</ul>
</template>
</category>
!!!!!!!!作用域插槽也可以有名字,传入时指定名字即可

============================================================

vuex 是什么

用来对Vue中多个组件中共享的数据进行集中式的状态管理,也是一种组件间的通信方式

  1. 什么时候使用vuex?
    1. 当多个组件依赖同一个数据时候
    2. 来自不同组件的行为需要变更同一数据的时候

107 需要回看

安装vuex
npm -i vuex@3,Vue2 只能使用版本3 的vuex,Vue3 只能使用4版本的veux

创建在根目录store/index.js

// 该文件用于创建Vuex中最为核心的store
import Vue from 'vue'
import Vuex from 'vuex'

// 使用vuex插件
Vue.use(Vuex);

// 准备actions---用于响应组件中的动作,通过this.$store.dispatch()调用
const actions = {}
// 准备mutations---用于操作数据(state),通过this.$store.commit()调用
const mutations = {}
// 准备state---用于存储数据
const state = {}
// 用于将state中的值加工输出
const getters = {}

// 创建store
export default new Vuex.Store({
    actions,
    mutations,
    state,
    getters
})

在main.js 中引入vuex

import store from './store/index'
new Vue({
  el: "#app",
  render: h => h(App),
  store: store // 安装store
})

vuex使用流程

  1. 数据保存在state下
const state = {
    sum: 0
}
  1. 在组件中通过this.$store.dispatch('addWait', 2) 调用action中的方法

actions 中的方法可以写一些通用的业务逻辑,操作state数据的操作通过调用mutations 中的方法来完成
actions 中的方法还可以继续通过dispatch来调用其他的actions方法

const actions = {
    addWait(context, value) {
        settimeOut(() => {
            context.commit('add', value);
        }, 500);
    }
}
  1. 在actions中通过context.commit调用mutations 中的方法

mutations 中的方法用来直接修改state中的数据,一般不写业务逻辑
Vue开发者工具直接监听mutations,如果在其他地方修改state中的数据,开发者工具检测不到

const mutations = {
    add(state, value) {
        state.sum += value;
    }
}
  1. mutations修改完数据,模板会重新渲染
  2. getters 使用方法
    getter 接收一个state参数,可以把state中的数据做加工,然后输出
const getters = {
    bigSum(state) {
        return state.sum * 10;
    }
}

组件使用bigSum可以通过this.$store.getters.bigSum获取

mapState

获取store下的数据必须要写成 this.store.state.sum,this.store.state...
其中this.$store.state.可以通过mapState简化

import {mapState, mapGetters, mapMutations, mapActions} from 'vuex'

export default{
    // ...
    computed: {
        // mapState把state中的sum返回给计算属性he,使用的时候直接取this.he (对象写法)
        //...mapState({he: 'sum', xuexiao: 'school'})
        // 如果映射后的计算属性名跟state字段名称一样,可以用数组写法,更简便
        ...mapState(['sum', 'school'])
        // mapGetters同理,跟mapState一样一样的,自己实现
    },
    methods: {
        // 通过mapMutations生成mutations方法,
        // 注意,加入有参数要传递,调用生成方法jia的时候可以传参
        ...mapMutations({jia: 'sum', jian: 'sub'})
        // mapMutations 简写形式如mapSate
        // mapActions 同理,自己实现
    }
}

===========================================================================
vuex模块化编程

假如很多个业务的 actions,state,mutations,getters全部混在一起放在store里,代码将变得难以维护,vuex模块化编程支持把actions等以模块化分类,放到不同模块地下,这样编码和调用都将变得更有条理

  1. store 里面采用模块化编程
const personOptions = {
    namespaced: true,
    actions: {},
    mutations: {
        add(state, value) {
            state.sum += value;
        },
        sub(state, value) {
            state.sum -= value;
        }
    },
    state: {
        school: 'YC',
    },
    getters: {}
}

const countOptions = {
    namespaced: true,
    actions: {},
    mutations: {},
    state: {
        sum: 0,
    },
    getters: {
        mutiTen(state) {
            return state.sum * 10;
        }
    }
}

export default new Vuex.Store({
    modules: {
        person: personOptions,
        count: countOptions
    }
})
  1. 组件使用时:
    <h1>{{count.sum}}</h1>
    <h1>{{count.add(2)}}</h1>
computed: {
    ...mapState(['person', 'count']),

    // 想要直接取到count下state里面的sum,这么写
    // 同时要在store 里面countOptions 下面加上namespeaced: true !!!!!!!!!!!!
    ...mapState('count', ['sum']),
    // 用map获取getters里面的数据同state

    // 如果不想用map,可以这么写
    sum() {
        return this.$store.state.count.sum;
    }
    // 不用map获取getters里的数据,注意跟获取state不一样,要这么写
    bigSum() {
        return this.$store.getters['count/mutiTen']
    }

}
// ...
methods: {
    ...mapActions(['person', 'count'])
    // 想要直接取count下mutations里面的add,sub方法可以这么写
    // 同时要在store 里面countOptions 下面加上namespeaced: true !!!!!!!!!!!!
    ...mapMutations('count', {jia: 'add', jian: 'sub'})
    // 如果获取mutations里的方法不用重命名,直接这么写
    ...mapMutations('count', ['add', 'sub'])
    // 如果不想用map,可以这么写
    add() {
        this.$store.commit('count/add', 2)
    }
}

模块化编程可以把 上面例子中的count和person写到不同的js文件里面,在store/index.js 中引入再注册

=======================================================================================

vue 路由

  1. 安装
    vue3 对应 vue-router@4, vue2对应 vue-router@3

npm i vue-router@3

  1. 创建路由
    在根目录新建router/index.js文件
// 该文件用于创建应用的路由器
import VueRouter from 'vue-router'
import About from '../components/About'
import Home from '../components/Home'

export default new VueRouter({
    routes: [
        {
            path: '/about', 
            component: About
        },
        {
            path: '/home', 
            component: Home
        }
    ]
})
  1. 引入
    在main.js
import router from './router'

Vue.use(VueRouter)

new Vue({
    el: '#app',
    render: h => h(App),
    router: router
})
  1. 使用
  • 在页面中通过router-link 跳转路由
  • 在页面中通过router-view 指定组件呈现位置
<router-link class="..." active-class="路由激活时候的高亮样式" to="/about"></router-link>
<router-link class="..." active-class="路由激活时候的高亮样式" to="/home"></router-link>

<div class="panel-body">
    // 指定组件的呈现位置
    <router-view></router-view>
</div>

============================================

  1. 组件一般分为路由组件和普通组件,路由组件一般放在pages文件夹下,普通组件一般放在components文件夹下
  2. 通过切换,隐藏了的路由组件默认是被销毁了,需要的是再重新挂载
  3. 每个组件都有自己的$route属性,里面存储着自己的路由信息
  4. 整个应用只有一个router,可以通过组建的$router属性获得

=========================================================

子路由

  1. 子路由配置在父路由节点下的children下
  2. 子路由path前面不要加/
  3. router-link使用子路由的时候要带上父路由,比如:

<router-link active="active-class" to="/home/news">news</router-link>

export default new VueRouter({
    routes: [
        {
            path: '/about', 
            component: About
        },
        {
            path: '/home', 
            component: Home,
            children: [
                {
                    path: 'message',
                    component: Message
                },
                {
                    path: 'news',
                    component: News
                }
            ]
        }
    ]
})

=======================================================

路由传参

  1. 在路由组件里过query传参
// 在router-link里传参
<router-link to="/home/new/message?id=666&title=ddd">message</router-link>
// 动态传参,to 必须要用bind取绑定,to里面用模板字符串
<router-link :to="`/home/new/message?id=${m.id}&title=${m.title}`">message</router-link>

// 在message组件里接收参数
this.$route.query.id
this.$toute.query.title
  1. 对象传参
<router-link
    :to={
        path:'/home/message/detail',
        query: {
            id: m.id,
            title: m.title
        }
    }>
{{m.title}}
</router-link>

==========================================================================
命名路由

  1. 命名路由的作用就是简化路由跳转
  2. 使用
// 给路由命名
{
    path: '/demo',
    component: Demo,
    children: [
        {
            path: 'test',
            component: Test,
            children: [
                {
                    name: 'hello',
                    path: 'welcome',
                    component: Hello
                }
            ]
        }
    ]
}

// 简化跳转
完整路径写法
<router-link to="/demo/test/welcome">跳转</router-link>
简化写法
<router-link :to="{name: 'hello'}">跳转</router-link>

==============================================================

params 传参

  1. 在路由里配置参数
{
    path: 'message',
    component: Message,
    children: [
        {
            name: 'detail',
            path: 'detail/:id/:title',
            component: Detail
        }
    ]
}
  1. 跳转时携带参数
// 方式1
<router-link :to="`/message/detail/${m.id}/${m.title}`">详情<router-link>
// 方式2
<router-link :to="{
    name: 'detail', // 对象写法此处只能支持name!!!!!,不支持path
    params: {
        id: m.id,
        title: m.title
    }
}">
</router-link>
  1. 使用时获取参数
this.$route.params.id
this.$route.params.title

=================================================================

路由的props

  1. 第一种写法,传固定值(此写法用处极少,几乎没用。。。)

props 值为对象,对象中的key-value都会以props的形式传递给Detail组件

// 在route中配置
{
    path: 'message',
    component: Message,
    children: [
        {
            path: 'detail',
            component: Detail,
            props: {
                a: 1,
                b: 'hello'
            }
        }
    ]
}
// 在detail中通过props配置项接收参数
<div>
    <h1>a: {{this.a}}</h1>
    <h1>b: {{this.b}}</h1>
</div>
name: 'Detail',
computed: {
    // ...
},
props: ['a', 'b']
  1. 第二种写法,路由配置props为true

props 值为布尔值,若布尔值为真,就会把路由组件收到的所有params参数,以props的形式传递给Detail组件

// 在route中配置
{
    path: 'message',
    component: Message,
    children: [
        {
            path: 'detail/:id/:title',
            component: Detail,
            props: true
        }
    ]
}
// 在detail中通过props配置项接收参数
<div>
    <h1>a: {{this.a}}</h1>
    <h1>b: {{this.b}}</h1>
</div>
name: 'Detail',
computed: {
    // ...
},
props: ['a', 'b']
  1. 第三种写法,路由配置props为一个函数

函数里返回值返回的key-value会传递给detail组件的props

// 在route中配置
{
    path: 'message',
    component: Message,
    children: [
        {
            path: 'detail/:id/:title',
            component: Detail,
            props($route) {
                return {id: $route.query.id, title: $route.query.title}
            }
            // 可以用解构赋值简化为下面这种写法
            props({query: {id, title}}) {
                return {id, title}
            }
        }
    ]
}

// 在detail中通过props配置项接收参数
<div>
    <h1>a: {{this.a}}</h1>
    <h1>b: {{this.b}}</h1>
</div>
name: 'Detail',
computed: {
    // ...
},
props: ['a', 'b']

===========================================================
router-link 处理浏览器历史记录

  1. 模式1:push模式(默认)

每次新的浏览记录会被追加到历史记录里面

  1. 模式2:replace模式(需要手动开启)

每次产生的新的历史记录会替换上一条历史记录
<router-link replace></router-link>

啥时候用push,啥时候用replace???
比如有这么一个页面A,A里面有几个子路由B,C,D
假如想要A 进入时候保留历史记录,而B,C,D三个子路由不保留历史记录,后退直接到A,则可以这么设置
A 为push 模式,B,C,D为replace 模式

先点A,然后点B,C,D,点一下浏览器后退按钮,页面直接退回A,
因为B,C,D为replace模式,虽然点了三下,但是只保留了一条历史记录,一后退直接就回到A了
=====================================================================

编程式路由导航

methods:{
    // 用push模式跳转路由
    pushShow(m) {
        this.$router.push({
            path: '/home/message',
            query: {
                id: m.id,
                title: m.title
            }
        })
    },
    // 用replace模式跳转路由
    replaceShow(m) {
        this.$router.replace({
            path: '/home/message',
            query: {
                id: m.id,
                title: m.title
            }
        })
    },
    // 路由回退
    back(){
        this.$router.back();
    }
    // 路由前进
    forward(){
        this.$router.forward();
    },
    // go test
    goTest(){
        // 后退两步
        this.$router.go(-2);
        // 前进三步
        this.$router.go(3);
    }
}

===============================================================
缓存路由组件

  1. 路由组件切换默认是先销毁,再重新挂载新的路由组件
  2. 想要前一个组件不被销毁,而是隐藏起来,那就要设置<router-view>的缓存
// 当前router-view切换的所有组件都缓存起来,不销毁
<keep-alive>
    <router-view></router-view>
</keep-alive>
// keep-alive还可以指定特定组件缓存,其他销毁,此处只指定News组件缓存
<keep-alive include="News">
    <router-view></router-view>
</keep-alive>
// 指定缓存多个组件
<keep-alive :include="['News', 'Messages']">
    <router-view></router-view>
</keep-alive>

======================================================

路由组件独有的生命周期钩子

  1. activated: 路由组件激活会被调用
  2. deactivated: 路由组件失活会被调用

对于路由组件来说,如果被指定缓存了(keep-alive),那么组件的mounted 和 beforeDestory两个钩子组件就失效不会被调用了,这时候可以用activated和deactivated两个钩子函数替代

===========================================================

路由守卫

  1. 配置全局前置路由守卫
const router = new VueRouter({
    routes: [
        {
            path: '/about', 
            component: About
        },
        {
            path: '/home', 
            component: Home,
            children: [
                {
                    path: 'message',
                    component: Message
                },
                {
                    path: 'news',
                    component: News
                }
            ]
        }
    ]
})

// 全局前置路由守卫,初始化被调用,每次路由切换之前调用
router.beforeEach((to, from, next) => {
    // to: 要去的路由
    // from: 来的路由
    // next: 执行跳转,不调用next则停止跳转
    if(to.path === 'home/news' || to.path === 'home/messages'){
        if (localStorage.getItem('role') === 'admin'){
            next();
        } else {
            alert('没有权限!!!');
        }
    } else {
        next();
    }
})

export default router
  1. 配置route的元信息

路由配置里有个meta配置项,接收一个对象,配置好之后可以在this.$route 中拿到

// 比如配置当前路由是否需要授权
{
    path: '/userinfo', 
    component: UserInfo,
    meta: {
        isAuth: true
    }
}

在路由守卫中可以用meta元信息作判断
router.beforeEach((to, from, next) => {
    // 判断当前路由是否需要授权,如果需要授权,再验证权限
    if(to.meta.isAuth) {
        if (localStorage.getItem('role') === 'admin'){
            next();
        } else {
            alert('没有权限!!!');
        }
    } else {
        next();
    }
})
  1. 后置路由守卫: 初始化时和每次路由切换之后被调用
router.afterEach((to, from) => {
    // to: 要去的路由
    // from: 来的路由
    // 后置路由守卫没有next,因为路由已经切换完成,没有接下来的操作了
    // 比如路由切换完成后去修改页面title信息 (title 配置在路由元信息中)
    document.title = to.meta.title;
})
  1. 独享路由守卫:某一个路由单独使用的路由守卫
{
    path: '/userinfo', 
    component: UserInfo,
    meta: {
        isAuth: true
    },
    beforeEnter(to, from, next){
        // 内部逻辑只对userinfo这个页面产生作用
        // 独享路由守卫只有前置,没有后置
    }
}
  1. 组件内路由守卫

组件内部实现的路由守卫,在路由进入和离开时会调用

mounted() {
    // ...
},
// 通过路由规则,进入该组件时被调用
// 假如当前组件作为子组件直接加载到页面上,不是通过路由规则进入到组件,beforeRouteEnter 不会被调用
// afterRouteLeave同理,必须是通过路由规则进入组件,在离开时才会被调用
beforeRouteEnter(to, from, next) {
    // 可以写鉴权逻辑
    // 此处的to时当前组件的route对象
},
// 通过路由规则,离开该组件时被调用
afterRouteLeave(to, from, next) {
    // 此处的to是离开当前页面要去的路由对象
    // from是当前组件的路由对象
}

beforeRouteEnter 和 afterRouteLeave 不与前置和后置路由守卫对应
前置后置路由守卫是在页面切换之前和之后调用,一旦页面切换,必然会先调用前置路由守卫,再调用后置路由守卫
beforeRouteEnter 是在进入页面时被调用,如果停留在页面上,afterRouteLeave 不会被调用,只有在跳走之前才会调用 afterRouteLeave
===============================================================================
vue 路由的hash工作模式和history工作模式

  1. hash 哈希模式路劲里带有#, 对于浏览器来说往后端发送路劲只会发送#之前的,比如:
    浏览器发送路由:http://www.test.com/userinfo/test1/#/sdfs/sdfsd/sdf
    后端收到的只是#前面的部分:http://www.test.com/userinfo/test1/,#后面的部分不会发送给后端
  2. history模式
    vue路由模式默认是hash模式,history模式需要手动开启
const router = new VueRouter({
    mode: history,
    routes: [
        {
            path: '/about', 
            component: About
        },
        // ...
    ]
})
  1. hash模式和history模式的区别
  • hash模式
    • 路由中永远带着#,不美观
    • 若以后将地址通过手机第三方app分享,若app严格校验,则地址会被标记为不合法
    • 兼容性好
  • history模式
    • 路由中没有#,显得美观
    • 兼容性跟hash模式相比略差
    • 应用部署上线时,需要后台服务器支持,比如IIS,nginx另外作配置,否则页面刷新会出现404问题
      • ??? 为啥刷新会出现404,因为vue是单页面应用,路劲只是用于vue app内部作路由跳转
      • history模式路由中没有#,页面刷新浏览器会把整个路由发送给后台,后台找不到对应的路劲,自然会被404
      • 后台服务器可以作相关配置,把该地址的路由都认为是静态路由,不返回404
        ==========================================================================

vue UI组件库

  1. 移动端
    • Vant
    • Cube UI
    • Mint UI
  2. PC 端
    • element UI
    • Iview UI
      组件库可以按需引入,也可以完整引入,具体怎么引入,自己研究官方文档!!!

===============================================================================

Vue3

  1. Vue3 带来了什么
    • 性能提升
      • 打包速度,内存减少,初次渲染速度
    • 源码升级
      • 使用Proxy代替defineProperty实现响应式
      • 重写虚拟dom的实现和Tree-Shanking
    • 拥抱TypeScript
    • 新特性
  2. vue 3 不在通过Vue对象创建APP
// 引入工厂方法,用于创建应用实例对象
import { createApp } from 'vue'
// 根模块
import App from './App.vue'

// 先创建 app实例对象,然后把对象挂载到index.html的 app容器上
createApp(App).mount('#app')

  1. vue 3 的模板结构可以没有根标签
  2. vue 3 的setup

setup 是vue3 的一个新的配置项,数据,方法,生命周期钩子等都要写在setup里

export default {
    name: 'App',
    setup() {
        let name = 'zhangsan';
        let age = 9;

        function sayHello() {
            alert(`I'm ${name} and ${age} years old`)
        }
        
        // 返回一个正常对象,对象里的属性和方法可以被模板直接使用
        return {
            name,
            age,
            sayHello
        }

        // 2. 返回一个函数,函数会渲染一个h1 替换模板的内容
        return () => h('h1', 'hunter') 
    }
}

=================================================================

vue 3 ref 函数

  1. vue 3 setup中申明的变量默认不是响应式的,变量变化不会引起页面的变化,想要让变量成为响应式变量,就要加上ref
setup() {
    // 加了ref的变量就变成了一个 引用对象(refrence implement)
    let name = ref('张三')
    let age = ref(18)
    let job = ref({
        type: '前端工程师',
        salary: '18k'
    })

    // 修改引用变量
    function changeInfo(){
        name.value = '李四'
        age.value = 19
        job.value.type = '后端工程师'、
        job.value.salary = '20k'
    }

    // 在模板里面读取变量不用.value,vue3 解析发现对象是ref对象的时候会自动加上.value
}
  1. ref 函数处理基本数据类型,使用的是Object.defineProperty实现数据劫持,最后实现相响应式
  2. ref 函数处理对象或数组数据,使用的是Proxy来实现响应式,Proxy 操作封装在了一个名字叫reactive的函数中;所以处理对象或者数组建议直接使用reactive函数
  3. 用ref处理基本类型,修改数据永远都要用.value来访问,比较麻烦,推荐用下面写法
setup() {
    // 推荐写法!!!
    let person = reactive({
        name: '张三',
        age: 18,
        job: {
            type: '前端工程师',
            salary: '18K'
        }
    })

    // 在模板里面读取变量不用.value,vue3 解析发现对象是ref对象的时候会自动加上.value
    return {
        person
    }
}
  1. reactive 总结
    • 作用:定义一个对象类型的响应式数据(基本类型还是使用ref)
    • 语法:const 代理对象 = reactive(源对象),接收一个对象,返回一个代理对象(Proxy的实例对象)
    • reactive 代理的响应式数据是深层次的
    • 内部基于es6的proxy实现,通过代理对象操作源对象内部数据进行操作
      ===========================================================================

vue 2 响应式原理总结

  1. 实现原理

    • 对象类型:通过Object.defineProperty()对属性的读写进行劫持
    • 数组类型:通过对数组的一系列方法进行拦截(对数组的操作方法进行了包裹)
  2. 存在的问题

    • 新增删除属性界面不会更新,需要用Vue.set方法绑定新属性,Vue.delete删除一个存在的属性
    • 直接通过下标修改数组,界面不会自动更新
      =================================================================================
      vue 3 的响应式
  3. 可以直接新增、删除对象属性,可以直接修改数组元素,不需要使用vue 的set,delete 方法

  4. 代码实现

let person = {
    name: '张三',
    age: 18
}

// 任何对p的操作都会修改person对象,p是对person的代理
cosnt p = new Proxy(person, {
    // 读取属性时会被调用
    // target 是person对象
    // propName 是读取的属性名称
    get(target, propName) {
        return target[propName]
    },
    // 增加或者修改属性时会调用
    // target 是person对象
    // propName 是读取的属性名称
    // value 是传入的修改值
    set(target, propName, value) {
        target[propName] = value
    },
    // 删除属性的时候会调用
    delete(target, propName) {
        return delete target[propName]
    },
})
  1. es6 反射
let obj = {a: 1, b: 2}
// 反射读取属性
Reflect.get(obj, 'a')
// 反射修改obj属性
Reflect.set(obj, 'a', 110)
// 反射删除obj属性
Reflect.deleteProperty(obj, a)

// Reflect 也有defineProperty
// res 时布尔值,返回是否成功
const res = Reflect.defineProperty(obj, 'c', {
    get(){
        return 3
    }
})
  1. vue 3操作Proxy对象其实用的是Reflect
let person = {
    name: '张三',
    age: 18
}

// 任何对p的操作都会修改person对象,p是对person的代理
cosnt p = new Proxy(person, {
    // 读取属性时会被调用
    // target 是person对象
    // propName 是读取的属性名称
    get(target, propName) {
        // return target[propName]
        return Reflect.get(target, propName)
    },
    // 增加或者修改属性时会调用
    // target 是person对象
    // propName 是读取的属性名称
    // value 是传入的修改值
    set(target, propName, value) {
        // target[propName] = value
        Reflect.set(target, propName, value)
    },
    // 删除属性的时候会调用
    delete(target, propName) {
        // return delete target[propName]
        return Reflect.deleteProperty(propName)
    },
})

对比reactive和ref

  1. 从数据角度对比
    • ref 用来定义基本类型数据
    • reactive 用来定义对象类型数据
    • 备注:ref也可以用来定义对象类型数据,它内部会求助reactive转换为代理对象
  2. 从原理角度对比
    • ref 通过 Object.defineProperty() 的get、set来实现响应式
    • reactive内部通过Proxy来实现响应式,通过Reflect来对对象进行操作
  3. 从使用角度对比
    • ref 定义的数据:操作时需要用.value,读取数据时候,模板中直接读取,不需要.value
    • reactive 定义的数据:操作于读取数据都不需要.value

Vue3 setup 注意点

  1. setup执行时机
    • 在beforeCreate之前执行一次,this时undifined
  2. set的参数
    • props:值为对象,包含:组件外部传递过来,且组件内部申明接收
    • context上下文:
      • attrs: 值为对象,包含组件外部传递过来,但是没有在props中申明接收的属性,相当于this.$attrs
      • slots: 收到的插槽内容,相当于 this.$slots
      • emit: 分发自定义事件的函数,相当于 this.$emit
  3. setup接收参数
export default{
    name: 'Demo',
    props: ['msg', 'school'],
    // 申明自定义事件,申明后父组件才可以绑定
    emits: ['sayHello']
    // props 接收父组件传递的参数,
    // context 上下文对象,包含:attrs, emit, slots
    setup(props, context){

    }
}

Vue3 计算属性

import {reactive, computed} from 'vue'
export default{
    name: 'Demo',
    // vue2 写法
    //computed: {
    //  fullName() {
    //      return `${this.person.firstName} - ${this.person.lastName}`;
    //  }
    //},
    setup(props, context){
        let person = reactive({
            firstName: '张',
            lastName: '三'
        })
        //vue3 计算属性
        person.fullName = computed(() => {
            return `${this.person.firstName} - ${this.person.lastName}`
        })
        //vue3 计算属性完整写法,考虑读和取
        person.fullName = computed({
            get() {
                return `${this.person.firstName} - ${this.person.lastName}`
            },
            set() {
                const nameArr = value.split('-')[0]
                person.firstName = nameArr[0]
                person.lastName = nameArr[1]
            }
        })
        return {
            person
        }
    }
}

vue3 watch属性

import {ref, watch, reactive} from 'vue'
export default{
    name: 'Demo',
    // vue2 写法
    //watch: {
    //  sum: {
    //      // 立即监听
    //      immediate: true,
    //      // 深度监视
    //      deep: true,
    //      handler(newValue, oldValue) {
    
    //      }
    //  }
    //},
    setup() {
        let sum = ref(0)
        let msg = ref(msg)
        let person = reactive({
            name: '张三',
            age: '18',
            salary: {
                j1: '30K'
            }
        })

        // 1. 监视ref定义的响应式数据
        watch(sum, (newValue, oldValue) => {
            console.log('sum变了', newValue, oldValue);
        })
        // 2. 监视ref定义的多个响应式数据
        // newValue 和 oldValue都是数组
        watch([sum, msg], (newValue, oldValue) => {
            console.log('监视数据变了', newValue, oldValue)
        }, {immediate: true})
        // 3. 监听reactive对象定义的响应式数据,此处无法正确的获得newvalue和oldvalue
        // 强制开启了深度监视,deep配置无效
        watch(person, (newValue, oldValue) => {
             
        })
        // 4. 监听reactive对象的某个属性
        // 必须通过函数返回一个属性,直接写person.age无效
        watch(() => person.age, (newValue, oldValue) => {
            console.log('age 变了', newValue, oldValue)
        })
        // 5. 监听reactive对象的多个属性
        // 必须通过函数返回一个属性,直接写person.age无效
        watch([() => person.age, () => person.name], (newValue, oldValue) => {
            console.log('属性 变了', newValue, oldValue)
        })
        // 特殊情况:监听reactive对象中的某个属性,这个属性是一个对象,这时候deep属性会生效
        watch(() => person.salary, (newValue, oldValue) => {
            console.log('属性 变了', newValue, oldValue)
        }, {deep: true}) 


        return {
            sum,
            msg,
            person
        }
    }
}

vue3 watchEffect

import {ref, watch, reactive, watchEffect} from 'vue'
export default{
    name: 'Demo',
    setup() {
        let sum = ref(0)
        let msg = ref(msg)
        let person = reactive({
            name: '张三',
            age: '18',
            salary: {
                j1: '30K'
            }
        })

        watch(sum, (newValue, oldValue) => {
            console.log('sum变了', newValue, oldValue);
        })

        // watchEffect 不用指明监视属性
        // 用谁就监视谁,如果sum或者person.salary.j1的值变了,watchEffect就会被调用
        // 与computed有点像,computed必须有返回值,watchEffect更注重逻辑
        watchEffect(() => {
            const x1 = sum.value;
            const x2 = person.salary.j1
        })

        return {
            sum,
            msg,
            person
        }
    }
}

vue3 生命周期、

  1. 有两个钩子函数改名了
    • beforeDestory => beforeUnmount
    • destoryed => Unmounted
  2. vue2 中的钩子函数在vue3 中还可以以配置的方式使用,也支持在setup中通过组合式函数使用钩子函数
    • 组合式钩子函数名称都是在原来函数名称前面加个on
    • beforeCreate 和 created没有对应的组合式钩子函数
    • vue3 认为setup方法就相当于 beforeCreate + created
    • 如果同时写了组合式API的钩子函数 和 配置项钩子函数,那么组合式API的先调用
      • 比如:setup > beforeCreate > created > onBeforeMounte > beforeMounte > onMounted > mounted ...

vue3 自定义hook函数

  1. 有如下需求:鼠标点击时输出鼠标的坐标
import {ref, reactive, onMounted} from 'vue'
export default {
    name: 'Demo',
    setup() {
        let point = reactive({
            x: 0, 
            y: 0
        })

        function savePoint(event) {
            point.x = event.pageX
            point.y = event.pageY
        }

        // 添加window窗体监听事件
        onMounted(() => {
            window.addEventListener('click', savePoint)
        })

        // 组件卸载前移除 window窗体监听事件
        onBeforeUnmount(() => {
            window.removeEventListener('click', savePoint)
        })

        return {point}
    }
}
  1. 假如挂载函数、卸载函数代码想要提取出来供大家调用,可以这么写
  • 在根目录下创建/hooks/usePoint.js文件
  • 把代码放进去
import {reactive, onMounte, onBeforeUnmounted}
export default function() {
    let point = reactive({
        x: 0, 
        y: 0
    })

    function savePoint(event) {
        point.x = event.pageX
        point.y = event.pageY
    }

    // 添加window窗体监听事件
    onMounted(() => {
        window.addEventListener('click', savePoint)
    })

    // 组件卸载前移除 window窗体监听事件
    onBeforeUnmount(() => {
        window.removeEventListener('click', savePoint)
    })

    return point
}
  • 其他地方引入上面定义的文件
import usePoint from '../hooks/usePoint'
export default {
    name: 'Demo',
    setup() {
        let point = usePoint()

        return {point}
    }
}
  1. 总结:
    • 什么是hook:本质是一个函数,把setup函数中的CompositionAPI进行了封装
    • 类似于vue2中的mixin
    • 自定义hook的优势:复用代码,让setup可读性更好

toRef 和 toRefs

  1. 作用:创建一个ref对象,其value指向另一个对象
  2. 语法:const name = toRef(person, 'name')
  3. 应用:要将响应式中的某个属性单独提供给外部使用
  4. 扩展:toRefs 与 toRef功能一致,但是可以批量创建ref对象,语法toRefs(person)
import {reactive, toRef, toRefs} from 'vue'
export default{
    name: 'Demo',
    setup() {
        let person = reactive({
            name: '',
            age: 18,
            job: {
                j1: {
                    salary: 20
                }
            }
        })

        // toRef让name2变量指向 person.name,name2修改会影响 person.name
        const name2 = toRef(person, 'name')
        const salary = toRef(person.job.j1, 'salary')

        // 批量处理person的所有属性,功能根ref一样
        const x = toRefs(person)

        return {
            name2,
            ...x
        }
    }
}

其他Composition API

  1. shallowReactive 浅层次响应式
    • 何时使用:当对象只有最外层的属性的时候使用
// shallowReactive 只考虑第一层属性的响应式,下面的salary 不支持响应式
let person = shallowReactive({
    name: '',
    age: 18,
    job: {
        j1: {
            salary: 20
        }
    }
})
  1. shallowRef 传入基本类型的话根ref没有区别,但是shallowRef 不会处理对象类型的响应式
    • 何时使用:如果后续功能不会修改对象的属性,而是用新生对象来代替,就使用shallowRef

readonly 和 shallowReadonly

  1. 作用:让对象只读
  2. 语法:person = readonly(person)
  3. 场景:不希望数据被修改时
return default{
    name: 'Demo',
    setup(){
        let person = reactive({
            name: '',
            age: 18,
            job: {
                j1: {
                    salary: 20
                }
            }
        })

        // 限制一个对象的所有层次的属性只读
        person = readonly(person)
        // 只对第一层对象限制只读
        person2 = shallowReadonly(person)
    }
}

toRaw & markRaw

  1. toRaw
    • 作用:把一个reactive生成的响应式对象变成普通对象
    • 场景:用于读取响应式对象的普通对象,对这个普通对象的所有操作都不会引起页面更新
    • 语法:
  2. markRaw
    • 作用:用于标记一个对象, 使其用于不会再成为响应式对象
    • 场景:
      • 有些值不应该被设置为响应式的,例如复杂的第三方类库
      • 当渲染具有不可变数据的大列表时,跳过响应式转换,可以提高性能
export default {
    name: 'Demo',
    setup() {
        let person = reactive({
            name: '张三'
        })
        // 把响应式对象转换成普通对象
        const p = toRaw(person)

        function addCar(){
            let car = {name: 'benchi', price: 40}
            // 如果直接写 person.car = car,那么car就是一个响应式对象
            // 用markRaw把car变成普通对象
            person.car = markRaw(car)
        }
    }
}

customRef

  1. 作用: 创建一个自定义Ref, 并对其依赖项和更新触发进行显示控制
  2. 案例:实现防抖效果
// ref 相当于精装房,customRef相当于毛坯房,需要自己实现一些东西
import {ref, customRef} from 'vue'
export default {
    name: 'App',
    setup() {
        function myRef(value, delay) {
            let timer = null
            return customRef((track, trigger) => {
                return {
                    get() {
                        // 通知vue追踪value的变化
                        // 也就是提前和getter商量一下,让他认为这个value是有用的,当值变化时读取新值
                        track() 
                        return value
                    },
                    set(nweValue) {
                        clearTimeout(timer)
                        value = newValue // 修改value
                        timer = setTimeout(() => {
                            trigger() // 通知Vue重写解析模板,模板会调用get方法读取新值
                        }, delay)
                    }
                }
            })
        }
        let keyWord = myRef('hello', 500)
        return {keyWord}
    }
}

provide 与 inject

  1. 作用:适用于祖孙组件之间通信
  2. 套路:父组件有一个provide选项来提供数据,子组件
  3. 用法:
// 祖先组件
import {provide} from 'vue'
export default {
    name: 'app',
    setup() {
        let car = reactive({name: 'BYD', price: 40})
        // 通过provide把数据提供给后代组件
        provide('car', car)
    }
}

// 后代组件
import {inject} from 'vue'
export default {
    name: 'Child',
    setup() {
        // 后代组件通过inject获取父组件提供的对象
        let x = inject('car')
    }
}

响应式数据判断方法

  • isRef:检查一个值是否为一个ref对象
  • isReactive:检查一个对象是否是由reactive创建的响应式对象
  • isReadonly:检查一个对象是否是由readonly创建的只读对象
  • isProxy:检查一个对象是否是由reactive 或者 readonly方法创建

Vue2 中的API为配置式(Options API),Vue3中的API为组合式(Composition API)

  • Options API:
    • 新增或者修改需求,需要分别在data,methods,computed中修改功能对应的代码
    • 同一个功能的代码时分别放到data,methods,computed等模块中,代码结构不合理
  • composition API:
    • 同一个功能相关的data,methods,computed方法都写在一起,代码结构更合理
    • hook函数功能强大,提高代码复用

Fragment

  • Vue2 中必须要有根标签
  • Vue3 中可以没有根标签,内部多个标签会包裹在一个Fragment虚拟元素中
  • 好处:减少元素层级
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,657评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,662评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,143评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,732评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,837评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,036评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,126评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,868评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,315评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,641评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,773评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,859评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,584评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,676评论 2 351

推荐阅读更多精彩内容