watch监听器
如果我们需要监听数据变化,然后做一些逻辑处理,就需要用到watch
了。
如何使用? 默认情况下,监听器只能监听本身数据的变化,内部属性的变化是不能被监听的(对于对象来说)
watch: { // 侦听顶级 property a(val, oldVal) { console.log(`new: ${val}, old: ${oldVal}`) } }
如果想要监听内部数据(数组或者对象)的变化,我们可以将监听写成一个对象,并且传入一个deep: true
属性,来让他深度监听,不管内部嵌套多深,都会被监听到。这里需要注意的是,监听函数的新旧值是一模一样的,因为它们的引用指向同一个对象/数组。Vue 不会保留变更之前值的副本。如果想要使用旧数据,我们需要自己拷贝副本。
watch: { // 该回调会在任何被侦听的对象的 property 改变时被调用,不论其被嵌套多深 c: { handler(val, oldVal) { console.log('c changed') }, deep: true }, }
如果我们想要立即执行监听器,我们就需要传递一个immediate: true
属性。
watch: { // 该回调将会在侦听开始之后被立即调用 e: { handler(val, oldVal) { console.log('e changed') }, immediate: true }, }
我们还可以对一个属性传入多个监听函数。他将被依次调用
watch: { // 你可以传入回调数组,它们会被逐一调用 f: [ 'handle1', // mothods中定义的方法 function handle2(val, oldVal) { console.log('handle2 triggered') }, { handler: function handle3(val, oldVal) { console.log('handle3 triggered') } /* ... */ } ] }
我们还可以单独监听一个对象中的特定属性值的变化。注意:监听函数拿到的新旧值依旧是一样的。都是改变后的新值。而且是整个对象。而非单独监听的这个属性的值。
watch: { // 侦听单个嵌套 property 'c.d': function (val, oldVal) { // do something } }
如果我们想要监听数组中对象属性值的变化,我们不可以像上面那种监听方法,我们或者通过deep: true
来深度监听,或者在子组件中监听传递的数组中的每一项
我们还可以调用this.$watch()
来监听。并且他会返回一个函数,用于取消监听。
- 第一个参数是要侦听的源。
- 第二个参数是侦听的回调函数callback。
- 第三个参数是额外的其他选项,比如deep、immediate。
const unwatch = this.$watch("info", function (newInfo, oldInfo) { console.log(newInfo, oldInfo); }, { deep: true, immediate: true } ) // 调用它将取消监听 unwatch()
mixins 混入
组件和组件之间有时候会存在相同的代码逻辑,我们希望对相同的代码逻辑进行抽取。这个属性对于vue2中代码抽离和复用,非常有效。但是vue3中我们可以使用另外的方式来对代码进行抽离复用
在Vue2和Vue3中都支持的一种方式就是使用Mixin来完成:
- Mixin提供了一种非常灵活的方式,来分发Vue组件中的可复用功能。
- 一个Mixin对象可以包含任何组件选项。
- 当组件使用Mixin对象时,所有Mixin对象的选项将被 混合 进入该组件本身的选项中。 如果Mixin对象中的选项和组件对象中的选项发生了冲突,那么Vue会如何操作呢?
这里分成不同的情况来进行处理;
- 情况一:如果是data函数的返回值对象 如果data返回值对象的属性发生了冲突,那么会保留组件自身的数据。
- 情况二:混入生命周期钩子函数
生命周期的钩子函数会被合并到数组中,都会被调用。
- 情况三:值为对象的选项,例如 methods、components 和 directives,将被合并为同一个对象。
- 比如都有methods选项,并且都定义了方法,那么它们都会生效;
- 但是如果对象的key相同,那么会取组件对象的键值对; 如果每个组件都需要用到一段相同的逻辑,那么我们就可以使用全局混入。
- 全局的Mixin可以使用 应用app的方法 mixin 来完成注册。
- 一旦注册,那么全局混入的选项将会影响每一个组件。
app.mixin(混入的对象)
混入的代码和该组件中本身的代码执行顺序:全局混入 > 混入 > 自身组件中的代码。
Vue的组件化
我们将一个完整的页面分成很多个组件,每个组件都用于实现页面的一个功能块,而每一个组件又可以进行细分,而组件本身又可以在多个地方进行复用。前面我们的createApp函数传入了一个对象App,这个对象其实本质上就是一个组件,也是我们应用程序的根组件。
vue中的组件其实很简单,官网讲的很详细。
但是有很多需要注意的地方。接下来我们就介绍一下:
props约束
- 当传递的是对象或者数组,我们指定默认值必须是一个工厂函数。并且返回默认值对象和数组。
- 我们可以通过数组来表示可以是多个类型。
- 我们还可以通过
validator
检验函数来自定义约束类型。
- Prop 的大小写命名,最好使用
-
链接命名。
非props属性处理
当我们传递给一个组件某个属性,但是该属性并没有定义对应的props或者emits时,就称之为 非Prop的Attribute。
- 当组件有单个根节点时,非Prop的Attribute将自动添加到根节点的Attribute中
- 如果我们不希望组件的根元素继承attribute,可以在组件中设置
inheritAttrs: false
。
- 禁用attribute继承的常见情况是需要将attribute应用于根元素之外的其他元素。
- 我们可以通过 $attrs来访问所有的 非props的attribute。
- 多个根节点的attribute
- 多个根节点的attribute如果没有显示的绑定,那么会报警告,我们必须手动的指定要绑定到哪一个属性上。
子组件向父组件传参
我们可以通过emits
来对传递的事件参数进行校验,如果出现不符合的,将会出现警告。
如果我们徐想要校验参数,直接写数组就行。
emits: ["add", "sub", "addN"]
全局事件总线
主要用在非父子组件传递参数。
Vue3从实例中移除了 on、on、on、off 和 $once 方法,所以我们如果希望继续使用全局事件总线,要通过第三方的库mitt
。
// eventBus.js import mitt from 'mitt'; const emitter = mitt(); export default emitter;
注册和监听事件
// 发送事件 emitter.emit("字符串事件名", 参数) // 定义事件 emitter.on('字符串事件名', 回调函数) // 监听所有事件 emitter.on('*', (事件类型, 对应事件传递的参数) => {})
移除事件
// 移除所有事件 emitter.all.clear() // 移除指定事件 emitter.off("事件名", 移除事件的引用)
vite的简单介绍
下一代前端开发与构建工具。
他是解决上一代构建工具的问题:
- 在实际开发中,我们编写的代码往往是不能被浏览器直接识别的,比如ES6、TypeScript、Vue文件等等。所以我们必须通过构建工具来对代码进行转换、编译,类似的工具有webpack、rollup、parcel。
- 随着项目越来越大,需要处理的JavaScript呈指数级增长,模块越来越多。
- 构建工具需要很长的时间才能开启服务器,HMR也需要几秒钟才能在浏览器反应出来。
- 开发阶段不需要对代码做过多的适配,并且将浏览器不能识别的文件都转化为esModule文件,提升构建速度,开发效率提升。当项目打包时,在对项目做适配。
它主要由两部分组成:
- 一个开发服务器,它基于原生ES模块提供了丰富的内建功能,HMR的速度非常快速;
- 一套构建指令,它使用
rollup
打开我们的代码,并且它是预配置的,可以输出生成环境的优化过的静态资源;
如果我们不借助于其他工具,直接使用ES Module来开发有什么问题呢?
- 首先,当加载一个库时,加载了这个库的所有依赖模块的js代码,对于浏览器发送请求是巨大的消耗。
- 其次,我们的代码中如果有TypeScript、less、vue等代码时,浏览器并不能直接识别。
多以上述问题就需要vite来解决。
现在先安装vite
npm install vite –g #全局安装 npm install vite –D #局部安装
Vite对css的支持
- vite可以直接支持css的处理
- vite可以直接支持css预处理器,比如less,sass
- 但是需要安装less,sass编译器
npm install less -D npm install sass -D
- vite直接支持postcss的转换:
- 只需要安装postcss,并且配置
postcss.config.js
的配置文件即可
npm install postcss postcss-preset-env -D
// postcss.config.js module.exports = { plugins: [ require("postcss-preset-env") ] }
vite对Typescript的支持
- vite对TypeScript是原生支持的,它会直接使用ESBuild来完成编译:
- 只需要直接导入即可。
如果我们查看浏览器中的请求,会发现请求的依然是ts的代码:
这是因为vite中的服务器Connect会对我们的请求进行转发,获取ts编译后的代码,给浏览器返回,浏览器可以直接进行解析。 注意:在vite2中,已经不再使用Koa了,而是使用Connect来搭建的服务器
Vite对vue的支持
- Vue 3 单文件组件插件支持:@vitejs/plugin-vue
- Vue 3 JSX 插件支持:@vitejs/plugin-vue-jsx
- Vue 2 插件支持:underfin/vite-plugin-vue2 在vite.config.js中配置插件:
const vue = require('@vitejs/plugin-vue') module.exports = { plugins: [ vue() ] }
上述配置完成后,我们引入.vue文件,并启动项目,会报错。这时候需要我们安装@vue/compiler-sfc
插件即可。
Vite脚手架工具
执行以下命令即可创建一个完整的vue项目。
npm install @vitejs/create-app -g create-app 项目名
插槽
插槽的作用
通过props传递给组件一些数据,让组件来进行展示,但是为了让这个组件具备更强的通用性,我们不能将组件中的内容限制为固定的div、span等等这些元素。我们可以定义插槽,让外部可以自定义展示的内容和元素。
插槽的使用
插槽的使用过程其实是抽取共性、预留不同。我们会将共同的元素、内容依然在组件内进行封装。同时会将不同的元素使用slot
标签作为占位,让外部决定到底显示什么样的元素。
具体使用可以访问官网。
下面我们来介绍插槽使用的需要注意什么。
注意事项
- 如果想要给插槽做样式定义,我们需要给
slot
标签包裹上一个div
元素,如果直接在slot标签上写class,那么插槽替换的内容将替代完整的slot
插槽。
- 除了默认插槽外,我们传入内容是都需要在
template
标签上指定插槽的名称。
- 可以通过
v-slot:[SlotName]
方式动态绑定一个名称。
- 插槽不能访问提供插槽的组件中的属性。
- 如果我们渲染插槽的时候,需要用到子组件中的数据,我们就可以通过作用域插槽来将数据传递到父组件进行使用。数据放在一个对象中,并且将作为
v-slot
指令的值。
// 这里的item, index属性将会被放入一个对象中传递给父组件。 <slot :item="item" :index="index"></slot>
动态组件
我们如果需要根据条件切换组件,我们就可以使用component
标签。并指定一个is
属性。其中is
属性的值:
- 可以是通过component函数注册的组件。
- 在一个组件对象的components对象中注册的组件。 所以切换组件,我们就可以改变
is
属性中的值。
并且,我们还可以将组件中的props和事件写入component
组件进行传递。当渲染对应的组件时,就会将props和事件传入到对应的组件中。
<component :is="currentTab" name="zh" :age="20" @pageClick="pageClick"> </component>
组件缓存
默认情况下,我们每次离开一个组件时,该组件都会被销毁,有时候,我们希望保持组件的状态。所以就需要使用keep-alive
组件来包裹住需要缓存的组件。只要被包裹后,该组件中的状态就不会消失。
keep-alive有一些属性:(这些一般用于动态组件,路由使用。单个组件包裹一般不需要)
- include - string | RegExp | Array。只有名称匹配的组件会被缓存。
- exclude - string | RegExp | Array。任何名称匹配的组件都不会被缓存。
- max - number | string。最多可以缓存多少组件实例,一旦达到这个数字,那么缓存组件中最近没有被访问的实例会被销毁。
include 和 exclude prop 允许组件有条件地缓存:
- 二者都可以用逗号分隔字符串、正则表达式或一个数组来表示。
- 匹配首先检查组件自身的 name 选项。
如果我们想要在组件缓存进入和离开之前做一些事情的时候,我们不能调用create, unmounted
钩子函数,但是vue给我们内置了用activated
和 deactivated
这两个生命周期钩子函数。
activated() { console.log("about activated"); }, deactivated() { console.log("about deactivated"); }
异步组件
如果我们的项目过大了,对于某些组件我们希望通过异步的方式来进行加载(目的是可以对其进行分包处理),那么Vue中给我们提供了一个函数:
defineAsyncComponent
。
defineAsyncComponent接受两种类型的参数:
- 类型一:工厂函数,该工厂函数需要返回一个Promise对象。
defineAsyncComponent(() => import("./AsyncCategory.vue"))
上面的import函数返回的就是一个promise对象。是es6的语法。
- 类型二:接受一个对象类型,对异步函数进行配置。
import { defineAsyncComponent } from 'vue' const AsyncComp = defineAsyncComponent({ // 工厂函数 loader: () => import('./Foo.vue') // 加载异步组件时要使用的组件 loadingComponent: LoadingComponent, // 加载失败时要使用的组件 errorComponent: ErrorComponent, // 在显示 loadingComponent 之前的延迟 | 默认值:200(单位 ms) delay: 200, // 如果提供了 timeout ,并且加载组件的时间超过了设定值,将显示错误组件 // 默认值:Infinity(即永不超时,单位 ms) timeout: 3000, // 定义组件是否可挂起 | 默认值:true suspensible: false, /** * * @param {*} error 错误信息对象 * @param {*} retry 一个函数,用于指示当 promise 加载器 reject 时,加载器是否应该重试 * @param {*} fail 一个函数,指示加载程序结束退出 * @param {*} attempts 允许的最大重试次数 */ onError(error, retry, fail, attempts) { if (error.message.match(/fetch/) && attempts <= 3) { // 请求发生错误时重试,最多可尝试 3 次 retry() } else { // 注意,retry/fail 就像 promise 的 resolve/reject 一样: // 必须调用其中一个才能继续错误处理。 fail() } } })
结合Suspense组件使用
与之结合使用后,他会忽略提供的异步组件中的加载、错误、延迟和超时选项。 suspense组件,具有两个插槽。而且两个插槽只能有一个直接子节点。
- default默认插槽,用来显示异步组件。
- fallback插槽,用来显示加载时的组件。
<suspense> <template #default> // 异步组件 <async-category></async-category> </template> <template #fallback> <loading></loading> </template> </suspense>
teleport组件
在组件化开发中,我们封装一个组件A,在另外一个组件B中使用。那么组件A中template的元素,会被挂载到组件B中template的某个位置。最终我们的应用程序会形成一颗DOM树结构。
但是某些情况下,我们希望组件不是挂载在这个组件树上的,可能是移动到Vue app之外的其他位置。比如移动到body元素上,或者我们有其他的div#app之外的元素上。这个时候我们就可以通过teleport来完成。
Teleport是什么呢?
它是一个Vue提供的内置组件,类似于react的Portals。teleport翻译过来是心灵传输、远距离运输的意思。
它有两个属性:
- to:指定将其中的内容移动到的目标元素,可以使用选择器。
- disabled:是否禁用 teleport 的功能。但是他仍然是使用方的子组件。仍然可以传递props。同一个目标父组件可以挂载多个teleport传递的组件。按先后顺序插入。
<teleport to="#zh"> <hello-world :name="helloVal"></hello-world> </teleport> <teleport to="#zh"> <p>另外一个组件</p> </teleport> components: { HelloWorld, }, setup() { const helloVal = ref('hello') return { helloVal, } }
// HelloWorld.vue <div> <h2>{{name}}</h2> </div> props: ['name'] }
vue生命周期
每个组件都可能会经历从创建、挂载、更新、卸载等一系列的过程。在这个过程中的某一个阶段,用于可能会想要添加一些属于自己的代码逻辑(比如组件创建完后就请求一些服务器数据)。
但是我们如何可以知道目前组件正在哪一个过程呢?
Vue给我们提供了组件的生命周期函数,生命周期钩子的 this
上下文将自动绑定至实例中,因此你可以访问 data、computed 和 methods。