vue2-3学习笔记

一、初始Vue

动态构建用户界面的渐进式JavaScript框架

1.1 创建Vue实例

  1. 想让Vue工作,就必须创建一个Vue实例,且要传入一个配置对象;

  2. {{xxx}}中的xxx要写js表达式,且xxx可以自动读取到data中的所有属性;

  3. 一旦data中的数据发生改变,那么页面中用到该数据的地方也会自动更新;

注意区分:js表达式 和 js代码(语句)

​ 1.表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方:

​ (1). a

​ (2). a+b

​ (3). demo(1)

​ (4). x === y ? 'a' : 'b'

​ 2.js代码(语句)

​ (1). if(){}

​ (2). for(){}

html 复制代码
<!-- 准备好一个容器 -->
	<div id="demo">
		<h1>Hello,{{name.toUpperCase()}},{{address}}</h1>
	</div>
javascript 复制代码
//关闭VUe的生产提醒 vue2.x才需要关闭 vue3.x 不需要
Vue.config.productionTip = false
// 创建Vue实例
	new Vue({
		el:'#demo', //el用于指定当前Vue实例为哪个容器服务,值通常为css选择器字符串。
		data:{ //data中用于存储数据,数据供el所指定的容器去使用,值我们暂时先写成一个对象。
			name:'atguigu',
			address:'北京'
		}
	})

1.3 模板语法

Vue模板语法有2大类:

  1. 插值语法:

    ​ 功能:用于解析标签体内容

    ​ 写法:{{xxx}}, xxx是js表达式,且可以直接读取到data中的所有属性

  2. 指令语法:

    ​ 功能:用于解析标签(包括:标签属性、标签体内容、绑定事件...)。

    ​ 举例:v-bind:href="xxx" 或 简写为 :href="xxx",xxx同样要写js表达式,且可以直接读取到data中的所有属性。

    ​ 备注:Vue中有很多的指令,且形式都是:v-???,此处我们只是拿v-bind举个例子。

1.4 数据绑定

单项数据绑定

  1. 语法: v-bind:href="xxx"或简写成 :href
  2. 特点: 数据只能从data流向页面

双向数据绑定

  1. 语法: v-model:value="xxx"或简写为v-model="xxx"
  2. 特点:数据不仅能从data流向页面,还能从页面流向data

备注:

​ 1.双向绑定一般都应用在表单类元素上(如:input、select等)

​ 2.v-model:value 可以简写为 v-model,因为v-model默认收集的就是value值。

1.4 el与data的两种写法

  1. el有两种写法

    1. .new Vue是后配置el属性
    2. 先创建Vue实例,随后再通过 vm.$mount('#root')指定el的值
  2. data的两种写法

    1. 对象式
    2. 函数式
  3. 一个重要的原则:

    ​ Vue管理的函数,一定不要写箭头函数,一旦写箭头函数,this就不是Vue实例

1.5 MVVM 模型

  1. M:模型(Model) :对应data中的数据
  2. V: 视图(View) : 模板(展示model的数据)
  3. VM: 视图模型(ViewModel):Vue 实例对象(连接model和view层实现双向绑定和更新识图)

1.6 事件的基本使用

1.61 事件的基本使用

使用v-on:xxx 或 @xxx 绑定事件,其中xxx是事件名

事件的回调需要配置在methods对象中,最终会在vm上;

注:

  1. methods中配置的函数,不要用箭头函数!否则this就不是vm了;
  2. methods中配置的函数,都是被Vue所管理的函数,this的指向是vm 或 组件实例对象;
  3. @click="demo" 和 @click="demo($event)" 效果一致,但后者可以传参;

1.6.2 事件修饰符

  • prevent:阻止默认事件(常用);
  • stop:阻止事件冒泡(常用);
  • once:事件只触发一次(常用);
  • capture:使用事件的捕获模式;
  • self:只有event.target是当前操作的元素时才触发事件;
  • passive:事件的默认行为立即执行,无需等待事件回调执行完毕;
html 复制代码
<!-- 阻止默认事件(常用) -->
<a href="http://www.atguigu.com" @click.prevent="showInfo">点我提示信息</a>

1.6.3 键盘事件

Vue中常用的按键别名:

​ 回车 => enter

​ 删除 => delete (捕获"删除"和"退格"键)

​ 退出 => esc

​ 空格 => space

​ 换行 => tab (特殊,必须配合keydown去使用)

​ 上 => up

​ 下 => down

​ 左 => left

​ 右 => right

Vue未提供别名的按键,可以使用按键原始的key值去绑定,但注意要转为kebab-case(短横线命名)

系统修饰键(用法特殊):ctrl、alt、shift、meta

​ (1).配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发。

​ (2).配合keydown使用:正常触发事件。

也可以使用keyCode去指定具体的按键(不推荐)

Vue.config.keyCodes.自定义键名 = 键码,可以去定制按键别名

html 复制代码
<div id="root">
	<h2>欢迎来到{{name}}学习</h2>
	<input type="text" placeholder="按下回车提示输入" @keydown.huiche="showInfo">
</div>
<script type="text/javascript">
		Vue.config.keyCodes.huiche = 13 //定义了一个别名按键

		new Vue({
			el:'#root',
			data:{
				name:'尚硅谷'
			},
			methods: {
				showInfo(e){
					// console.log(e.key,e.keyCode)
					console.log(e.target.value)
				}
			},
		})
	</script>

1.7 计算属性和监听器

注:vue中的数据代理

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

1.7.1 计算属性(感觉跟数据代理差不多)

**定义:**要用的属性(computed)不存在,要通过已有属性计算得来。

**原理:**底层借助了Objcet.defineproperty方法提供的getter和setter。

初次使用是会调用getter函数,当依赖的数据发生改变时会再次调用且必须写set函数去响应修改

**优势:**与methods实现相比,内部有缓存机制(复用),效率更高,调试方便。

**备注:**计算属性最终会出现在vm上,直接读取使用即可。

如果修改属性需要在computed计算属性的属性写setter方法,getter方法可省略

例如:

js 复制代码
<script>
	const vm = new Vuew({
		el:'#root',
        data:{
            firstName:'张',
            lastName:'三'
        },
      //计算属性  
        computed:{
            fullName:{
                //get有什么用?当有人读取fullName时,get就会被调用,却返回值就为fullName的值      get什么时候调用?1. 初次读取fullName时,2.所依赖的数据发生变化时
                get(){
                    return this.firstName + '-' +this.lastName
                }
                //  set什么时候调用?修改的时候
                set(value){ //一般不用
            	  
        }
            }
        }
	})
</script>

简写

js 复制代码
<script>
	const vm = new Vuew({
		el:'#root',
        data:{
            firstName:'张',
            lastName:'三'
        },
      //计算属性  
        computed:{ //只提供一个getter
            fullName(){
            	  return this.firstName + '-' +this.lastName
            }
        }
	})
</script>

1.7.2 监视属性watch

1.当被监视的属性变化时, 回调函数自动调用, 进行相关操作

2.监视的属性必须存在,才能进行监视!!

3.监视的两种写法:

(1).new Vue时传入watch配置

(2).通过vm.$watch监视

javascript 复制代码
//第一种写法
watch:{
	isHot:{
	immediate:true, //初始化时让handler调用一下
	//handler什么时候调用?当isHot发生改变时。
	handler(newValue,oldValue){
	console.log('isHot被修改了',newValue,oldValue)
	}
}
//第二种写法
vm.$watch('isHot',{
			immediate:true, //初始化时让handler调用一下
			//handler什么时候调用?当isHot发生改变时。
			handler(newValue,oldValue){
				console.log('isHot被修改了',newValue,oldValue)
			}
		})

4.深度监视:

​ (1).Vue中的watch默认不监测对象内部值的改变(一层)。

​ (2).配置deep:true可以监测对象内部值改变(多层)。

备注:

(1).Vue自身可以监测对象内部值的改变,但Vue提供的watch默认不可以!

(2).使用watch时根据数据的具体结构,决定是否采用深度监视。

javascript 复制代码
watch:{
    isHot:{
        // immediate:true, //初始化时让handler调用一下
        //handler什么时候调用?当isHot发生改变时。
        handler(newValue,oldValue){
            console.log('isHot被修改了',newValue,oldValue)
        }
    },
    //监视多级结构中所有属性的变化
    numbers:{
        deep:true,
        handler(){
            console.log('numbers改变了')
        }
    }
}

1.8 class绑定

  1. 字符串写法,适用于:样式的类名不确定,需要动态指定

    vue 复制代码
    <div class="basic" :class="mood"></div>
    <script>
        const vm = new Vuew({
            el:'#root',
            data:{
               mood:"happy"
            },
        })
    </script>
  2. 数组写法,适用于:要绑定的样式个数不确定,名字也不确定

    vue 复制代码
    <div class="basic" :class="classArr"></div>
    <script>
        const vm = new Vuew({
            el:'#root',
            data:{
               classArr:["happy","sad","normal"]
            },
        })
    </script>
  3. 对象写法,适用于:要绑定的样式个数确定,名字也确定,但动态决定用不用

    vue 复制代码
    <div class="basic" :class="classObj"></div>
    <script>
        const vm = new Vuew({
            el:'#root',
            data:{
                classObj:{
                    hxx:true,
                    hxx2:false
                }
            },
        })
    </script>

1.9 条件渲染

1.9.1.v-if

写法:

  1. v-if="表达式"
  2. v-else-if="表达式"
  3. v-else="表达式"

**适用于:**切换频率较低的场景。

特点:不展示的DOM元素直接被移除

**注意:**v-if可以和:v-else-if、v-else一起使用,但要求结构不能被"打断"。

1.9.2 v-show

**写法:**v-show="表达式"

**适用于:**切换频率较高的场景。

特点:不展示的DOM元素未被移除,仅仅是使用样式隐藏display:none

1.9.3 总结

v-if需要操作dom元素有更高的切换消耗,v-show仅隐藏dom元素,如果需要非常频繁的切换,建议使用v-show较好,如果切换频率低,则使用v-if较好

1.10 列表渲染 null

1.11 收集表单数据

<input type="text"/>,则v-model收集的是value值,用户输入的就是value值。

<input type="radio"/>,则v-model收集的是value值,且要给标签配置value值。

<input type="checkbox"/>

​ 1.没有配置input的value属性,那么收集的就是checked(勾选 or 未勾选,是布尔值)

​ 2.配置input的value属性:

​ (1)v-model的初始值是非数组,那么收集的就是checked(勾选 or 未勾选,是布尔值)

​ (2)v-model的初始值是数组,那么收集的的就是value组成的数组

备注:v-model的三个修饰符:

​ lazy:失去焦点再收集数据 //懒收集

​ number:输入字符串转为有效的数字 //一般用在年龄

​ trim:输入首尾空格过滤 //过滤空格

例如:

​ 年龄:

1.12 过滤器

**定义:**对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理)。

语法:

​ 1.注册过滤器:Vue.filter(name,callback) 或 new Vue{filters:{}}

​ 2.使用过滤器:{{ xxx | 过滤器名}} 或 v-bind:属性 = "xxx | 过滤器名"(少用)

​ 3. 可以嵌套 {{ xxx | 过滤器名1 | 过滤器名2}} 先1 然后将1返回值给2

备注:

​ 1.过滤器也可以接收额外参数、多个过滤器也可以串联

​ 2.并没有改变原本的数据, 是产生新的对应的数据

*例如

js 复制代码
<p>{{time | timeFormat("YYYY-MM-DD")}}</p>
<script>
    //全局过滤器
    Vue.filter(timeFormat,function(value,str){
   		return dayjs(value).format(str)
})

	const vm = new Vuew({
		el:'#root',
        data:{
           time:"时间戳"
        },
        //局部过滤器
        filters:{
            //value的值是 过滤器上面 | 符号前面的值  str是括号传参的值  ES6支持默认值
            timeFormat(value,str="YYYY-MM-DD HH:mm:ss"){
                return dayjs(value).format(str) //dayjs 是用BootCDN下载的js库
            }
        }
	})
</script>

1.13 内置指令与自定义指令 null

1.13.1 内置指令

v-text指令:

​ 1.作用:向其所在的节点中渲染文本内容。

​ 2.与插值语法的区别:v-text会替换掉节点中的内容,{{xx}}则不会。

v-html指令:(少用很危险)

​ 1.作用:向指定节点中渲染包含html结构的内容。

​ 2.与插值语法的区别:

​ (1).v-html会替换掉节点中所有的内容,{{xx}}则不会。

​ (2).v-html可以识别html结构。

​ 3.严重注意:v-html有安全性问题!!!!

​ (1).在网站上动态渲染任意HTML是非常危险的,容易导致XSS攻击。

​ (2).一定要在可信的内容上使用v-html,永不要用在用户提交的内容上!

v-cloak指令(没有值):😶

​ 1.本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性。

​ 2.使用css配合v-cloak可以解决网速慢时页面展示出{{xxx}}的问题。

​ 3.在vue实例接管容器的一瞬间删除v-cloak 配合display:none使用 📜

v-once指令:

​ 1.v-once所在节点在初次动态渲染后,就视为静态内容了。

​ 2.以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能。

v-pre指令:

​ 1.跳过其所在节点的编译过程。

​ 2.可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译。

1.13.2 自定义指令

  1. 自定义指令-函数式

    js 复制代码
    <p v-big="n"></p>
    <script>
    
    
    	const vm = new Vuew({
    		el:'#root',
            data:{
               n:1
            },
            //声明指令
     		directives:{
                //big函数何时被调用? 1.指令与元素成功绑定时 2.指令所在的模板被重新解析时
                //element 为自定义指令绑定的元素(即<p></p>) binding为元素中的值 (即:n)
                big(element,binding){
                    console.log(element,binding)
                    element.innerText = binding.value * 10
                }
            }
    	})
    </script>
  2. 自定义指令-对象式

    js 复制代码
    <input  type="text" v-fbind:value="n"/>
    <script>
    
    
    	const vm = new Vuew({
    		el:'#root',
            data:{
               n:1
            },
            //声明指令
     		directives:{
              
                fbind:{
                    //指令与元素成功绑定时
                    bind(element,binding){
                         element.value = binding.value 
                    },
                    //指令所在元素被插入页面时
                    inserted(element,binding){
                         element.focus()
                    },
                    //指令所在模板被重新解析时
                    update(element,binding){
                         element.value = binding.value 
                         element.focus()
                    }
                }
            }
    	})
    </script>
  3. 注意事项:

    1. 多个单词命名 用'-'连接 比如:big-number 但在directives中要把名字写成这样'big-number'
    2. 上面的都是局部的自定义指令,全局写法 Vue.directive('名字',function(){上面一样})

1.14 生命周期

​ vm的一生(vm的生命周期):

​ 将要创建 ====> 调用beforeCreate函数

​ 创建完毕 ====> 调用create函数

​ 将要挂载 ====> 调用beforeMount函数

​ 挂载完毕 ====> 调用mounted函数 ===========================>【重要的钩子】

​ 将要更新 ====> 调用beforeUpdate函数

​ 更新完毕 ====> 调用updated函数

​ 将要销毁 ====> 调用beforeDestroy函数 ============================>【重要的钩子】

​ 销毁完毕 ====> 调用destroyed函数

常用的生命周期函数

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

关于销毁vue实例

  • 销毁后借助vue开发者工具看不到任何信息
  • 销毁后自定义事件会失效,但原生DOM事件依然有效
  • 一般不会在beforeDestroy操作数据,因为即使操作数据,也不会在触发更新流程了。

二、Vue的组件化编程

2.1 非单文件组件

非单文件组件的基本使用

**非单文件组件:**一个文件中包含有 n 个组件

Vue使用组件的三大步骤:

三大步骤:

  1. 定义组件(创建组件)
  2. 注册组件
  3. 使用组件(写组件标签)

1)如何定义一个组件?

使用Vue.extend(options)创建,其中options和new Vue(options)时传入的那个options几乎一样,但也有点区别;

区别如下:

​ 1.el不要写,为什么? --------- 最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器。

​ 2.data必须写成函数,为什么? ------------ 避免组件被复用时,数据存在引用关系。

**备注:**使用template可以配置组件结构。

2)如何注册组件?

​ 1.局部注册:靠new Vue的时候传入components选项

​ 2.全局注册:靠Vue.component('组件名',组件)

3)编写组件标签:

<school></school>

几个注意点

1)关于组件名:

​ 一个单词组成:

​ 第一种写法(首字母小写):school

​ 第二种写法(首字母大写):School

​ 多个单词组成:

​ 第一种写法(kebab-case命名):my-school

​ 第二种写法(CamelCase命名):MySchool (需要Vue脚手架支持)大驼峰

​ 备注:

​ (1).组件名尽可能回避HTML中已有的元素名称,例如:h2、H2、header都不行。

​ (2).可以使用name配置项指定组件在开发者工具中呈现的名字。

2)关于组件标签:

​ 第一种写法:

​ 第二种写法:

​ 备注:不用使用脚手架时,会导致后续组件不能渲染。

3)一个简写方式:

​ const school = Vue.extend(options) 可简写为:const school = options

2.2 VueComponet

关于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。

一个重要的内置关系:

js 复制代码
VueComponent.prototype.__proto__ === Vue.prototype

为什么要有这个关系:

让组件实例对象(vc)可以访问到 Vue原型上的属性、方法。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1QxFsds6-1693207452746)(C:\\Users\\黄佳欣\\AppData\\Roaming\\Typora\\typora-user-images\\image-20230605155049133.png)

prototype是显示原型属性(函数)

__proto__隐式原型属性(对象)

程序员通过显示原型属性操作原型对象给对象赋值

2.3 单文件组件

模板页面 :

html 复制代码
<template>
页面模板
</template>

JS 模块对象

html 复制代码
<script>
export default {
data() {return {}},
methods: {},
computed: {},
components: {}
} 
</script>

样式

html 复制代码
<style>
样式定义
</style>

基本使用 :引入组件(import)=>映射成标签(components)=>使用组件标签()

三、使用Vue脚手架

3.1 初始化脚手架

**说明:**Vue 脚手架是 Vue 官方提供的标准化开发工具(开发平台)

具体步骤:

  1. 全局安装@vue/cli: npm install -g @vue/cli
  2. 创建vue项目:vue create xxx
  3. 选择使用所需要的vue的版本
  4. 启动项目:npm run serve

Vue 脚手架隐藏了所有 webpack 相关的配置,若想查看具体的 webpack 配置,请执行:

vue inspect > output.js

分析脚手架结构:

.文件目录

├── node_modules

├── public

│ ├── favicon.ico: 页签图标

│ └── index.html: 主页面

├── src

│ ├── assets: 存放静态资源

│ │ └── logo.png

│ │── component: 存放组件

│ │ └── HelloWorld.vue

│ │── App.vue: 汇总所有组件

│ └── main.js: 入口文件

├── .gitignore: git版本管制忽略的配置

├── babel.config.js: babel的配置文件

├── package.json: 应用包配置文件

├── README.md: 应用描述文件

└── package-lock.json: 包版本控制文件

render函数

javascript 复制代码
new Vue({
    el:'#app',
    // 简写形式
	render: h => h(App),
    // 完整形式
	// render(createElement){
	//     return createElement(App)
	// }
})
  1. vue.js与vue.runtime.xxx.js的区别:
    1. vue.js是完整版的Vue,包含:核心功能 + 模板解析器。
    2. vue.runtime.xxx.js是运行版的Vue,只包含:核心功能;没有模板解析器。
  2. 因为vue.runtime.xxx.js没有模板解析器,所以不能使用template这个配置项,需要使用render函数接收到的createElement函数去指定具体内容。

vue.config.js配置文件

  1. 使用vue inspect > output.js可以查看到Vue脚手架的默认配置。
  2. 使用vue.config.js可以对脚手架进行个性化定制,详情见:https://cli.vuejs.org/zh

3.2 ref属性和props配置项

ref属性

功能:被用来给元素或子组件注册引用信息(id的替代者和id差不多)

应用在html标签上获取的是真实DOM元素,应用在组件标签上获取的是组件实例对象(vc)

使用方式:

  1. 打标识:<h1 ref="xxx"></h1><School ref="xxx"></School>
  2. 获取:this.$refs.xxx

props配置项

功能:让组件接受外部传来的值

传递数据:<Demo name="xxx" :age/>

接受数据:第三种方式(限制类型、限制必要性、指定默认值)

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

javascript 复制代码
 //简单声明接收
props:['name','sex','age']
//接收的同时对数据进行类型限制
props:{
    name:string,
    sex:string,
    age:Number
}
//接收的同时对数据进行显示+默认值的指定+必要性的限制
props:{
    name:{
        type:String,//name的类型是字符串
        required:true//name是必要的
    },
    age:{
        type:Number,//name的类型是字符串
        default:99//默认值
    }

3.3 mixin(混入)

功能:可以把多个组件共用的配置提取成一个混入对象(可以共用函数或数据)

使用方法:

  1. 定义混入:创建minin.js
javascript 复制代码
const mixin = {
    data(){....},
    methods:{....}
    ....
}
  1. 使用混入:

    1. 全局混入:Vue.mixin(xxx)

    2. 局部混入:mixins:['xxx']

组件和混入对象同名值基本与组件优先

同名生命周期钩子函数将合并成一个数组同时被调用,且混入的钩子函数将在自身组件前调用

3.4 plugin自定义插件

功能:用于增强Vue

本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据

定义插件:

src/plugin.js:

javascript 复制代码
export default {
	install(Vue,x,y,z){
	....
	//给Vue原型上添加一个方法(vm和vc就都能用了)
	Vue.prototype.hello = ()=>{alert('你好啊')}
	}
}

使用插件:Vue.use(plugin)

3.5 Todo-List案例

  1. 组件化编码流程:

    ​ (1).拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突。

    ​ (2).实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:

    ​ 1).一个组件在用:放在组件自身即可。

    ​ 2). 一些组件在用:放在他们共同的父组件上(状态提升)。

    ​ (3).实现交互:从绑定事件开始。

  2. props适用于:

    ​ (1).父组件 ==> 子组件 通信

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

  3. 使用v-model时要切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改的!

  4. props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做。避免子组件直接修改父组件传输过来的值,也就是props接收的值,可以使用父组件传输函数的方式

  5. 数组reduce()方法的使用

    js 复制代码
    todos={
        name: "hxx" , age:2 , done : true,
        name: "hxx1" , age:21 , done : true,
        name: "hxx2" , age:22 , done : false
    }
    //计算属性
    computed(){
        //定义计算属性方法
    	todoTotal(){
            //数组使用reduce函数  数组有几个就循环几次
            //pre是上一个值的返回值 0:用来声明初始值 也就是pre一开始的值
            //current:用来接收数值中的值 例子下是 todo对象
    		return this.todos.reduce((pre,current,0)=>{
    			return pre + (current.done ? 1: 0)
    		})
            //循环过程为: pre 0 pre 1 pre 2  计算属性结果为2			 
    	}
    }

scoped样式

scoped样式:

  1. 功能:让样式在局部生效,防止冲突,固定样式作用域
  2. 写法:<style scoped>

scoped样式一般不会在App.vue中使用

3.6 组件的自定义事件

功能:一种组件间通信的方式,适用于:子组件 ===> 父组件

使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)

绑定自定义事件:

本质上类似函数传递一样的方法 还免去了props的调用

  1. 第一种方式,在父组件中:<Demo @xxx="test"/><Demo v-xxx="test"/>

    js 复制代码
    method(){
    	test(name,...a){
            console.log(name,a)//如果this.$emit('xxx',数据)中的数据只有一个参数的话会被name接收  有多个的话第一个会被name接收 后面的都会是数组 ...a是ES6写法
        }
    }
  2. 第二种方式,在父组件中:(与第一种相比灵活性更高,如可使用定时器)

javascript 复制代码
<Demo ref="demo"/>
......
mounted(){
   this.$refs.demo.$on('xxx',this.test)
}

触发自定义事件:

​ 触发自定义事件:```this.$emit('xxx',数据)``----->子组件

解绑自定义事件:

this.$off('atguigu')``````解绑一个

​ this. o f f ( ) ' ' ' 解绑所有 t h i s . off()```解绑所有this. off()'''解绑所有this.off('demo1','demo2')解绑多个```

注意:

​ 组件上也可以绑定原生DOM事件,需要使用native修饰符。如:<Student @click.native="show"/>

​ 若想让自定义事件只能触发一次,可以使用once修饰符,或$once方法。

​ 通过this.$refs.xx.$on('xxx',回调)绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题!

3.7 全局事件总线

  1. 一种组件间通信的方式,适用于任意组件间通信。

    组件vc会递归的寻找 b u t 最终会找到 v m 身上,因为 v c 是 v m 用 e x t e n d ( ) 方法创建的,所以要在 n e w v u e 中增加一个属性 ( p r o t o t y p e ) but最终会找到vm身上,因为vc是vm用extend({})方法创建的,所以要在new vue中增加一个属性(prototype) but最终会找到vm身上,因为vc是vm用extend()方法创建的,所以要在newvue中增加一个属性(prototype)but并指向它本身this

  2. 安装全局事件总线:

    js 复制代码
    new Vue({
    	......
    	beforeCreate() {
    		Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm 
    	},
        ......
    }) 
  3. 使用事件总线:

    1. 接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。

      js 复制代码
      methods(){
        demo(data){......}
      }
      ......
      mounted() {
        this.$bus.$on('事件名',this.demo)//$on是用来绑定事件的
      }
    2. 提供数据:this.$bus.$emit('事件名',数据)$emit用来触发事件

  4. 最好在 组件中的 beforeDestroy钩子中,用$off去解绑当前组件所用到的事件。

3.8 消息订阅与发布

  1. 一种组件间通信的方式,适用于任意组件间通信。

  2. 使用步骤:

    1. 安装pubsub:npm i pubsub-js

    2. 引入: import pubsub from 'pubsub-js'

    3. 接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。

      js 复制代码
      methods(){
        demo(msg,data){......}
        //msg 为发送地键值对的名称
        //data为发送地的值
      }
      ......
      mounted() {
        this.pid = pubsub.subscribe('xxx',this.demo) //订阅消息
      }
    4. 提供数据:pubsub.publish('xxx',数据)

    5. 最好在beforeDestroy钩子中,用PubSub.unsubscribe(pid)去取消订阅。

    nextTick

    1. 语法:this.$nextTick(回调函数)

    2. 作用:在下一次 DOM 更新结束后执行其指定的回调。

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

3.9 过渡动画

vue 复制代码
<template>
	<div>
        <button @click="isShow = !isShow">点我切换</button>
        <transition name="hello" appear> //appear:DOM元素第一次生成就使用样式
            <h1 v-show="isShow">你好啊</h1>  //transition在解析到页面的时候会隐藏
    	</transition>
    </div>
</template>
<script>
	export default{
        name:'Test',
        data(){
            return {
                isSHow:true
            }
        },
    }
</script>
<style scope>
    h1{
        background-color:orange;
    }
    .hello-enter-active{ //hello要更上面的name一样  如果上面没有声明name 则默认为v
        animation: test1 0.5s linear;
    }
    .hello-leave-active{
        animation: test1 0.5s linear;
    }
    @keyframes test1{
        from{
            transform :translateX(-100%)
        }
        to{
            transform :translateX(0px)
        }
    }
</style>

四、Vue中的ajax

4.1 解决ajax跨域问题

需要下载axios库:npm install axios

方法一

​ 在vue.config.js中添加如下配置:

js 复制代码
devServer:{
  proxy:"http://localhost:5000"
}

说明:

  1. 优点:配置简单,请求资源时直接发给前端(8080)即可。
  2. 缺点:不能配置多个代理,不能灵活的控制请求是否走代理。
  3. 工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器 (优先匹配前端资源)public下的目录文件

方法二

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

js 复制代码
module.exports = {
	devServer: {
      proxy: {
      '/api1': {// 匹配所有以 '/api1'开头的请求路径
        target: 'http://localhost:5000',// 代理目标的基础路径
        changeOrigin: true,
        pathRewrite: {'^/api1': ''}
      },
      '/api2': {// 匹配所有以 '/api2'开头的请求路径
        target: 'http://localhost:5001',// 代理目标的基础路径
        changeOrigin: true,
        pathRewrite: {'^/api2': ''}
      }
    }
  }
}
/*
   changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
   changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:8080
   changeOrigin默认值为true
*/

说明:

  1. 优点:可以配置多个代理,且可以灵活的控制请求是否走代理。
  2. 缺点:配置略微繁琐,请求资源时必须加前缀。

4.2 slot 插槽

  1. 作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式,适用于 父组件 ===> 子组件 。组件中的组件,可复用代码片段

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

使用方式:

  1. 默认插槽:在父组件中写上的html代码 放入子组件的slot标签中

    vue 复制代码
    父组件中:
            <Category>
               <div>html结构1</div>
            </Category>
    子组件中:
            <template>
                <div>
                   <!-- 定义插槽 -->
                   <slot>插槽默认内容...</slot>
                </div>
            </template>
  2. 具名插槽:

    vue 复制代码
    父组件中:
            <Category>
                <template slot="center">
                  <div>html结构1</div>
                </template>
    
                <template v-slot:footer> //v-slot:footer只能放在template和component中
                   <div>html结构2</div>
                </template>
            </Category>
    子组件中:
            <template >
                <div>
                   <!-- 定义插槽 -->
                   <slot name="center">插槽默认内容...</slot>
                   <slot name="footer">插槽默认内容...</slot>
                </div>
            </template>
  3. 作用域插槽:

    1. 理解:数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。(games数据在Category组件中,但使用数据所遍历出来的结构由App组件决定)

    2. 具体编码:

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

五、VueX(store)

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

在多个组件需要共享数据时使用

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vjOjWb4e-1693207452748)(C:\\Users\\黄佳欣\\AppData\\Roaming\\Typora\\typora-user-images\\image-20230619195534013.png)

5.1 VueX环境搭建

vue3 兼容vuex4版本

npm i vuex,默认安装的是vuex4版本

如果vue2的框架仅支持vue3版本

npm i vuex@3

  1. 创建文件:src/store/index.js

    注意:js文件中是先执行文件中的import语句在执行其他语句 简称"先引用后其他

js 复制代码
//引入Vue核心库 
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'

Vue.use(Vuex)
//准备actions对象------响应组件中用户的动作
const actions = {}
//准备mutations对象------修改state中的数据
const mutations = {}
//准备state对象------保存具体的数据
const state = {}

//创建并暴露store
export default new Vuex.Store({
	actions,
	mutations,
	state
})
  1. main.js中创建vm时传入store配置项
js 复制代码
......

//引入store
import store from './store'
......

//创建vm
new Vue({
	el:'#app',
	render: h => h(App),
	store //要上面使用vuex才能使用store
})

5.2 VueX的基本使用

  1. 初始化数据、配置actions、配置mutations,操作文件store.js
js 复制代码
//引入Vue核心库
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//引用Vuex
Vue.use(Vuex)
//服务员
const actions = {
    //响应组件中加的动作
	jia(context,value){
		// console.log('actions中的jia被调用了',miniStore,value)
		context.commit('JIA',value)
	},
}
//后厨
const mutations = {
    //执行加
	JIA(state,value){
		// console.log('mutations中的JIA被调用了',state,value)
		state.sum += value
	}
}
//传菜
//初始化数据
const state = {
   sum:0
}
//暴露餐厅
//创建并导出/暴露store
export default new Vuex.Store({
	actions,
	mutations,
	state,
})
  1. 组件中读取vuex中的数据:$store.state.sum

  2. 组件中修改vuex中的数据:$store.dispatch('action中的方法名',数据)dispatch调用action中的方法$store.commit('mutations中的方法名',数据)commit直接调用mutations中方法

    备注:若没有网络请求或其他业务逻辑,组件中也可以越过actions,即不写dispatch,直接编写commit

5.3 getters的使用

  1. 概念:当state中的数据需要经过加工后再使用时,可以使用getters加工。类似于vue中的计算属性

  2. store.js中追加getters配置

    js 复制代码
    ......
    
    const getters = {
    	bigSum(state){
    		return state.sum * 10
    	}
    }
    
    //创建并暴露store
    export default new Vuex.Store({
    	......
    	getters
    })
  3. 组件中读取数据:$store.getters.bigSum

5.4 四个map方法的使用

​ 以下 本质上是将调用store中的state.getter.action等的函数 简写成一行

  1. mapState方法: 用于帮助我们映射state中的数据为计算属性

    js 复制代码
    computed: {
        //借助mapState生成计算属性:sum、school、subject(对象写法)
         ...mapState({sum:'sum',school:'school',subject:'subject'}),
         //mapState返回的是一个对象类型的函数 如:{sum:function(){},school:function(){}....}
         //...是ES6的写法 下同
        //借助mapState生成计算属性:sum、school、subject(数组写法)
        ...mapState(['sum','school','subject']),
    },
  2. mapGetters方法: 用于帮助我们映射getters中的数据为计算属性

    js 复制代码
    computed: {
        //借助mapGetters生成计算属性:bigSum(对象写法)
        ...mapGetters({bigSum:'bigSum'}),
    
        //借助mapGetters生成计算属性:bigSum(数组写法)
        ...mapGetters(['bigSum'])
    },
  3. mapActions方法: 用于帮助我们生成与actions对话的方法,即:包含$store.dispatch(xxx)的函数

    js 复制代码
    methods:{
        //靠mapActions生成:incrementOdd、incrementWait(对象形式)
        ...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
    
        //靠mapActions生成:incrementOdd、incrementWait(数组形式)
        ...mapActions(['jiaOdd','jiaWait'])
    }
  4. mapMutations方法: 用于帮助我们生成与mutations对话的方法,即:包含$store.commit(xxx)的函数

    js 复制代码
    methods:{
        //靠mapActions生成:increment、decrement(对象形式) 如果有参数的话 需要在调用increment函数时 将参数写在括号里 如:increment(5)
        ...mapMutations({increment:'JIA',decrement:'JIAN'}),
        
        //靠mapMutations生成:JIA、JIAN(对象形式)
        ...mapMutations(['JIA','JIAN']),
    }

备注:mapActions与mapMutations使用时,若需要传递参数需要:在模板中绑定事件时传递好参数,否则参数是事件对象。

5.5.模块化+命名空间

  1. 目的:让代码更好维护,让多种数据分类更加明确。

  2. 修改store.js

    javascript 复制代码
    const countAbout = {
      namespaced:true,//开启命名空间
      state:{x:1},
      mutations: { ... },
      actions: { ... },
      getters: {
        bigSum(state){
           return state.sum * 10
        }
      }
    }
    
    const personAbout = {
      namespaced:true,//开启命名空间
      state:{ ... },
      mutations: { ... },
      actions: { ... }
    }
    
    const store = new Vuex.Store({
      modules: {
        countAbout,
        personAbout
      }
    })
  3. 开启命名空间后,组件中读取state数据:

    js 复制代码
    //方式一:自己直接读取
    this.$store.state.personAbout.list
    //方式二:借助mapState读取:
    ...mapState('countAbout',['sum','school','subject']),
  4. 开启命名空间后,组件中读取getters数据:

    js 复制代码
    //方式一:自己直接读取
    this.$store.getters['personAbout/firstPersonName']
    //方式二:借助mapGetters读取:
    ...mapGetters('countAbout',['bigSum'])
  5. 开启命名空间后,组件中调用dispatch

    js 复制代码
    //方式一:自己直接dispatch
    this.$store.dispatch('personAbout/addPersonWang',person)
    //方式二:借助mapActions:
    ...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
  6. 开启命名空间后,组件中调用commit

    js 复制代码
    //方式一:自己直接commit
    this.$store.commit('personAbout/ADD_PERSON',person)
    //方式二:借助mapMutations:
    ...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),

六、 Vue-Router

路由

  1. 理解: 一个路由(route)就是一组映射关系(key - value),多个路由需要路由器(router)进行管理。
  2. 前端路由:key是路径,value是组件。

6.1 基本使用

  1. 安装vue-router,命令:npm i vue-router

现在vue-router默认版本为4,而且vue-router4仅支持vue3

npm i vue-router@3

  1. 应用插件:Vue.use(VueRouter)并修改main.js
js 复制代码
//引入VueRouter
import VueRouter from "vue-router";
//引入路由器
import router from './router'
Vue.use(VueRouter)
new Vue({
  render: h => h(App),
  router:router
}).$mount('#app')
  1. 编写router配置项:
js 复制代码
//引入VueRouter
import VueRouter from 'vue-router'
//引入Luyou 组件
import About from '../components/About'
import Home from '../components/Home'

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

//暴露router
export default router
  1. 实现切换(active-class可配置高亮样式)

    vue 复制代码
    <router-link active-class="active" to="/about">About</router-link>
  2. 指定展示位置

    vue 复制代码
    <router-view></router-view>

6.2 几个注意点

  1. 路由组件通常存放在pages文件夹,一般组件通常存放在components文件夹。
  2. 通过切换,"隐藏"了的路由组件,默认是被销毁掉的,需要的时候再去挂载。
  3. 每个组件都有自己的$route属性,里面存储着自己的路由信息。
  4. 整个应用只有一个router,可以通过组件的$router属性获取到。

6.3 多级路由(子路由)

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

6.4 路由的query参数

  1. 传递参数

    vue 复制代码
    <!-- 跳转并携带query参数,to的字符串写法 -->
    <router-link :to="/home/message/detail?id=666&title=你好">跳转</router-link>
    				
    <!-- 跳转并携带query参数,to的对象写法 -->
    <router-link 
    	:to="{
    		path:'/home/message/detail',
    		query:{
    		   id:666,
                title:'你好'
    		}
    	}"
    >跳转</router-link>
  2. 接收参数:

    js 复制代码
    $route.query.id
    $route.query.title

6.5 命名路由

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

  2. 如何使用

    1. 给路由命名:

      js 复制代码
      {
      	path:'/demo',
      	component:Demo,
      	children:[
      		{
      			path:'test',
      			component:Test,
      			children:[
      				{
                          name:'hello' //给路由命名
      					path:'welcome',
      					component:Hello,
      				}
      			]
      		}
      	]
      }
    2. 简化跳转:

      vue 复制代码
      <!--简化前,需要写完整的路径 -->
      <router-link to="/demo/test/welcome">跳转</router-link>
      
      <!--简化后,直接通过名字跳转 -->
      <router-link :to="{name:'hello'}">跳转</router-link>
      
      <!--简化写法配合传递参数 -->
      <router-link 
      	:to="{
      		name:'hello',
      		query:{
      		   id:666,
                 title:'你好'
      		}
      	}"
      >跳转</router-link>

6.6 路由的params参数

  1. 配置路由,声明接收params参数

    js 复制代码
    {
    	path:'/home',
    	component:Home,
    	children:[
    		{
    			path:'news',
    			component:News
    		},
    		{
    			component:Message,
    			children:[
    				{
    					name:'xiangqing',
    					path:'detail/:id/:title', //使用占位符声明接收params参数
    					component:Detail
    				}
    			]
    		}
    	]
    }
  2. 传递参数

    vue 复制代码
    <!-- 跳转并携带params参数,to的字符串写法 -->
    <router-link :to="/home/message/detail/666/你好">跳转</router-link>
    				
    <!-- 跳转并携带params参数,to的对象写法 -->
    <router-link 
    	:to="{
    		name:'xiangqing',
    		params:{
    		   id:666,
                title:'你好'
    		}
    	}"
    >跳转</router-link>

    特别注意:路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置!

  3. 接收参数:

    js 复制代码
    $route.params.id
    $route.params.title

6.7 路由的props配置

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

js 复制代码
{
	name:'xiangqing',
	path:'detail/:id',
	component:Detail,

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

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

6.8 <router-link>的replace属性

  1. 作用:控制路由跳转时操作浏览器历史记录的模式
  2. 浏览器的历史记录有两种写入方式:分别为pushreplacepush是追加历史记录,replace是替换当前记录。路由跳转时候默认为push
  3. 如何开启replace模式:<router-link replace .......>News</router-link>

6.9 编程式路由导航

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

  2. 具体编码:

    js 复制代码
    //$router的两个API
    this.$router.push({
    	name:'xiangqing',
    		params:{
    			id:xxx,
    			title:xxx
    		}
    })
    
    this.$router.replace({
    	name:'xiangqing',
    		params:{
    			id:xxx,
    			title:xxx
    		}
    })
    this.$router.forward() //前进
    this.$router.back() //后退
    this.$router.go() //可前进也可后退  +3 往前走3步  -3 往后走3步 0则为刷新

6.10 缓存路由组件

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

  2. 具体编码:

    vue 复制代码
    <!--  缓存一个路由组件 -->
    <keep-alive include="News">  //News为组件的名字
        <router-view></router-view>
    </keep-alive>
    
    
    <!--  缓存多个路由组件 -->
    <keep-alive :include="['News','hxx']">  //News,hxx为组件的名字
        <router-view></router-view>
    </keep-alive>

6.11 两个新的生命周期钩子

  1. 作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态。
  2. 具体名字:
    1. activated路由组件被激活时触发。
    2. deactivated路由组件失活时触发。
  3. 注意:上面两个生命周期钩子需要配合使用

6.12 路由守卫

  1. 作用:对路由进行权限控制

  2. 分类:全局守卫、独享守卫、组件内守卫

  3. 全局守卫:

    js 复制代码
    //配置是否要在路由守卫中进行权限验证
    //引入VueRouter
    import VueRouter from 'vue-router'
    //引入路由 组件
    import About from '../components/About'
    import Home from '../components/Home'
    
    //创建router实例对象,去管理一组一组的路由规则
    const router = new VueRouter({
    	routes:[
    		{
    			path:'/about',
    			component:About,
                meta:{isAuth:true} //isAute表示是否要进行权限验证 true是需要 false不需要 配合下面路由前置守卫使用 不写默认为undefined 则为false
    		},
    		{
    			path:'/home',
    			component:Home,
                meta:{isAuth:false,title:"hxx"} //meta是用来存储需要的自定义数据的
    		}
    	]
    })
    
    
    //全局前置守卫:初始化时执行、每次路由切换前执行
    router.beforeEach((to,from,next)=>{
    	console.log('beforeEach',to,from)
    	if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
    		if(localStorage.getItem('school') === 'atguigu'){ //权限控制的具体规则
    			next() //放行
    		}else{
    			alert('暂无权限查看')
    			// next({name:'guanyu'})
    		}
    	}else{
    		next() //放行
    	}
    })
    
    //全局后置守卫:初始化时执行、每次路由切换后执行
    //后置路由守卫是没有next的
    router.afterEach((to,from)=>{
    	console.log('afterEach',to,from)
        //缩写 document.title = to.meta.title || "vue_test"
    	if(to.meta.title){ 
    		document.title = to.meta.title //修改网页的title
    	}else{
    		document.title = 'vue_test'
    	}
    })
    
    export default router
  4. 独享守卫: 只有前置守卫没有后置守卫

    js 复制代码
    //引入VueRouter
    import VueRouter from 'vue-router'
    //引入Luyou 组件
    import About from '../components/About'
    import Home from '../components/Home'
    
    //创建router实例对象,去管理一组一组的路由规则
    const router = new VueRouter({
    	routes:[
    		{
    			path:'/about',
    			component:About,
                //独享路由前置守卫
                beforeEnter(to,from,next){
                    console.log('beforeEnter',to,from)
                    if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
                        if(localStorage.getItem('school') === 'atguigu'){
                            next()
                        }else{
                            alert('暂无权限查看')
                            // next({name:'guanyu'})
    					}
                    }else{
                        next()
                    }
    		}
    		},
    		{
    			path:'/home',
    			component:Home
    		}
    	]
    })
    
    //暴露router
    export default router
  5. 组件内守卫: 在组件中写的

    js 复制代码
    //进入守卫:通过路由规则,进入该组件时被调用 组件使用不会被调用
    beforeRouteEnter (to, from, next) {
    },
    //离开守卫:通过路由规则,离开该组件时被调用
    beforeRouteLeave (to, from, next) {
    }

6.13 路由器的两种工作模式

​ 路径上的'#' 被称为hash

​ 路由中默认使用hash 要改为history 需要做一下修改

js 复制代码
const router = new VueRouter({
    mode:'history'
	routes:[]
})
  1. 对于一个url来说,什么是hash值?------ #及其后面的内容就是hash值。

  2. hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器。

  3. hash模式:

    1. 地址中永远带着#号,不美观 。
    2. 若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。
    3. 兼容性较好。
  4. history模式:

    1. 地址干净,美观 。
    2. 兼容性和hash模式相比略差。
    3. 应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题(nginx)。

总结

route 是用来获取路由信息的,router是用来操作路由的

七、VueUI组件库

移动端常用 UI 组件库

  1. Vant https://youzan.github.io/vant
  2. Cube UI https://didi.github.io/cube-ui
  3. Mint UI http://mint-ui.github.io

PC 端常用 UI 组件库

  1. Element UI https://element.eleme.cn
  2. IView UI https://www.iviewui.com

js处理库

  1. BootCDN

八、Vue3

1.Vue3简介

  • 2020年9月18日,Vue.js发布发布3.0版本

2.Vue3带来了什么

1.性能的提升

  • 打包大小减少41%
  • 初次渲染快55%,更新渲染快133%
  • 内存减少54%等

2. 源码的升级

  • 使用Proxy 代替defineProperty实现响应式
  • 重写虚拟DOM的实现和Tree-Shaking(树摇--->术语表:剔除一些没有的代码 )

3.拥抱TypeScript

  • Vue3可以更好的支持TypeScript

4. 新的特性

  1. Composition API(组合API)
    • setup配置
    • ref与reactive
    • watch与watchEffect
    • provide与inject
  2. 新的内置组件
    • Fargment
    • Teleport
    • Supense
  3. 其他改变
    • 新的生命周期钩子
    • data选项应始终被声明为一个函数
    • 移出keyCode支持作为v-on的修饰符

一、创建一个Vue3.0工程

1. 使用vue-cil创建

js 复制代码
//查看vue版本
vue -V
//创建一个vue-cil项目
vue create vue3_test
//启动项目
npm run serve

2. 使用vite创建

官方地址:Home | Vite中文网 (vitejs.cn)

  • 什么是vite ------新一代前端构建工具

  • 优势:

    • 开发环境中,无需打包操作,可快速的冷启动
    • 轻量快速的热重载(HMR)
    • 真正的按需编译,不在等待整个应用编译完成
  • 传统构建与vite构建对比图

  • 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s0yUeaVH-1693207452749)(C:\\Users\\黄佳欣\\AppData\\Roaming\\Typora\\typora-user-images\\image-20230621095428782.png)

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rasSXxwZ-1693207452758)(C:\\Users\\黄佳欣\\AppData\\Roaming\\Typora\\typora-user-images\\image-20230621095458518.png)

bash 复制代码
##创建工程
npm init vite-app 工程名
##进入工程目录
cd 工程名
##安装依赖
npm install 
## 运行
npm run dev

二、常见Composition API(组合式API)

1. setup

  1. 理解:vue3中的一个新的配置项 值是一个函数
  2. setup是所有Composition API(组合API)"表演的舞台"
  3. 组件中所用的:数据、方法等等,均要配置在setup中
  4. setup函数的两种返回值:
    1. 若返回一个对象,则对象中的属性、方法,在模板中均可以直接使用(重点关注)
    2. 若返回一个渲染函数,则可以自定义渲染内容(了解)
  5. 注意点:
    1. 尽量不要与vue2.x配置混用
      1. Vue2.x配(data、method、computed...)中可以访问到setup中的属性、方法。
      2. 但在setup中不能访问到Vue2.x配置(data、method、computed...)
      3. 如果有重名,setup优先
    2. setup不能是一个aynsc函数,因为返回值不在是return的对象,而是promise,模板看不到return对象的属性。(后期可以返回一个Promise实例,但需要Suspose和异步组件的配合)
    3. setup 优先于beforeCreate 所以this 不是VC 而是setup本身

2. ref函数

  • 作用: 定义一个响应式的数据

  • 语法:

    js 复制代码
    import {ref} from 'vue'
    const xxx  = ref(initValue)
    • 创建一个包含响应式数据的引用对象(reference对象)
    • JS中操作数据 :xxx.value
    • 模板中读取数据: 不需要value 直接 <div>{``{xxx}}</div>
  • 备注:

    • 接收的数据可以是 : 基本类型、也可以是对象类型
    • 基本类型的数据: 响应式依然是靠着Object.defineProperty()getset完成的
    • 对象类型的数据: 内部"求助"了vue3.0中的一个新函数 ----reactive函数
      • 对象类型的数据ref将使用Proxy来实现的,vue3用reactive封装了Proxy

3. reactive函数

  • 作用: 定义一个对象类型的响应式数据(基本类型别用它,用ref函数)

  • 语法:const 代理对象 = reactive(源对象)接收一个对象(或数组),返回一个代理对象(Proxy的实例对象,简称proxy对象

  • reactive定义的响应式数据是"深层次的"

  • 内部基于ES6的Proxy实现,通过代理对象操作源对象内部数据进行操作。

4.Vue3.0中的响应式原理

vue2.x的响应式

  • 实现原理:

    • 对象类型:通过Object.defineProperty()对属性的读取、修改进行拦截(数据劫持)

    • 数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了)

      js 复制代码
      Object.defineProperty(data,'count',{
          get(){},
          set(){}
      })
  • 存在问题:

    • 直接新增属性、删除属性,页面不会更新。
    • 直接通过下标修改数组,界面不会自动更新。
  • Vue2.x的解决方法:

    js 复制代码
    data(){
    	person:{
    		name:"hxx"
    	}
    }
    methods:{
    	addSex(){
    		this.$set(this.person,"sex",'女')
            //vue.set(this.person,"sex",'女')
    	},
    	dataleName(){
    		this.$delete(this.person,"name")							     //vue.$delete(this.person,"name")	
    	}
    }

vue3.x的响应式

  • 实现原理:

    • 通过Proxy(代理):拦截对象中任意属性的变化,包括:属性值的读写、属性的添加、属性的删除等【Proxy:是ES6新特性】

    • 通过Reflect(反射):对源对象的属性进行操作

    • MDN文件描述的Proxy与Reflect

      • Proxy:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_ Objects/Proxy

      • Reflect:https://developer.moilla.org/zh-CN/docs/Web/JavaScript/Reference/Global Objects/Reflect

        js 复制代码
        	//源数据
            let person = {
                name : "张三",
                age : 18
            }
            //模拟真正的Vue3.x实现响应式
            // vue3.x使用了window自带的Proxy对象来实现响应式 并配合Reflect使用
            const p = new Proxy(person,{
                // target 为源数据 即person
                //propName 为要获取的属性
                //value 只有修改才有 获取要修改什么值
                //得到拦截
                get(target,propName){
                    console.log(`有人读取了p身上的${propName}属性`)
                    return Reflect.get( target,propName)
                },
                //修改拦截
                set(target,propName,value){
                    console.log(`有人修改了p身上的${propName}属性`)
                    Reflect.set( target,propName,value)
                },
                //删除拦截
                deleteProperty(target,propName){
                    console.log(`有人删除了p身上的${propName}属性`)
                    return Reflect.defineProperty(target,propName)
          },
            })

5.reactive对比ref

  • 从定义数据角度对比:

    • ref用来定义:基本数据类型。
    • reactive用来定义:对象或者数组类型数据
    • 备注: ref也可以用来定义 对象(或数组)类型数据 ,他内部会通过reactive转为代理对象
  • 从原理角度对比:

    • ref通过Object.defineProperty()getset来实现响应式(数据劫持).
    • reactive通过使用 Proxy 来实现响应式(数据劫持),并通过 Reflect 操作 源对象内部的数据
  • 从使用角度对比:

    • ref定义的数据:操作数据需要.value,读取数据时直接读取 不需要 .value
    • reactive定义的数据: 操作数据域读取数据: 均不需要 .value
  • 推荐写法:

    js 复制代码
    setup(){
    	let data = reactve(
    		person : {},
    		student:{}
    	)
        return {
            data
        }
    }

6. setup的两个注意点

  • setup执行的时机
    • 在beforeCreate之前执行一次,this是undefined
  • setup的参数
    • props:值为一个对象,包含:组件外部传递过来,且组件内部声明接收了的属性
    • context:上下文对象
      • attrs:值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性,相当于this.$attrs
      • slots:收到的插槽内容,相当于this.$slots
      • emit:分发自定义事件的函数,相当于this.$emit

7. 计算属性与监视

1. computed函数

  • 与vue2.x中computed配置功能一致

  • 写法

    vue 复制代码
    <script>
     setup(props,context){
        //数据
        let person = reactive({
          firstName : "张",
          lastName :"三",
        })
        //计算属性 --- 简写形式
        //给person追加一个属性fullName vue3 直接追加也可以进行数据劫持 因为底层使proxy
        // person.fullName = computed(()=>{
        //   return person.firstName+"-"+person.lastName
        // })
        //计算属性 --- 完整写法
        person.fullName = computed({
          get(){
            return person.firstName+"-"+person.lastName
          },
          set(value){
            const arr= value.splice('-')
            person.fullName = arr[0]
            person.lastNameName = arr[1]
          }
        })
        return {
          person
        }
      }
    </script>

2. watch函数

  • 与vue2.x基本一致

  • 注意:

    • 监视reactive定义的响应式数据时,deep会默认强制开启,deep配置无效,oldValue是无法获取的
    • 监视reactive定义的响应式数据的某个属性时,deep配置有效
    vue 复制代码
    <template>
      <h1>当前求和为{{sum}}</h1>
      <button @click="sum++ ">点我加一</button>
      <hr/>
      <h1>{{msg}}</h1>
      <button @click="msg+='!' ">点我加一</button>
      <hr/>
      <h2>姓名:{{person.name}}</h2>
      <h2>年龄:{{person.age}}</h2>
      <h2>薪水:{{person.job.j1.salary}}</h2>
      <button @click="person.name+='~'">修改姓名</button>
      <button @click="person.age++">增加年龄</button>
      <button @click="person.job.j1.salary++">增加薪水</button>
    </template>
    
    <script>
    import {reactive, computed, ref,watch} from 'vue'
    export default {
      name: "demo",
      setup(props,context){
        //数据
        let sum = ref(0);
        let msg = ref("你好啊")
        //情况一:监听ref定义的数据
        // watch(sum,(newValue,oldValue)=>{
        //   console.log("sum的值变了")
        // })
        //情况二:监听多个ref定义的响应式数据
        // watch([sum,msg],(newValue,oldValue)=>{
        //   console.log("sum的值变了",newValue,oldValue)
        // },{immediate:true})
        let person = reactive({
          name:"hxx",
          age : 89,
          job :{
            j1:{
              salary:20
            }
          }
        })
        //情况三 监视reactive所定义的一个响应式对象
        // 1.注意此处无法使用oldValue
        // 2.注意:强制开启了深度监视(无法关掉)
    
        // watch(person,(newValue,oldValue)=>{
        //   console.log(newValue,oldValue)
        // },{deep:true})
    
        //情况四:监视reactive中对象的某个属性
        watch(()=>person.name,(newValue,oldValue)=>{
          console.log("person中的name改变了",newValue,oldValue)
        })
    
        //情况五:监视reactive中对象的某些属性
        // watch([()=>person.name,()=>person.age],(newValue,oldValue)=>{
        //   console.log("person中的name,age改变了",newValue,oldValue)
        // })
        //特殊情况
        // watch(()=>person.job,(newValue,oldValue)=>{
        //   console.log("person中的job改变了",newValue,oldValue)
        // },{deep:true}) //使用监视reactive定义的某个对象中属性时 如果这个属性是一个对象的话要开启深度监视
        return {
          sum,msg,person
        }
      }
    }
    </script>
  • ref中.value的问题

    js 复制代码
      setup(props,context){
        //数据
        let sum = ref(0);
        let msg = ref("你好啊")
    
        let person = ref({
          name:"hxx",
          age : 89,
          job :{
            j1:{
              salary:20
            }
          }
        })
        watch(sum,(newValue,oldValue)=>{
          console.log("sum改变了",newValue,oldValue)
        })
        console.log(sum)
        console.log(person)
        //将person的value暴露出来
        // watch(person.value,(newValue,oldValue)=>{
        //   console.log("person改变了",newValue,oldValue)
        // })
        //开启深度监视
        watch(person,(newValue,oldValue)=>{
          console.log("person改变了",newValue,oldValue)
        },{deep:true})
        return {
          sum,msg,person
        }

3. watchEffect函数

  • watch的套路是:既要指明监视的属性,也要指明监视的回调
  • watchEffect的套路是:不用特意的指明监视哪个属性,监视回调中用到哪个属性,那就监视哪个属性
  • watchEffect很想computed
    • 但computed更看重计算出来的值,所以必须写返回值return
    • watchEffect看重的是计算过程(回调函数函数体),所以不需要写返回值
js 复制代码
    //watchEffect全部监视
    //watchEffect 所指定的回调中 用到的数据发生变化 则重新执行回调函数
    watchEffect(()=>{
      const x1 = sum.value
      console.log("watchEffect被使用了")
    })

8. 生命周期函数

  • vue2.x与vue3.x两者的生命周期写法差不多 ,但vue3.x可以使用组合式写法

    选项式 API Hook inside setup
    beforeCreate setup
    created `
    beforeMount onBeforeMount
    mounted onMounted
    beforeUpdate onBeforeUpdate
    updated onUpdated
    beforeUnmount onBeforeUnmount
    unmounted onUnmounted
    errorCaptured onErrorCaptured
    renderTracked onRenderTracked
    renderTriggered onRenderTriggered
    activated onActivated
    deactivated onDeactivated
  • 组合式写法:

    vue 复制代码
    <script>
    import {reactive, computed, ref,onUnmounted,onBeforeUnmount,onUpdated,onBeforeUpdate,onMounted,onBeforeMount} from 'vue'
    export default {
      name: "demo",
      setup(props,context){
        //数据
        let sum = ref(0);
    
        onBeforeMount(()=>{
          console.log("onBeforeMount")
        })
        onMounted(()=>{
          console.log("onMounted")
        })
        onBeforeUpdate(()=>{
          console.log("onBeforeUpdate")
        })
        onUpdated(()=>{
          console.log("onUpdated")
        })
        onBeforeUnmount(()=>{
          console.log("onBeforeUnmount")
        })
        onUnmounted(()=>{
          console.log("onUnmounted")
        })
        return {
          sum
        }
      }
    }
    </script>

9. 自定义hook函数

  • 什么是hook? ------本质是一个函数,把setup函数中使用的Composition API进行了封装
  • 类似于vue2的mixin
  • 自定义hook的优势:复用代码,让setup中的逻辑更清楚易懂
  • 本质上是把部分代码封装起来,可以让多个.vue文件在setup中使用

10. toRef

  • 作用:创建一个ref对象,其value值指向了一个对象中的某个属性

  • 语法:const name2 = toRef(person,'name').

  • 应用:要将响应式对象中的某个属性单独提供给外部使用时

  • 扩展: toReftoRefs功能一致,但torefs可以批量创建多个ref对象,语法为:toRefs(person)

  • 我的理解:toRef就是将reactive中的对象 转化为单个数据ref 格式,以便在template渲染中可以简写。

  • 例子:

    vue 复制代码
    <template>
      <hr/>
      <h2>姓名:{{name}}</h2>
      <h2>年龄:{{age}}</h2>
      <h2>薪水:{{job.j1.salary}}</h2>
      <button @click="name+='~'">修改姓名</button>
      <button @click="age++">增加年龄</button>
      <button @click="job.j1.salary++">增加薪水</button>
    </template>
    <script>
    import {reactive, computed, ref,toRef,toRefs} from 'vue'
    export default {
      name: "demo",
      setup(props,context){
        let person = reactive({
          name:"hxx",
          age : 89,
          job :{
            j1:{
              salary:20
            }
          }
        })
        const name2 = toRef(person,'name')
        const x = toRefs(person)
        return {
         // name: toRef(person,"name"),
         // age: toRef(person,"age"), salary : toRef(person.job.j1,"salary")
          ...toRefs(person)
        }
      }
    }
    </script>

三、其他Composition API

1. shallowReactive与shallowRef

  • shallowReactive:只处理对象最外层属性的响应式(浅响应式)
  • shallowRef:只处理基本类型的响应式,不进行对象的响应式处理
  • 什么时候使用:
    • 如果有一个对象数据,结构比较深,但变化时只是外层属性变化===>shallowReactive
    • 如果有一个对象数据,后续功能不会修改该对象中的属性,而是产生新对象来代替===>shallowRef。

2. readonly与shallowReadonly

  • readonly:让一个响应式数据变为只读(深只读)

  • shallowReacdonly:让一个响应式数据变为只读的(浅只读)

  • 应用场景:不希望数据被修改时。

    js 复制代码
    import {toRefs,shallowReactive,readonly,shallowReadonly } from 'vue'
    	// person = readonly(person)
        person = shallowReadonly(person)

3. toRaw与markRaw

raw:生的

  • toRaw:

    • 作用:将一个由reactive生成的响应式对象 转为普通对象
    • 使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新
  • markRaw:

    • 作用:标记一个对象,使其永远不会再变成响应式对象。
    • 应用场景:
      • 有些值不应被设置为响应式的,例如复杂的第三方类库等。
      • 当渲染具有不可变数据源的大列表是,跳过响应式转换可以提高性能
  • 我的理解:

    • toRaw的应用应该不多,只是将响应式对象转化为普通对象。可以应用在从后端请求过来的数据,经过再封装发还回后端,就需要使用toRaw,以免源数据被改变,因为vue3的reactive是使用Proxy来进行封装响应式的。

    • markRaw与上面差不多

    • 具体写法:

      js 复制代码
      function showRewPerson() {
          let p = toRaw(person)
          console.log(p)
      }
      function addCar() {
          let car = {name:"宝马",price:50}
          person.car = markRaw(car)
      }

4. customRef

  • 定义:创建一个自定义的 ref,显式声明对其依赖追踪和更新触发的控制方式。

  • 例子:

    vue 复制代码
    <template>
      <input v-model="msg" type="text"/>
      <h2>{{msg}}</h2>
    </template>
    <script>
    import {customRef} from 'vue'
    
    export default {
      name: 'App',
      setup(){
        function  myRef(value,delay){
          //通过customRef来实现自定义
          return customRef((track, trigger)=> {
            let timer
            return {
              get() {
                console.log("得到一个值",value)
                track() //告诉vue这个value值是要被追踪的
                return value
              },
              set(newValue) {
                console.log("设置值",newValue)
                clearTimeout(timer) //防抖: 就是如果需要延迟几秒在响应数据的话 就先清除掉定时器 到最后的修改在响应
                timer=  setTimeout(()=>{
                  value = newValue
                  trigger() //告诉vue去更新页面
                },delay)
              },
            }
          })
        }
        let msg = myRef("hello",500); //使用自定义的ref
        return {
          msg
        }
      }
    }
    </script>

5. provide 与 inject

  • 作用:实现 祖与后代组件间通信

  • 套路:父组件有一个provide选项来提供数据,后代组件有一个inject选项来开始使用这些数据

  • 具体写法:

    • 祖组件中:

      js 复制代码
        setup(){
          let car =reactive( {name:"宝马",price:50} )
          provide("car",car) //向后代组件传递一个响应式数据
          return{
            ...toRefs(car)
          }
        }
    • 后代组件中:

      js 复制代码
        setup(){
          let car = inject('car')//使用jnject来接收响应式数据
          console.log(car)
          return{
            ...toRefs(car)
          }
  • 我的理解:provide与inject是一种vue3新型的组件传递数据的方式,本方式传递的数据是响应式的。

6. 响应式数据的判断

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

四、Composition API的优势

1.Options API 存在的问题

使用传统OptionsAPI中,新增或者修改一个需求,就需要分别在data,methods,computed里修改。

2.Composition API的优势

我们可以更加优雅的组织我们的代码,函数。让相关概念的代码更加有序的组织在一起。(hook)

五、新组件

1.Fragment

  • 在Vue2中:组件必须有一个根标签
  • 在Vue3中:组件可以没有根标签,内部会将多个标签包含在一个Fragment虚拟元素中
  • 好处:减少标签层级,减少内存占用

2.Teleport

  • 什么是Teleport?-----teleport是一种能够将我们的 组件html结构移动到指定位置的技术。

    html 复制代码
    <!--  /to表示移动到哪里  -->  
    	<teleport to="body" v-if="isShow"> 
          <div class="content">
            <div class="hh" >
              <h1>哈哈哈哈</h1>
              <h1>哈哈哈哈</h1>
              <h1>哈哈哈哈</h1>
              <button @click="isShow=false">点我关闭</button>
            </div>
          </div>
        </teleport>

3. Suspense

  • 等待异步组件是渲染一些额外内容,让应用有更好的用户体验

  • 使用步骤:

    • 异步引入组件:

      js' 复制代码
      import {defineAsyncComponent} from 'vue'
      const Child = defineAsyncComponent(()=>import('./components/Child'))//动态(异步)引入组件
    • 使用suspense标签包裹组件,并配置好defaultfallback

      html 复制代码
      <suspense>
          <template v-slot:default>
             <Child/>
          </template>
          <template v-slot:fallback>
             <h1>等待</h1>
          </template>
      </suspense>

六、其他

1.全局API的转移

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

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-id6kkU4G-1693207452760)(C:\\Users\\黄佳欣\\AppData\\Roaming\\Typora\\typora-user-images\\image-20230623202601762.png)

2.其他改变

  • data选项应始终被声明为一个函数

  • 过度类名的更改

    复制代码
    vue2.x写法
        .v-enter ,
        .v-leave-to {
        opacity: 0;
        }
        .v-leave,
        .v-enter-to {
        opacity: 1;
        }
        Vue3.x写法
        .v-enter- from,
        .v-leave-to {
       	 opacity: 0;6968
        }
        .v-leave- from,
        .v-enter-to {
        opacity: 1;
       }
  • 移出keyCode作为v-on的修饰符 ,同时也不支持config.keyCodes

  • 移除v-on.native修饰符

    • 父组件中绑定事件

      html 复制代码
      <my-component
      	v-on:close="handleComponentEvent"
      	v-on:click="handleNativeClickEvent"
      />
    • 子组件中声明自定义事件

      js 复制代码
      <script>
      	export default{
      		emits:['close']
      	}
      </script>
  • 移除过滤器

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

相关推荐
蒸蒸yyyyzwd3 分钟前
cpp选手备战秋招学习笔记day17
笔记·学习
谢白羽18 分钟前
SGLang模型加载过程笔记
笔记·llm·论文·agent·vllm·大模型部署·sglang
AAA代码批发商38 分钟前
Days 32 Linux 多线程:线程分离属性、多进程 vs 多线程、线程同步互斥锁、信号量
linux·笔记
陈天伟教授1 小时前
【30天学会机械制图 第15 天】项目四 学会看零件图 任务1 学习零件的基本表达方法 (1)
学习·工程制图·机械制图
从零开始的嵌入式之旅1 小时前
day31
linux·c语言·经验分享·笔记·嵌入式硬件
小张帅三代1 小时前
yolo基础学习笔记
笔记·学习·yolo
Mh1 小时前
听说我兄弟喜欢跑车,所以必须安排上
前端·javascript·vue.js
zttbee1 小时前
vue学习(白话功能版)
前端·vue.js·学习·前端框架
北风toto1 小时前
UML(统一建模语言)详细笔记
笔记·uml·软件设计师