如何做网站的自由撰稿人/网站快速排名互点软件
组件之间的通信
经过前面几章的阅读,相信开发者已经可以搭建一个基础的 Vue 3 项目了!
但实际业务开发过程中,还会遇到一些组件之间的通信问题,父子组件通信、兄弟组件通信、爷孙组件通信,还有一些全局通信的场景。
TIP这一章节的内容,Vue 3 对比 Vue 2 的变化都比较大!
这一章就按使用场景来划分对应的章节吧,在什么场景下遇到问题,也方便快速找到对应的处理办法。
父子组件通信
爷孙组件通信
兄弟组件通信
全局组件通信
父子组件通信
父子组件通信是指,B 组件引入到 A 组件里渲染,此时 A 是 B 的父级;B 组件的一些数据需要从 A 组件拿,B 组件有时也要告知 A 组件一些数据变化情况。
他们之间的关系如下, Child.vue 是直接挂载在 Father.vue 下面:
# 父组件
Father.vue
│ # 子组件
└─Child.vue
常用的方法有:
为了方便阅读,下面的父组件统一叫 Father.vue ,子组件统一叫 Child.vue 。
WARNING在 Vue 2 ,有的开发者可能喜欢用 $attrs / $listeners 来进行通信,但该方案在 Vue 3 已经移除了,详见 移除 $listeners 。
props / emits
这是 Vue 跨组件通信最常用,也是基础的一个方案,它的通信过程是:
1.父组件 Father.vue 通过 props 向子组件 Child.vue 传值
2.子组件 Child.vue 则可以通过 emits 向父组件 Father.vue 发起事件通知
最常见的场景就是统一在父组件发起 AJAX 请求,拿到数据后,再根据子组件的渲染需要传递不同的 props 给不同的子组件使用。
下发 props
注:这一小节的步骤是在 Father.vue 里操作。
下发的过程是在 Father.vue 里完成的,父组件在向子组件下发 props 之前,需要导入子组件并启用它作为自身的模板,然后在 setup 里处理好数据并 return 给 用。
在 Father.vue 的 script /> 里:
// Father.vue
import { defineComponent } from 'vue'
import Child from '@cp/Child.vue'interface Member {id: numbername: string
}export default defineComponent({// 需要启用子组件作为模板components: {Child,},// 定义一些数据并 `return` 给 `<template />` 用setup() {const userInfo: Member = {id: 1,name: 'Petter',}// 不要忘记 `return` ,否则 `<template />` 拿不到数据return {userInfo,}},
})
然后在 Father.vue 的 template /> 这边拿到 return 出来的数据,把要传递的数据通过属性的方式绑定在组件标签上。
<!-- Father.vue -->
<template><Childtitle="用户信息":index="1":uid="userInfo.id":user-name="userInfo.name"/>
</template>
这样就完成了 props 数据的下发。
在 template /> 绑定属性这里,如果是普通的字符串,比如上面的 title,则直接给属性名赋值就可以。
如果是变量名,或者其他类型如 number 、 boolean 等,比如上面的 index,则需要通过属性动态绑定的方式来添加,使用 v-bind: 或者 : 符号进行绑定。
另外官方文档推荐对 camelCase 风格(小驼峰)命名的 props ,在绑定时使用和 HTML attribute 一样的 kebab-case 风格(短横线),例如使用 user-name 代替 userName 传递,详见官网的 传递 prop 的细节 一节。
接收 props
注:这一小节的步骤是在 Child.vue 里操作。
接收的过程是在 Child.vue 里完成的,在 script /> 部分,子组件通过与 setup 同级的 props 来接收数据。
它可以是一个 string[] 数组,把要接受的变量名放到这个数组里,直接放进来作为数组的 item :
// Child.vue
export default defineComponent({props: ['title', 'index', 'userName', 'uid'],
})
但这种情况下,使用者不知道这些属性的使用限制,例如是什么类型的值、是否必传等等。
带有类型限制的 props
注:这一小节的步骤是在 Child.vue 里操作。
和 TypeScript 一样,类型限制可以为程序带来更好的健壮性, Vue 的 props 也支持增加类型限制。
相对于传递一个 string[] 类型的数组,更推荐的方式是把 props 定义为一个对象,以对象形式列出,每个 Property 的名称和值分别是各自的名称和类型,只有合法的类型才允许传入。
TIP注意,和 TS 的类型定义不同, props 这里的类型,首字母需要大写,也就是 JavaScript 的基本类型。
支持的类型有:
了解了基本的类型限制用法之后,接下来给 props 加上类型限制:
// Child.vue
export default defineComponent({props: {title: String,index: Number,userName: String,uid: Number,},
})
现在如果传入不正确的类型,程序就会抛出警告信息,告知开发者必须正确传值。
如果需要对某个 Prop 允许多类型,比如这个 uid 字段,可能是数值,也可能是字符串,那么可以在类型这里,使用一个数组,把允许的类型都加进去。
// Child.vue
export default defineComponent({props: {// 单类型title: String,index: Number,userName: String,// 这里使用了多种类型uid: [Number, String],},
})
可选以及带有默认值的 props
注:这一小节的步骤是在 Child.vue 里操作。
所有 props 默认都是可选的,如果不传递具体的值,则默认值都是 undefined ,可能引起程序运行崩溃, Vue 支持对可选的 props 设置默认值,也是通过对象的形式配置 props 的选项。
其中支持配置的选项有:
了解了配置选项后,接下来再对 props 进行改造,将其中部分选项设置为可选,并提供默认值:
// Child.vue
export default defineComponent({props: {// 可选,并提供默认值title: {type: String,required: false,default: '默认标题',},// 默认可选,单类型index: Number,// 添加一些自定义校验userName: {type: String,// 在这里校验用户名必须至少 3 个字validator: (v) => v.length >= 3,},// 默认可选,但允许多种类型uid: [Number, String],},
})
使用 props
注:这一小节的步骤是在 Child.vue 里操作。
在 template /> 部分, Vue 3 的使用方法和 Vue 2 是一样的,比如要渲染父组件传入的 props :
<!-- Child.vue -->
<template><p>标题:{{ title }}</p><p>索引:{{ index }}</p><p>用户id:{{ uid }}</p><p>用户名:{{ userName }}</p>
</template>
但是 script /> 部分,变化非常大!
在 Vue 2 里,只需要通过 this.uid 、 this.userName 就可以使用父组件传下来的 Prop ,但是 Vue 3 没有了 this ,所以是通过 setup 的入参进行操作。
// Child.vue
export default defineComponent({props: {title: String,index: Number,userName: String,uid: Number,},// 在这里需要添加一个入参setup(props) {// 该入参包含了当前组件定义的所有 propsconsole.log(props)},
})
关于 Setup 函数的第一个入参 props :
1.该入参包含了当前组件定义的所有 props (如果父组件 Father.vue 传进来的数据在 Child.vue 里未定义,不仅不会拿到,并且在控制台会有警告信息)。
2.该入参可以随意命名,比如可以写成一个下划线 _ ,通过 _.uid 也可以拿到数据,但是语义化命名是一个良好的编程习惯。
3.该入参具备响应性,父组件修改了传递下来的值,子组件也会同步得到更新,因此请不要直接解构,可以通过 toRef 或 toRefs API 转换为响应式变量
传递非 props 的属性
上一小节最后有一句提示是:
如果父组件 Father.vue 传进来的数据在 Child.vue 里未定义,不仅不会拿到,并且在控制台会有警告信息。
这种情况虽然无法从 props 里拿到对应的数据,但也不意味着不能传递任何未定义的属性数据,在父组件,除了可以给子组件绑定 props ,还可以根据实际需要去绑定一些特殊的属性。
比如给子组件设置 class、id,或者 data-xxx 之类的一些自定义属性,如果子组件 Child.vue 的 template /> 里只有一个根节点,那么这些属性默认会自动继承并渲染在 Node 节点上。
假设当前在子组件 Child.vue 是如下这样只有一个根节点,并且未接收任何 props :
TIP如果已安装 Vue VSCode Snippets 这个 VSCode 插件,可以在空的 .vue 文件里输入 v3 ,在出现的代码片段菜单里选择 vbase-3-ts 生成一个 Vue 组件的基础代码片段。
<!-- Child.vue -->
<template><div class="child">子组件</div>
</template><script lang="ts">
import { defineComponent } from 'vue'export default defineComponent({setup() {return {}},
})
</script><style scoped>
.child {width: 100%;
}
</style>
在 Father.vue 里对 Child.vue 传递了多个属性:
<!-- Father.vue -->
<template><Childid="child-component"class="class-name-from-father":keys="['foo', 'bar']":obj="{ foo: 'bar' }"data-hash="b10a8db164e0754105b7a99be72e3fe5"/>
</template>
回到浏览器,通过 Chrome 的审查元素可以看到子组件 Child.vue 在渲染后,按照 HTML 属性的渲染规则生成了多个属性:
<!-- Child.vue 在浏览器里渲染后的 HTML DOM 结构 -->
<divclass="child class-name-from-father"id="child-component"keys="foo,bar"obj="[object Object]"data-hash="b10a8db164e0754105b7a99be72e3fe5"data-v-2dcc19c8=""data-v-7eb2bc79=""
>子组件
</div>
TIP其中有两个以 data-v- 开头的属性是 <style /> 标签开启了 Style Scoped 功能自动生成的 Hash 值。
可以在 Child.vue 配置 inheritAttrs 为 false 来屏蔽这些非 props 属性的渲染。
// Child.vue
export default defineComponent({inheritAttrs: false,setup() {// ...},
})
关闭了 之后,现在的 DOM 结构如下,只保留了两个由 Style Scoped 生成的 Hash 值:
<!-- Child.vue 在浏览器里渲染后的 HTML DOM 解构 -->
<div class="child" data-v-2dcc19c8="" data-v-7eb2bc79="">子组件</div>
这一类非 props 属性通常称之为 attrs 。
刚接触 Vue 的开发者可能容易混淆这两者,确实是非常接近,都是由父组件传递,由子组件接收,支持传递的数据类型也一样,但为什么一部分是在 props 获取,一部分在 attrs 获取呢?笔者给出一个比较容易记忆的方式,不一定特别准确,但相信可以帮助开发者加深两者的区别理解。
根据它们的缩写,其实是可以知道 Prop 是指 Property ,而 Attr 是指 Attribute ,虽然都是 “属性” ,但 Property 更接近于事物本身的属性,因此需要在组件里声明,而 Attribute 更偏向于赋予的属性,因此用于指代父组件传递的其他未被声明为 Property 的属性。
获取非 props 的属性
注:这一小节的步骤是在 Child.vue 里操作。
在上一小节 传递非 props 的属性 已经在父组件 Father.vue 里向子组件 Child.vue 传递了一些 attrs 自定义属性,在子组件里想要拿到这些属性,使用原生 JavaScript 操作是需要通过 Element.getAttribute() 方法,但 Vue 提供了更简单的操作方式。
在 Child.vue 里,可以通过 setup 的第二个参数 context 里的 attrs 来获取到这些属性,并且父组件传递了什么类型的值,获取到的也是一样的类型,这一点和使用 Element.getAttribute() 完全不同。
// Child.vue
export default defineComponent({setup(props, { attrs }) {// `attrs` 是个对象,每个 Attribute 都是它的 `key`console.log(attrs.id) // child-componentconsole.log(attrs.class) // class-name-from-father// 传递数组会被保留类型,不会被转换为 `key1,key2` 这样的字符串// 这一点与 `Element.getAttribute()` 完全不同console.log(attrs.keys) // ['foo', 'bar']// 传递对象也可以正常获取console.log(attrs.obj) // {foo: 'bar'}// 如果传下来的 Attribute 带有短横线,需要通过这种方式获取console.log(attrs['data-hash']) // b10a8db164e0754105b7a99be72e3fe5},
})
TIP子组件不论是否设置 inheritAttrs 属性,都可以通过 attrs 拿到父组件传递下来的数据,但是如果要使用 Element.getAttribute() 则只有当 inheritAttrs 为 true 的时候才可以,因为此时在 DOM 上才会渲染这些属性。
与 Vue 2 的 template /> 只能有一个根节点不同, Vue 3 允许多个根节点,多个根节点的情况下,无法直接继承这些 attrs 属性(在 inheritAttrs: true 的情况也下无法默认继承),需要在子组件 Child.vue 里通过 v-bind 绑定到要继承在节点上。
可以通过 Vue 实例属性 $attrs 或者从 setup 函数里把 attrs return 出来使用
<!-- Child.vue -->
<template><!-- 默认不会继承属性 --><div class="child">不会继承</div><!-- 绑定后可继承, `$attrs` 是一个 Vue 提供的实例属性 --><div class="child" v-bind="$attrs">使用 $attrs 继承</div><!-- 绑定后可继承, `attrs` 是从 `setup` 里 `return` 出来的变量 --><div class="child" v-bind="attrs">使用 attrs 继承</div>
</template><script lang="ts">
import { defineComponent } from 'vue'export default defineComponent({setup(props, { attrs }) {return {attrs,}},
})
</script>
绑定 emits
注:这一小节的步骤是在 Father.vue 里操作。
如果父组件 Father.vue 需要获取子组件 Child.vue 的数据更新情况,可以由子组件通过 emits 进行通知,下面这个更新用户年龄的例子可以学习如何给子组件绑定 emit 事件。
事件的逻辑是由父组件决定的,因此需要在父组件 Father.vue 的 script /> 里先声明数据变量和一个更新函数,并且这个更新函数通常会有一个入参作为数据的新值接收。
在本例子里,父组件声明了一个 updateAge 方法,它接受一个入参 newAge ,代表新的年龄数据,这个入参的值将由子组件 Child.vue 在触发 emits 时传入。
因为还需要在 template /> 部分绑定给子组件,所以请记得 return 出来。
// Father.vue
import { defineComponent, reactive } from 'vue'
import Child from '@cp/Child.vue'interface Member {id: numbername: stringage: number
}export default defineComponent({components: {Child,},setup() {const userInfo: Member = reactive({id: 1,name: 'Petter',age: 0,})/*** 声明一个更新年龄的方法* @param newAge - 新的年龄,由子组件触发 emits 时传递*/function updateAge(newAge: number) {userInfo.age = newAge}return {userInfo,updateAge,}},
})
再看 Father.vue 的 template /> 部分,和 Click 事件使用 @click 一样,自定义的 emits 事件也是通过 v-on 或者是 @ 来绑定:
<!-- Father.vue -->
<template><Child @update-age="updateAge" />
</template>
和 props 一样,官方文档也推荐将 camelCase 风格(小驼峰)命名的函数,在绑定时使用 kebab-case 风格(短横线),例如使用 update-age 代替 updateAge 传递。
接收并调用 emits
注:这一小节的步骤是在 Child.vue 里操作。
和 props 一样,可以指定是一个数组,把要接收的 emit 事件名称写进去:
// Child.vue
export default defineComponent({emits: ['update-age'],
})
和 props 不同,通常情况下 emits 这样配置就足够使用了。
接下来如果子组件需要更新数据并通知父组件,可以使用 setup 第二个参数 context 里的 emit 方法触发:
// Child.vue
export default defineComponent({emits: ['update-age'],setup(props, { emit }) {// 通知父组件将年龄设置为 `2`emit('update-age', 2)},
})
emit 方法最少要传递一个参数:事件名称。
事件名称是指父组件 Father.vue 绑定事件时 @update-age=“updateAge” 里的 update-age ,如果改成 @hello=“updateAge” ,那么事件名称就需要使用 hello ,一般情况下事件名称和更新函数的名称会保持一致,方便维护。
对于需要更新数据的情况, emit 还支持传递更多的参数,对应更新函数里的入参,所以可以看到上面例子里的 emit(‘update-age’, 2) 有第二个参数,传递了一个 2 的数值,就是作为父组件 updateAge 的入参 newAge 传递。
如果需要通信的数据很多,建议第二个入参使用一个对象来管理数据,例如父组件调整为:
// Father.vue
function updateInfo({ name, age }: Member) {// 当 `name` 变化时更新 `name` 的值if (name && name !== userInfo.name) {userInfo.name = name}// 当 `age` 变化并且新值在正确的范围内时,更新 `age` 的值if (age > 0 && age !== userInfo.age) {userInfo.age = age}
}
子组件在传递新数据时,就应该使用对象的形式传递:
// Child.vue
emit('update-info', {name: 'Tom',age: 18,
})
这对于更新表单等数据量较多的场景非常好用。
接收 emits 时做一些校验
注:这一小节的步骤是在 Child.vue 里操作。
和 props 一样,子组件在接收 emits 时也可以对这些事件做一些验证,这个时候就需要将 emits 配置为对象,然后把事件名称作为 key , value 则对应为一个用来校验的方法。
还是用回上文那个更新年龄的方法,如果需要增加一个条件:当达到成年人的年龄时才会更新父组件的数据,那么就可以将 emits 调整为:
// Child.vue
export default defineComponent({emits: {// 需要校验'update-age': (age: number) => {// 写一些条件拦截,返回 `false` 表示验证不通过if (age < 18) {console.log('未成年人不允许参与')return false}// 通过则返回 `true`return true},// 一些无需校验的,设置为 `null` 即可'update-name': null,},
})
接下来如果提交 emit(‘update-age’, 2) ,因为不满足验证条件,浏览器控制台将会出现一段 [Vue warn]: Invalid event arguments: event validation failed for event “update-age”. 这样的警告信息。
v-model / emits
相对于 props / emits 这一对通信方案,使用 v-model 的方式更为简单:
1.在 Father.vue ,通过 v-model 向 Child.vue 传值
2.Child.vue 通过自身设定的 emits 向 Father.vue 通知数据更新
v-model 的用法和 props 非常相似,但是很多操作上更为简化,但操作简单带来的 “副作用” ,就是功能上也没有 props 那么多。
绑定 v-model
注:这一小节的步骤是在 Father.vue 里操作
和下发 props 的方式类似,都是在子组件上绑定 Father.vue 定义好的数据,这是绑定一个数据的例子:
<!-- Father.vue -->
<template><Child v-model:username="userInfo.name" />
</template>
和 Vue 2 不同, Vue 3 可以直接绑定 v-model ,而无需在子组件指定 model 选项 ,并且 Vue 3 的 v-model 需要使用英文冒号 : 指定要绑定的属性名,同时也支持绑定多个 v-model 。
如果要绑定多个数据,写多个 v-model 即可:
<!-- Father.vue -->
<template><Childv-model:uid="userInfo.id"v-model:username="userInfo.name"v-model:age="userInfo.age"/>
</template>
看到这里应该能明白了,一个 v-model 其实就是一个 prop ,它支持的数据类型和 prop 是一样的,所以子组件在接收数据的时候,完全按照 props 去定义就可以了。
点击回顾:接收 props ,了解在 Child.vue 如何接收 props,以及相关的 props 类型限制等部分内容。
配置 emits
注:这一小节的步骤是在 Child.vue 里操作。
虽然 v-model 的配置和 props 相似,但是为什么出这么两个相似的东西?自然是为了简化一些开发上的操作。
使用 props / emits ,如果要更新父组件的数据,还需要在父组件声明一个更新函数并绑定事件给子组件,才能够更新。
而使用 v-model / emits ,无需在父组件声明更新函数,只需要在子组件 Child.vue 里通过 update: 前缀加上 v-model 的属性名这样的格式,即可直接定义一个更新事件。
// Child.vue
export default defineComponent({props: {uid: Number,username: String,age: Number,},// 注意这里的 `update:` 前缀emits: ['update:uid', 'update:username', 'update:age'],
})
这里的 update 后面的属性名,支持驼峰写法,这一部分和 Vue 2 的使用是相同的。
在配置 emits 时,也可以对数据更新做一些校验,配置方式和讲解 props / emits 时 接收 emits 时做一些校验 这一小节的操作是一样的。
在 Child.vue 配置好 emits 之后,就可以在 setup 里直接操作数据的更新了:
// Child.vue
export default defineComponent({setup(props, { emit }) {// 2s 后更新用户名setTimeout(() => {emit('update:username', 'Tom')}, 2000)},
})
子组件通过调用 emit(‘update:xxx’) 即可让父组件更新对应的数据。
ref / emits
在学习 响应式 API 之 ref 的时候,已讲解过 ref 是可以用在 DOM 元素与子组件 上面,所以也可以使用 ref 配合 emits 完成父子组件的通信。
父组件操作子组件
注:这一小节的步骤是在 Father.vue 里操作。
父组件可以给子组件绑定 ref 属性,然后通过 Ref 变量操作子组件的数据或者调用子组件里面的方法。
先在 template /> 处给子组件标签绑定 ref 属性:
<!-- Father.vue -->
<template><Child ref="child" />
</template>
然后在 script /> 部分定义好对应的变量名称 child (记得要 return 出来哦),即可通过该变量操作子组件上的变量或方法:
// Father.vue
import { defineComponent, onMounted, ref } from 'vue'
import Child from '@cp/Child.vue'export default defineComponent({components: {Child,},setup() {// 给子组件定义一个 `ref` 变量const child = ref<typeof Child>()// 请保证视图渲染完毕后再执行操作onMounted(async () => {// 执行子组件里面的 AJAX 请求函数await child.value!.queryList()// 显示子组件里面的弹窗child.value!.isShowDialog = true})// 必须 `return` 出去才可以给到 `<template />` 使用return {child,}},
})
需要注意的是,在子组件 Child.vue 里,变量和方法也需要在 setup 里 return 出来才可以被父组件调用到。
子组件通知父组件
子组件如果想主动向父组件通讯,也需要使用 emits ,详细的配置方法可见:绑定 emits
爷孙组件通信
顾名思义,爷孙组件是比 父子组件通信 要更深层次的引用关系(也有称之为 “隔代组件” )。
C 组件被引入到 B 组件里, B 组件又被引入到 A 组件里渲染,此时 A 是 C 的爷爷级别(可能还有更多层级关系),它们之间的关系可以假设如下:
Grandfather.vue
└─Son.vue└─Grandson.vue
可以看到 Grandson.vue 并非直接挂载在 Grandfather.vue 下面,他们之间还隔着至少一个 Son.vue (在实际业务中可能存在更多层级),如果使用 props ,只能一级组件一级组件传递下去,就太繁琐了。
因此需要更直接的通信方式来解决这种问题,这一 Part 就是讲一讲 C 和 A 之间的数据传递,常用的方法有:
因为上下级的关系的一致性,爷孙组件通信的方案也适用于 父子组件通信 ,只需要把爷孙关系换成父子关系即可,为了方便阅读,下面的爷组件统一叫 Grandfather.vue,子组件统一叫 Grandson.vue 。
provide / inject
这个通信方式也是有两部分:
1.Grandfather.vue 通过 provide 向孙组件 Grandson.vue 提供数据和方法
2.Grandson.vue 通过 inject 注入爷爷组件 Grandfather.vue 的数据和方法
无论组件层次结构有多深,发起 provide 的组件都可以作为其所有下级组件的依赖提供者。
Vue 3 的这一部分内容对比 Vue 2 来说变化很大,但使用起来其实也很简单,开发者学到这里不用慌,它们之间也有相同的地方:
1.爷组件不需要知道哪些子组件使用它 provide 的数据
2.子组件不需要知道 inject 的数据来自哪里
另外要切记一点就是: provide 和 inject 绑定并不是可响应的,这是刻意为之的,除非传入了一个可侦听的对象。
发起 provide
注:这一小节的步骤是在 Grandfather.vue 里操作。
先来回顾一下 Vue 2 的用法:
export default {// 在 `data` 选项里定义好数据data() {return {tags: ['中餐', '粤菜', '烧腊'],}},// 在 `provide` 选项里添加要提供的数据provide() {return {tags: this.tags,}},
}
旧版的 provide 用法和 data 类似,都是配置为一个返回对象的函数,而 Vue 3 的新版 provide ,和 Vue 2 的用法区别比较大。
在 Vue 3 , provide 需要导入并在 setup 里启用,并且现在是一个全新的方法,每次要 provide 一个数据的时候,就要单独调用一次。
provide 的 TS 类型如下:
// `provide` API 本身的类型
function provide<T>(key: InjectionKey<T> | string, value: T): void// 入参 `key` 的其中一种类型
interface InjectionKey<T> extends Symbol {}
每次调用 provide 的时候都需要传入两个参数:
参数 说明
key 数据的名称
value 数据的值
其中 key 一般使用 string 类型就可以满足大部分业务场景,如果有特殊的需要(例如开发插件时可以避免和用户的业务冲突),可以使用 InjectionKey 类型,这是一个继承自 Symbol 的泛型:
import type { InjectionKey } from 'vue'
const key = Symbol() as InjectionKey<string>
还需要注意的是, provide 不是响应式的,如果要使其具备响应性,需要传入响应式数据,详见:响应性数据的传递与接收 。
下面来试试在爷组件 Grandfather.vue 里创建数据 provide 下去:
// Grandfather.vue
import { defineComponent, provide, ref } from 'vue'export default defineComponent({setup() {// 声明一个响应性变量并 provide 其自身// 孙组件获取后可以保持响应性const msg = ref('Hello World!')provide('msg', msg)// 只 provide 响应式变量的值// 孙组件获取后只会得到当前的值provide('msgValue', msg.value)// 声明一个方法并 providefunction printMsg() {console.log(msg.value)}provide('printMsg', printMsg)},
})
接收 inject
注:这一小节的步骤是在 Grandson.vue 里操作。
也是先回顾一下在 Vue 2 里的用法,和接收 props 类似:
export default {// 通过 `inject` 选项获取inject: ['tags'],mounted() {console.log(this.tags)},
}
Vue 3 的新版 inject 和 Vue 2 的用法区别也是比较大,在 Vue 3 , inject 和 provide 一样,也是需要先导入然后在 setup 里启用,也是一个全新的方法,每次要 inject 一个数据的时候,也是要单独调用一次。
另外还有一个特殊情况需要注意,当 Grandson.vue 的父级、爷级组件都 provide 了相同名字的数据下来,那么在 inject 的时候,会优先选择离它更近的组件的数据。
根据不同的场景, inject 可以接受不同数量的入参,入参类型也各不相同。
默认用法
默认情况下, inject API 的 TS 类型如下:
function inject<T>(key: InjectionKey<T> | string): T | undefined
每次调用时只需要传入一个参数:
参数 类型 说明
key string 与 provide 相对应的数据名称
接下来看看如何在孙组件里 inject 爷组件 provide 下来的数据:
// Grandson.vue
import { defineComponent, inject } from 'vue'
import type { Ref } from 'vue'export default defineComponent({setup() {// 获取响应式变量const msg = inject<Ref<string>>('msg')console.log(msg!.value)// 获取普通的字符串const msgValue = inject<string>('msgValue')console.log(msgValue)// 获取函数const printMsg = inject<() => void>('printMsg')if (typeof printMsg === 'function') {printMsg()}},
})
可以看到在每个 inject 都使用尖括号 <> 添加了相应的 TS 类型,并且在调用变量的时候都进行了判断,这是因为默认的情况下, inject 除了返回指定类型的数据之外,还默认带上 undefined 作为可能的值。
如果明确数据不会是 undefined ,也可以在后面添加 as 关键字指定其 TS 类型,这样 TypeScript 就不再因为可能出现 undefined 而提示代码有问题。
// Grandson.vue
import { defineComponent, inject } from 'vue'
import type { Ref } from 'vue'export default defineComponent({setup() {// 获取响应式变量const msg = inject('msg') as Ref<string>console.log(msg.value)// 获取普通的字符串const msgValue = inject('msgValue') as stringconsole.log(msgValue)// 获取函数const printMsg = inject('printMsg') as () => voidprintMsg()},
})
设置默认值
inject API 还支持设置默认值,可以接受更多的参数。
默认情况下,只需要传入第二个参数指定默认值即可,此时它的 TS 类型如下,
function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T
对于不可控的情况,建议在 inject 时添加一个兜底的默认值,防止程序报错:
// Grandson.vue
import { defineComponent, inject, ref } from 'vue'
import type { Ref } from 'vue'export default defineComponent({setup() {// 获取响应式变量const msg = inject<Ref<string>>('msg', ref('Hello'))console.log(msg.value)// 获取普通的字符串const msgValue = inject<string>('msgValue', 'Hello')console.log(msgValue)// 获取函数const printMsg = inject<() => void>('printMsg', () => {console.log('Hello')})printMsg()},
})
需要注意的是, inject 的什么类型的数据,其默认值也需要保持相同的类型。
工厂函数选项
inject API 在第二个 TS 类型的基础上,还有第三个 TS 类型,可以传入第三个参数:
function inject<T>(key: InjectionKey<T> | string,defaultValue: () => T,treatDefaultAsFactory?: false
): T
当第二个参数是一个工厂函数,那么可以添加第三个值,将其设置为 true ,此时默认值一定会是其 return 的值。
在 Grandson.vue 里新增一个 inject ,接收一个不存在的函数名,并提供一个工厂函数作为默认值:
// Grandson.vue
import { defineComponent, inject } from 'vue'interface Food {name: stringcount: number
}export default defineComponent({setup() {// 获取工厂函数const getFood = inject<() => Food>('nonexistentFunction', () => {return {name: 'Pizza',count: 1,}})console.log(typeof getFood) // functionconst food = getFood()console.log(food) // {name: 'Pizza', count: 1}},
})
此时因为第三个参数默认为 Falsy 值,所以可以得到一个函数作为默认值,并可以调用该函数获得一个 Food 对象。
如果将第三个参数传入为 true ,再运行程序则会在 const food = getFood() 这一行报错:
// Grandson.vue
import { defineComponent, inject } from 'vue'interface Food {name: stringcount: number
}export default defineComponent({setup() {// 获取工厂函数const getFood = inject<() => Food>('nonexistentFunction',() => {return {name: 'Pizza',count: 1,}},true)console.log(typeof getFood) // object// 此时下面的代码无法运行// 报错 Uncaught (in promise) TypeError: getMsg is not a functionconst food = getFood()console.log(food)},
})
因为此时第三个入参告知 inject ,默认值是一个工厂函数,因此默认值不再是函数本身,而是函数的返回值,所以 typeof getFood 得到的不再是一个 function 而是一个 object 。
这个参数对于需要通过工厂函数返回数据的情况非常有用!
兄弟组件通信
兄弟组件是指两个组件都挂载在同一个 Father.vue 下,但两个组件之间并没有什么直接的关联,先看看它们的关系:
Father.vue
├─Brother.vue
└─LittleBrother.vue
这种层级关系下,如果组件之间要进行通信,目前通常有这两类选择:
【不推荐】先把数据传给 Father.vue ,再使用 父子组件通信 方案处理
【推荐】借助 全局组件通信 的方案达到目的
下面的内容将进入全局通信的讲解。
全局组件通信
全局组件通信是指项目下两个任意组件,不管是否有直接关联(例如父子关系、爷孙关系)都可以直接进行交流的通信方案。
举个例子,像下面这种项目结构, B2.vue 可以采用全局通信方案直接向 D2.vue 发起交流,而无需经过它们各自的父组件。
A.vue
├─B1.vue
├───C1.vue
├─────D1.vue
├─────D2.vue
├───C2.vue
├─────D3.vue
└─B2.vue
常用的方法有:
EventBus
EventBus 通常被称之为 “全局事件总线” ,是用在全局范围内通信的一个常用方案,在 Vue 2 时期该方案非常流行,其特点就是 “简单” 、 “灵活” 、 “轻量级” 。
回顾 Vue 2
在 Vue 2 ,使用 EventBus 无需导入第三方插件,可以在项目下的 libs 文件夹里,创建一个名为 eventBus.ts 的文件,导出一个新的 Vue 实例即可。
// src/libs/eventBus.ts
import Vue from 'vue'
export default new Vue()
上面短短两句代码已完成了一个 EventBus 的创建,接下来就可以开始进行通信。
先在负责接收事件的组件里,利用 Vue 的生命周期,通过 eventBus.on添加事件侦听,通过eventBus.on 添加事件侦听,通过 eventBus.on添加事件侦听,通过eventBus.off 移除事件侦听。
import eventBus from '@libs/eventBus'export default {mounted() {// 在组件创建时,添加一个名为 `hello` 的事件侦听eventBus.$on('hello', () => {console.log('Hello World')})},beforeDestroy() {// 在组件销毁前,通过 `hello` 这个名称移除该事件侦听eventBus.$off('hello')},
}
然后在另外一个组件里通过 eventBus.$emit 触发事件侦听。
import eventBus from './eventBus'export default {methods: {sayHello() {// 触发名为 `hello` 的事件eventBus.$emit('hello')},},
}
这样一个简单的全局方案就完成了。
了解 Vue 3
Vue 3 应用实例不再实现事件触发接口,因此移除了 $on 、 $off 和 $once 这几个事件 API ,无法像 Vue 2 一样利用 Vue 实例创建 EventBus 。
根据官方文档在 事件 API 迁移策略 的推荐,可以使用 mitt 或者 tiny-emitter 等第三方插件实现 EventBus 。
创建 Vue 3 的 EventBus
这里以 mitt 为例,示范如何创建一个 Vue 3 的 EventBus ,首先需要安装它。
npm i mitt
然后在 src/libs 文件夹下,创建一个名为 eventBus.ts 的文件,文件内容和 Vue 2 的写法其实是一样的,只不过是把 Vue 实例换成了 mitt 实例。
// src/libs/eventBus.ts
import mitt from 'mitt'
export default mitt()
接下来就可以定义通信的相关事件了,常用的 API 和参数如下:
方法名称 作用:
on 注册一个侦听事件,用于接收数据
emit 调用方法发起数据传递
off 用来移除侦听事件
on 的参数:
这里的 handler 建议使用具名函数,因为匿名函数无法销毁。
emit 的参数:
off 的参数:
更多的 API 可以查阅 插件的官方文档 ,在了解了最基本的用法之后,来开始配置一对组件通信。
创建和移除侦听事件
在需要暴露交流事件的组件里,通过 on 配置好接收方法,同时为了避免路由切换过程中造成事件多次被绑定,从而引起多次触发,需要在适当的时机 off 掉:
import { defineComponent, onBeforeUnmount } from 'vue'
import eventBus from '@libs/eventBus'export default defineComponent({setup() {// 声明一个打招呼的方法function sayHi(msg = 'Hello World!') {console.log(msg)}// 启用侦听eventBus.on('sayHi', sayHi)// 在组件卸载之前移除侦听onBeforeUnmount(() => {eventBus.off('sayHi', sayHi)})},
})
关于销毁的时机,可以参考 组件的生命周期 。
调用侦听事件
在需要调用交流事件的组件里,通过 emit 进行调用:
import { defineComponent } from 'vue'
import eventBus from '@libs/eventBus'export default defineComponent({setup() {// 调用打招呼事件,传入消息内容eventBus.emit('sayHi', 'Hello')},
})
旧项目升级 EventBus
在 Vue 3 的 EventBus 里,可以看到它的 API 和旧版是非常接近的,只是去掉了 $ 符号。
如果要对旧的项目进行升级改造,由于原来都是使用了 $on 、 $emit 等旧的 API ,一个一个组件去修改成新的 API 容易遗漏或者全局替换出错。
因此可以在创建 eventBus.ts 的时候,通过自定义一个 eventBus 对象来挂载 mitt 的 API 。
在 eventBus.ts 里,改成以下代码:
// src/libs/eventBus.ts
import mitt from 'mitt'// 初始化一个 mitt 实例
const emitter = mitt()// 在导出时使用旧的 API 名称去调用 mitt 的 API
export default {$on: (...args) => emitter.on(...args),$emit: (...args) => emitter.emit(...args),$off: (...args) => emitter.off(...args),
}
这样在组件里就可以继续使用 eventBus.on、eventBus.on 、eventBus.on、eventBus.emit 等旧 API ,不会影响旧项目的升级使用。
Reative State
在 Vue 3 里,使用响应式的 reative API 也可以实现一个小型的状态共享库,如果运用在一个简单的 H5 活动页面这样小需求里,完全可以满足使用。
创建状态中心
首先在 src 目录下创建一个 state 文件夹,并添加一个 index.ts 文件,写入以下代码
// src/state/index.ts
import { reactive } from 'vue'// 如果有多个不同业务的内部状态共享
// 使用具名导出更容易维护
export const state = reactive({// 设置一个属性并赋予初始值message: 'Hello World',// 添加一个更新数据的方法setMessage(msg: string) {this.message = msg},
})
这就完成了一个简单的 Reactive State 响应式状态中心的创建。
设定状态更新逻辑
接下来在一个组件 Child.vue 的 script /> 里添加以下代码,分别进行了以下操作:
1.打印初始值
2.对 state 里的数据启用侦听器
3.使用 state 里的方法更新数据
4.直接更新 state 的数据
// Child.vue
import { defineComponent, watch } from 'vue'
import { state } from '@/state'export default defineComponent({setup() {console.log(state.message)// Hello World// 因为是响应式数据,所以可以侦听数据变化watch(() => state.message,(val) => {console.log('Message 发生变化:', val)})setTimeout(() => {state.setMessage('Hello Hello')// Message 发生变化: Hello Hello}, 1000)setTimeout(() => {state.message = 'Hi Hi'// Message 发生变化: Hi Hi}, 2000)},
})
观察全局状态变化
继续在另外一个组件 Father.vue 里写入以下代码,导入 state 并在 template /> 渲染其中的数据:
<!-- Father.vue -->
<template><div>{{ state.message }}</div><Child />
</template><script lang="ts">
import { defineComponent } from 'vue'
import Child from '@cp/Child.vue'
import { state } from '@/state'export default defineComponent({components: {Child,},setup() {return {state,}},
})
</script>
可以观察到当 Child.vue 里的定时器执行时, Father.vue 的视图也会同步得到更新。
一个无需额外插件即可实现的状态中心就这么完成了!
Vuex
Vuex 是 Vue 生态里面非常重要的一个成员,运用于状态管理模式。
它也是一个全局的通信方案,对比 EventBus,Vuex 的功能更多,更灵活,但对应的学习成本和体积也相对较大,通常大型项目才会用上 Vuex 。
在了解之前
摘自 Vuex 仓库 README 文档的一段官方提示:
意思是 Pinia 已经成为 Vue 生态最新的官方状态管理库,不仅适用于 Vue 3 ,也支持 Vue 2 ,而 Vuex 将进入维护状态,不再增加新功能, Vue 官方强烈建议在新项目中使用 Pinia 。
Vuex 的目录结构
在 Vue 3 里使用 Vuex ,需要选择 4.x 版本,也是当前的 @latest 标签对应的版本,请先安装它。
npm i vuex
接下来按照下面的目录结构创建对应的目录与文件:
src
│ # Vuex 的目录
├─store
├───index.ts
└─main.ts
一般情况下一个 index.ts 足矣,它是 Vuex 的入口文件,如果的项目比较庞大,可以在 store 目录下创建一个命名为 modules 的文件夹,使用 Vuex Modules 的方式导入到 index.ts 里去注册。
回顾 Vue 2
在 Vue 2 ,需要先分别导入 vue 和 vuex,使用 use 方法启用 Vuex 后,通过 new Vuex.Store(…) 的方式进行初始化。
// src/store/index.ts
import Vue from 'vue'
import Vuex from 'vuex'Vue.use(Vuex)export default new Vuex.Store({state: {},mutations: {},actions: {},modules: {},
})
之后在组件里就可以通过 this.$store 操作 Vuex 上的方法了。
export default {mounted() {// 通过 `this.$store` 操作 Vuexthis.$store.commit('increment')console.log(this.$store.state.count)},
}
了解 Vue 3
Vue 3 需要从 Vuex 里导入 createStore 创建实例:
// src/store/index.ts
import { createStore } from 'vuex'export default createStore({state: {},mutations: {},actions: {},modules: {},
})
之后在 src/main.ts 里启用 Vuex :
// src/main.ts
import { createApp } from 'vue'
import App from './App.vue'
import store from './store'createApp(App).use(store) // 启用 Vuex.mount('#app')
Vue 3 在组件里使用 Vuex 的方式和 Vue 2 有所不同,需要像使用路由那样通过一个组合式 API useStore 启用。
import { defineComponent } from 'vue'
import { useStore } from 'vuex'export default defineComponent({setup() {// 需要创建一个 store 变量const store = useStore()// 再使用 store 去操作 Vuex 的 API// ...},
})
Vuex 的配置
除了初始化方式有一定的改变, Vuex 在 Vue 3 的其他配置和 Vue 2 是一样的。
由于现在在 Vue 3 里已经更推荐使用 Pinia , Vuex 已处于维护状态,因此关于 Vuex 的使用将不展开更多的介绍,有需要的开发者可以查看 Vuex 官网的 使用指南 了解更多。
相关文章:

Vue3 组件之间的通信
组件之间的通信 经过前面几章的阅读,相信开发者已经可以搭建一个基础的 Vue 3 项目了! 但实际业务开发过程中,还会遇到一些组件之间的通信问题,父子组件通信、兄弟组件通信、爷孙组件通信,还有一些全局通信的场景。 …...

多路查找树
1.二叉树与 B 树 1.1二叉树的问题分析 二叉树的操作效率较高,但是也存在问题, 请看下面的二叉树 二叉树需要加载到内存的,如果二叉树的节点少,没有什么问题,但是如果二叉树的节点很多(比如 1 亿), 就 存在如下问题:问…...

Mybatis——注入执行sql查询、更新、新增以及建表语句
文章目录前言案例dao和mapper编写XXXmapper.xml编写编写业务层代码,进行注入调用额外扩展--创建表语句前言 在平时的项目开发中,mybatis应用非常广泛,但一般都是直接CRUD类型sql的执行。 本片博客主要说明一个另类的操作,注入sq…...

即时通讯系列-4-如何设计写扩散下的同步协议方案
1. 背景信息 上篇提到了, IM协议层是主要解决会话和消息的同步, 在实现上, 以推模式为主, 拉模式为辅. 本文Agenda: (How)如何同步(How)如何设计同步位点如何设计 Gap过大(SyncGapOverflow) 机制如何设计Ack机制总结 提示: 本系列文章不会单纯的给出结论, 希望能够分享的是&…...

tui-swipe-action组件上的按钮点击后有阴影的解决方法
大家好,我是雄雄,欢迎关注微信公众号:雄雄的小课堂。 目录 前言问题描述问题解决前言 一直未敢涉足电商领域,总觉得这里面的道道很多,又是支付、又是物流的,还涉及到金钱,所以我们所做的项目,一直都是XXXX管理系统,XXX考核系统,移动端的也是,XX健康管理平台…… 但…...

【大数据Hadoop】Hadoop 3.x 新特性总览
Hadoop 3.x 新特性剖析系列11. 概述2. 内容2.1 JDK2.2 EC技术2.3 YARN的时间线V.2服务2.3.1 伸缩性2.3.2 可用性2.3.3 架构体系2.4 优化Hadoop Shell脚本2.5 重构Hadoop Client Jar包2.6 支持等待容器和分布式调度2.7 支持多个NameNode节点2.8 默认的服务端口被修改2.9 支持文件…...

Python-第三天 Python判断语句
Python-第三天 Python判断语句一、 布尔类型和比较运算符1.布尔类型2.比较运算符二、if语句的基本格式1.if 判断语句语法2.案例三、 if else 语句1.语法2.案例四 if elif else语句1.语法五、判断语句的嵌套1.语法六、实战案例一、 布尔类型和比较运算符 1.布尔类型 布尔&…...

失手删表删库,赶紧跑路?!
在数据资源日益宝贵的数字时代公司最怕什么?人还在,库没了是粮库、车库,还是小金库?实际上,这里的“库”是指的数据库Ta是公司各类信息的保险柜小到企业官网和客户信息大到金融机构的资产数据和国家秘密即便没有跟数据…...

技术树基础——16排它平方数(Bigdecimal,int,string,数组的转换)
题目:03879 * 203879 41566646641这有什么神奇呢?仔细观察,203879 是个6位数,并且它的每个数位上的数字都是不同的,并且它平方后的所有数位上都不出现组成它自身的数字。具有这样特点的6位数还有一个,请你…...

04动手实践:手把手带你实现gRPC的Hello World
这篇文章就从实践的角度出发,带大家一起体验一下gRPC的Hello World。文中的代码将全部使用Go语言实现,使用到的示例也是GitHub上提供的grpc-go,下面我们开始: Hello World官方示例 首先我们要clone GitHub上gRPC的源代码到我们本地 git clone https://github.com/grpc/g…...

区块链技术与应用1——BTC-密码学原理
文章目录比特币中的密码学原理1. 哈希函数2. 数字签名3. 比特币中的哈希函数和数字签名简单介绍:比特币与以太坊都是以区块链技术为基础的两种加密货币,因为他们应用最广泛,所以讲区块链技术一般就讲比特币和以太坊。比特币中的密码学原理 1…...

PyTorch学习笔记:data.WeightedRandomSampler——数据权重概率采样
PyTorch学习笔记:data.WeightedRandomSampler——数据权重概率采样 torch.utils.data.WeightedRandomSampler(weights, num_samples, replacementTrue, generatorNone)功能:按给定的权重(概率)[p0,p1,…,pn−1][p_0,p_1,\dots,p_{n-1}][p0,p1,…,pn…...

SpringMVC对请求参数的处理
如何获取SpringMVC中请求中的信息 ? 默认情况下,可以直接在方法的参数中填写跟请求参数一样的名称,此时会默认接受参 数 ,如果有值,直接赋值,如果没有,那么直接给空值 。Controller RequestMapp…...

12年老外贸的经验分享
回想这12年的经历,很庆幸自己的三观一直是正确的,就是买家第一不管什么原因,只要你想退货,我都可以接受退款。不能退给上级供应商,我就自己留着,就是为了避免因为这个拒收而失去买家。不管是什么质量原因&a…...

电子电路中的各种接地(接地保护与GND)
前言多年以前,雷雨天气下,建筑会遭遇雷击,从而破坏建筑以及伤害建筑内的人,为了避免雷击的伤害,人们发明了避雷针,并将避雷针接地线,从而引导雷击产生的电流经过地线流入到地下。地线࿱…...

php实现农历公历日期的相互转换
农历(Lunar calendar)和公历(Gregorian calendar)是两种不同的日历系统。公历是基于太阳和地球的运动来计算时间的,而农历是基于月亮的运动来计算时间的。农历中的月份是根据月亮的相对位置来确定的,而公历…...

基于SpringBoot的房屋租赁管理系统的设计与实现
基于SpringBoot的房屋租赁管理系统的设计与实现 1 绪论 1.1 课题来源 随着社会的不断发展以及大家生活水平的提高,越来越多的年轻人选择在大城市发展。在大城市发展就意味着要在外面有一处安身的地方。在租房的过程中,大家也面临着各种各样的问题&…...

一文带你为PySide6编译MySQL插件驱动
1.概述 最近使用PySide6开发程序,涉及与MySQL的数据交互。但是qt官方自pyqt5.12(记不太清了)以后不再提供MySQL的插件驱动,只能自己根据qt的源码编译。不过网上大部分都是qt5的MySQL驱动的编译教程。后来搜到了一个qt6的编译教程…...

图论算法:树上倍增法解决LCA问题
文章目录树上倍增法: LCA问题树上倍增法: LCA问题 树上倍增法用于求解LCA问题是一种非常有效的方法。 倍增是什么? 简单来说,倍增就是 1 2 4 8 16 … 2^k 可以发现倍增是呈 2的指数型递增的一类数据,和二分一样&…...

Java线程池中submit() 和 execute()方法有什么区别
点个关注,必回关 文章目录一. execute和submit的区别与联系1、测试代码的整体框架如下:2、首先研究Future<?> submit(Runnable task)和void execute(Runnable command),3、submit(Runnable task, T result) 方法可以使submit执行完Run…...

Vue.extend和VueComponent的关系源码解析
目录 0.概念解释 前言 需求分析 Vue.extend 编程式的使用组件 源码分析 0.概念解释 Vue.extend和VueComponent是Vuejs框架中创建组件的两种不同方式。Vue.extend方法能够让你根据Vue对象(继承)来定义一个新的可重用的组件构造器。而VueComponent方…...

【动态规划】01背包问题(滚动数组 + 手画图解)
01背包除了可以用形象的二维动态数组表示外,还可以使用空间复杂度更低的一维滚动数组。 目录 文章目录 前言 一、滚动数组的基本理解 二、确定dp及其下标含义 三、确定递推公式 四、确定初始化 五、确定遍历顺序 1.用物品(正序)遍历背…...

javaEE 初阶 — 超时重传机制
文章目录超时重传机制1. 数据重复传输问题2. 如何解决数据重复传输问题3. 重传次数问题TCP 的工作机制:确认应答机制 超时重传机制 如果传输数据的时候丢包了该怎么办? 利用 超时重传,也就是超过了一定的时间,如果还没响应就重新…...

小米5x wlan无法打开解决
诱因:想要利用空置设备做节点服务器或者边缘计算,因此解锁并刷了magisk,印象中在刷之前wlan已经无法打开无法进行wifi联网 表现: 1 WLAN开关无法打开,或者虚假打开,无法扫描wifi 2 设置->我的设备->全…...

负载均衡之最小活跃数算法
文章目录[toc]一、概念二、场景与设计思路三、实现四、代码下载一、概念 活跃数 集群中各实例未处理的请求数。 最小活跃数 集群中各个实例,哪个实例未处理的请求数据最小,就称之为最小活跃数。 二、场景与设计思路 场景 以获取微服务地址为场景。 设计…...

JavaScript 评测代码运行速度的几种方法
一、使用 performance.now() API 在 JavaScript 中,可以使用 performance.now() API 来评测代码的运行速度。该 API 返回当前页面的高精度时间戳,您可以在代码执行前后调用它来计算代码执行所需的时间。 例如: let t0 performance.now();…...

Linux 编译器 gcc/g++
本文已收录至《Linux知识与编程》专栏! 作者:ARMCSKGT 演示环境:CentOS 7 目录 前言 正文 gcc/g常用命令 自定义可执行程序名命令-o 预处理指令-E 编译指令-S 汇编指令-c 链接指令gcc 命令巧记口诀 链接库 动态库-动态链接 静态库…...

2.Java基础【Java面试第三季】
2.Java基础【Java面试第三季】前言推荐2.Java基础01_字符串常量Java内部加载-上58同城的java字符串常量池面试code讲解intern()方法---源码解释02_字符串常量Java内部加载-下whyOpenJDK8底层源码说明递推步骤总结考查点03_闲聊力扣算法第一题字节跳动两数求和题目说明面试题解法…...

Java高级-多线程
本篇讲解java多线程 基本概念: 程序、进程、线程 **程序(program)**是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。 **进程(process)**是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程…...

mysql高级(事务、存储引擎、索引、锁、sql优化、MVCC)
文章目录1.事务1.1 四大特性ACID1.2 并发事务2.存储引擎2.1 InnoDB2.2 MyISAM2.3 Memory2.4 存储引擎特点2.5 存储引擎的选择3.性能分析3.1 查看执行频次3.2 慢查询日志3.3 profile3.4 explain4.索引4.1 索引结构B-TreeBTreeHash面试题4.2 索引分类思考题4.3 语法4.4 使用规则最…...