vue 根据名称更改对象顺序_从文档开始,重学Vue(上)

050144f460c980e7b5166912c877fca8.png

文章较长?请先关注收藏?如果一不小心解决了你在使用vue中的某个痛点记得点个赞哦?

闲扯一番

vue也有些年头了,不得不说vue确实是一个了不起的框架(不接受任何反驳?)但在工作中有太多的前端开发者还只是停留在会用的圈圈中,有很多人觉得我没有看完官方文档也不妨我们做vue项目写vue代码啊?确实,这点不可否认

但是大哥,你一个vue文件写1000多行,是觉得自己的头发掉的不够快吗?

你们信不爱读文档的程序员能写出好代码吗?反正我是不信?

举个例子

我们知道prop是接受父组件参数,假如现在要接收一个对象,可能你会这样用

item:{    name:'刘小灰',    age:18}Vue.component('my-component', {    props:['item']    } {{item.name}} 

如果粗心的程序员没有传这个item,控制台就会报错

1d44aa920cde925a4898d3d4f36254a1.png这个时候,聪明的你会有两个选择

  • 索性不管,不影响正常逻辑
  • 大不了加个判断
 {{ item.name }}

5b4306856381f7a8e199f5833c1974fb.png页面又一切正常好像什么都没发生,这个时候你可能心里犯迷糊, 这个bug大家都是这样解决的吗?

如果你看过vue的官方文档,了解prop的所有用法,当你第一眼看到这个bug时就会立马反应过来,prop应该这样写更为合理

Vue.component('my-component', {    props:{         item:{            type:Object,            defent:()=>{return:{}}        }    }}

例子可能过于简单,主要想表达的思想就是 只有先了解框架具备的所有能力,才能写出更高质量的代码

从风格指南开始

8c61007c5c945fedd57008a0fa1a4db6.png既然是重学vue说明不是第一次学了,这个时候建议大家从 风格指南 开始重学,如果是新手还是建议大家从 教程 一步一步开始学

以下示例均在 vue-cli3 中完成

组件的命名规范

在开发中你可能会遇到 不知道给组件怎么取名 的尴尬情况,遵从vue规范,让你给组件起名即 顺畅规范

组件名为多个单词

组件名应该始终是多个单词的,根组件 App 以及、之类的 Vue 内置组件除外。

这样做可以避免跟现有的以及未来的 HTML 元素相冲突,因为所有的 HTML 元素名称都是单个单词的。-官方文档

用多个单词定义组件不仅可以避免和原有的HTML元素相冲突,在外观上看来也更加的好看?

采用PascalCasekebab-case命名规范

或的意思是我们在命名时即可以采用驼峰命名da也可以采用-命名,但建议大家在项目中统一风格只用一种,我本人习惯使用PascalCase格式

单词大写开头对于代码编辑器的自动补全最为友好,因为这使得我们在 JS(X) 和模板中引用组件的方式尽可能的一致。然而,混用文件命名方式有的时候会导致大小写不敏感的文件系统的问题,这也是横线连接命名同样完全可取的原因   -官方文档

原因就是PascalCase更有利于 代码自动补全 ,至于导致大小写不敏感的系统文件问题我暂时还没遇到

基础组件用 Base | App | V 开头

推荐用Base开头,因为更加语义化如一个基础的按钮组件我们可以叫BaseBtn.vue

单例组件用 The开头

可能有的人不熟悉什么是单例组件,单例是一种设计模式不清楚这个概念的可以自己查阅资料(也可以关注公众号 码不停息 里面有介绍),比如我们常见的element-ui中通过js调用的弹窗组件就可以看做是一个单例组件

和父组件紧密耦合的子组件应该以父组件名作为前缀命名

如果一个公用组件比较复杂我们可以抽离出几个子组件,同时从命名上区别出组件之间的关系,如:

components/|- TodoList.vue|- TodoListItem.vue|- TodoListItemButton.vue

根据以上规则,我们来规范下项目中组件的目录ba7dac786a6cc892ff7c9d7a4a63a43b.png

1. 这里我把基础组件和单例组件单独拿出来放在了`common`文件夹中`components`文件里面放置项目公共组件2. 每个组件建议放在一个单独的文件夹而不是用单独的文件,有利于后期的扩展及维护

组件实例书写顺序规范

在我们平常开发中一个组件会调用很多vue实例,由于开发人员的习惯不同这些实例书写顺序也不同,这样无形之中增加了我们的维护成本,下面我们来看看vue推荐的书写顺序

vue文件里面js,要按照vue的生命周期来写,最开始是mixins->porps->data->computed->mounted->watch->methods->components,用不到的可以忽略,统一顺序,养成习惯

1. name2. components4. directives5. filters6. extends7. minins8. props9. data10. computed11. watch12. beforeCreate13. created14. beforeMount15. mounted16. beforeUpdate17. updated18. activated`19. deactivated20. beforeDestroy21. destroyed22. methods

上面列的比较多,在我们实际开发中,没有用到的可以不写,保留这个顺序即可

a0802016a86d0185561a104c01df7df9.png

组件父子通信规范

应该优先通过 prop 和事件进行父子组件之间的通信,而不是 this.$parent 或变更 prop

一个理想的 Vue应用是 prop 向下传递,事件向上传递的。遵循这一约定会让你的组件更易于理解。然而,在一些边界情况下 prop 的变更或 this.$parent 能够简化两个深度耦合的组件

记住这句话 一个理想的 Vue 应用是 prop 向下传递,事件向上传递的 可以让我们少写很多野路子代码

vue官方的风格规范有很多,我这里只是抛砖引玉,捡了我认为比较有用的给大家回顾下,更加详细的内容可以去官方文档瞅一瞅

事件名书写规范

直接上官方推荐1489aca513e53811c41ca96a65acdb5c.png

如:

总结

写在里面的(组件的使用,事件)使用kebab-case命名规范,其他地方使用PascalCase命名规范

  • 可以在任何地方都使用PascalCase吗?

    不推荐因为有些时候可能出现大小写不敏感的问题

  • 可以在任何地方都使用kebab-case吗?

    原则上可以这个看个人爱好,需要注意的是kebab-case对代码编辑器的自动补全不太友好

再看官方教程

70f324f1d17834f343ddc30817a85d5d.png相信大家最初学vue的时候都看过这个教程,下面我带着大家再回顾下比较重要且容易被遗忘的一些知识点

Vue的安装

目前使用vue最常用的就是通过npm引入或者直接script标签引入,下面是官方给出的vue构建的不同版本

2230d9d31aa30de1f14ef3bbd4d8ed2c.png我们来说说不同版本的使用场景

  • UMD UMD 版本可以通过 标签直接用在浏览器中
  • CommonJS CommonJS 版本用来配合老的打包工具比如 Browserifywebpack 1。这些打包工具的默认文件 (pkg.main) 是只包含运行时的 CommonJS 版本 (vue.runtime.common.js)
  • ESModule 从 2.6 开始 Vue 会提供两个 ES Modules (ESM) 构建文件:
    • 为打包工具提供的 ESM:为诸如 webpack 2Rollup 提供的现代打包工具。ESM 格式被设计为可以被静态分析,所以打包工具可以利用这一点来进行“tree-shaking”并将用不到的代码排除出最终的包。为这些打包 工具提供的默认文件 (pkg.module)是只有运行时的 ES Module 构建 (vue.runtime.esm.js)。
    • 为浏览器提供的 ESM (2.6+):用于在现代浏览器中通过 直接导入。

可以看出 vue 给出了很多种构建版本适用于UMD CommonJS ESModule,对这些规范不理解的可以看 这篇文章,而 我们通常使用的通过webpack构建出来的vue-cli遵循的是ESModule规范

完整版&编译器&运行时

不同构建版本中又分为 完整版只包含运行时版本 ,为了便于理解我们可以把vue代码大致分为负责运行时的代码负责编译的代码,他们之间的关系是编译器 + 运行时 ≈ 完整版

而编译器只是在编译开发环境下使用,也就是说生产环境中我们只需要使用 只包含运行时版本vue,而不是 完整版vue,如果你是使用vue-cli可以在vue.config.js中配置生成环境下不打包vue然后通过 CDN 的方式去引入 只包含运行时版本vue,代码如下:

index.html                      vue-app          src="https://cdn.bootcss.com/vue/2.6.10/vue.runtime.min.js"      crossorigin="anonymous"    >  
module.exports = {  configureWebpack: {    externals: {      vue: 'Vue'    }  }}

下面是通过 npm使用vue通过cdn使用vue完整版通过cdn使用只包含运行时版打包后的性能对比图

6a754a69980a43e018b41474f51d2a05.png

通过cdn方式引入vue打出来的包要小

我们再来看vueruntime.js (只包含运行时)vue.main.js (完整版)大小的对比0d2541a87467cafae5f875f829d23652.png

这也验证了官方的数据

f7a00d95ef14e0009cdc45ea53d5112e.png

再看看你的项目vue引入对了吗?

Vue并不完全遵循MVVM 模型

  • 面试官 : 你知道vue是基于什么模型吗?
  • 面试者: 知道 MVVM
  • 面试官: 欣慰地点了点头

我们来看看官网b591126cec2152fad1ea8d8553578c3f.png???

要说清楚这点,我们先来看看学习几个典型的架构模型

MVC

531d10cd8c5cebf9772dd6f00471409c.pngMVC把软件分为三个层,分别是

视图(View):用户界面。控制器(Controller):业务逻辑模型(Model):数据保存

他们之间的通讯方式为1e1d34a66a9173b75426b03ac7db4f31.png可以看出MVC模型数据都是单向的,流程可以简化为

用户行为改变(点击事件)Viwe -> View通知Contoller进行逻辑处理 -> 处理后Controller通知Model层数据改变-> Model数据改变后交给View渲染(改变view层)注:用户也可以直接改变Contoller
MVP

e9e52b0a19009957347f7a37529c0683.pngMVP可以看做是MVC的衍生物,在MVPModel不能直接操作View,且所有的通讯都是双向的

MVVM

fb884c794a927af0eb6e4305c1fe4a07.pngMVVM 模式将 Presenter 改名为 ViewModel,基本上与 MVP 模式完全一致。唯一的区别是,它采用双向绑定(data-binding)View的变动,自动 反映在 ViewModel,反之亦然

为什么说Vue没有完全遵循MVVM

严格意义上在MVVMViewModel之间是不能通讯的,但Vue却提供了相应的Api $refs

我们可以在项目中这样使用

  export default {  name: 'home',  components: {},  data() {    return {}  },  mounted() {    console.log(this.$refs.dome.value)    this.$refs.dome.value = 2  },  methods: {}}

可以看出我们可以直接通过Model去操作View74060dfc572332775c4ed962b44f3a48.pngvue官方也对$refs进行说明

3380af30df3cc00c2aac67d3ed3c1a25.png所以说Vue并不是正在意义上的MVVM架构,但是思想是借鉴了MVVM然后又进行了些本土化,不过问题不大,现在根据MVVM本土化出来的架构都统称MV*架构

你还知道vue的哪些设计没有遵循MVVM规范呢? 欢迎留言

关于Vue实例

Object.freeze()

当一个 Vue实例被创建时,它将 data 对象中的所有的 property 加入到 Vue 的响应式系统中。当这些 property 的值发生改变时,视图将会产生“响应”,即匹配更新为新的值。

但在项目开发中,有的信息我们不需要他是响应式的,这个时候我们可以用Object.freeze() 如:

  
{{ user }}
export default { name: 'index', data() { return { number: 2, price: 10, user: Object.freeze({ age: 18 }) } }, mounted() { this.user.age = 20 }}.totle { padding-top: 20px;}

当我们给user数据加上Object.freeze()后,如果再更改user数据控制台就会报错b3ce80d25791e55046663751bcabc543.png

Object.freeze()只能用户对象|数组

生命周期

下图展示了实例的生命周期。你不需要立马弄明白所有的东西,不过随着你的不断学习和使用,它的参考价值会越来越高。

38b7baf09686fc9482d3cc0f6284442c.png

上面是官方给出的完整的生命周期流程图,可以说是应用的灵魂,下面我们在代码中实际运行顺序为

  beforeCreate: function() {    console.log(this)    console.log('创建vue实例前', this)  },  created: function() {    console.log('创建vue实例后', this)  },  beforeMount: function() {    console.log('挂载到dom前', this)  },  mounted: function() {    console.log('挂载到dom后', this)  },  beforeUpdate: function() {    console.log('数据变化更新前', this)  },  updated: function() {    console.log('数据变化更新后', this)  },  beforeDestroy: function() {    console.log('vue实例销毁前', this)  },  destroyed: function() {    console.log('vue实例销毁后', this)  }

挑几个重要的具体说明

  • beforeCreate 创建vue前调用,这个过程中进行了初始化事件、生命周期
  • created vue创建成功之后,dom渲染之前调用,通常请求数据会写在这个函数里面
  • mounted dom创建渲染完成时调用,这个时候页面已经渲染完毕,可以在这个函数里面进行dom操作
  • updated 数据更改且 时调用,他和watch不同,watch只有监听的数据变化就会触发,而 updated要求这个变更的数据必须在页面上使用了,且 只要页面的数据发生变化都会触发这个函数
  • beforeDestroy/destroyed vue 实例或者说组件销毁前后调用,如果页面中需要销毁定时器和释放内存,可以写在这个函数里

destroyedbeforeRouteLeave

destroyed 需要和 vue-routerbeforeRouteLeave api区别开

通常意义下路由发生变化也就意味上个组件被销毁,所以这两个函数都会触发destroyed 只是个监听功能,不能阻止页面要不要销毁 而beforeRouteLeave可以通过next()控制路由是否要变化

例如:当需要判断用户是否返回时使用beforeRouteLeave而不是destroyed

关于模板语法

v-html

双大括号会将数据解释为普通文本,而非 HTML 代码。为了输出真正的 HTML,你需要使用 v-html,比如用v-html渲染后端返回回来的富文本内容

值得注意的是:站点上动态渲染的任意 HTML 可能会非常危险,因为它很容易导致 XSS 攻击。请只对可信内容使用 HTML 插值,绝不要对用户提供的内容使用插值。

动态参数

vue指令支持动态参数,比如:

...

eventName=clickdoSomething就是点击事件当eventName=focus时doSomething就是focus事件

同理,属性也支持动态形式,如:

 ... 

计算属性和侦听器

计算属性 VS 方法

两者最大的区别就是

  • 计算属性是计算的作用,也就是对数据进行处理
  • 计算属性是响应式的且可以基于响应式依赖进行缓存

代码说明:

  
数量: 价格:
总价:
{{ totle }}
{{ totle }}
export default { name: 'index', data() { return { number: 2, price: 10 } }, computed: { totle() { console.log(1) const totle = this.number * this.price return totle > 0 ? totle : 0 } }}.totle { padding-top: 20px;}

这是例子很简单,就是时时计算totle值,我们在页面上故意写两个{{totle}}

d47eacb7af2e43a4a5fb3534a029eb37.png但控制台中只输出了一个1,说明计算属性totle只计算了一次,页面上第二个20 直接用了第一次计算的结果

我们把totle改成方法的形式看一看

  
数量: 价格:
总价:
{{ totle() }}
{{ totle() }}
export default { name: 'index', data() { return { number: 2, price: 10 } }, methods: { totle() { console.log(1) const totle = this.number * this.price return totle > 0 ? totle : 0 } }}.totle { padding-top: 20px;}

控制台打印结果306163051e105a1e0c9167a17270360b.png

可以看出totle在页面上调用了两次而控制台就输出两次

显然如果有多个数据依赖totle,方法的性能开销是计算属性的n倍,下面是官方的解释

32627559392c194a871faa7c8900fa16.png
计算属性 VS watch

我们使用watch实现上述功能

  
数量: 价格:
总价:
{{ totle }}
{{ totle }}
export default { name: 'index', data() { return { number: 2, price: 10, totle: 20 } }, watch: { price() { const totle = this.number * this.price this.totle = totle > 0 ? totle : 0 }, number() { const totle = this.number * this.price this.totle = totle > 0 ? totle : 0 } }}

显然没有计算属性来的优雅,所有项目中,当我们有动态计算需求时最应该使用计算属性,而不是watch

那什么时候使用watch呢?官方给出答案

7bc99050348c0e3940f0ec9dfb76584d.png

也就是说,当我们处理函数中有异步请求(定时器,ajax)时应该使用watch,因为计算属性里面不支持写异步

cf50c9a8995107785a6137e524c6f19f.png

可以看出,编辑器直接提示computed不支持异步的写法

总结
  • 有动态处理数据的时候优先使用计算属性
  • 如果在处理数据逻辑里面有异步需求,使用watch

事件

事件修饰符

Vue中给元素添加事件可谓是最常见的操作,Vue中也为我们提供了很多事件修饰符供我们使用

...
...
使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。因此,用 @click.prevent.self 会阻止所有的点击,而 @click.self.prevent 只会阻止对元素自身的点击。

有两个修饰符值得我们注意:

  • once 点击事件将只会触发一次
  • native 将原生事件绑定到组件 如:
 // 将会触发item-list内部的clickHandle // 将会触发父组件内部的clickHandle

官网上给出了很多像上面一样的小技巧,大家可以自行查阅

fb4c3a5596d5038696e30eed08b36fcc.png

关于组件

这应该是最重要的一节,组件是vue的灵魂 在实际开发中,好的组件可以让我们的开发效率及维护成本事倍功半,反之事倍功半

如何注册全局组件

在做项目中有些组件是我们经常用的,这样的组件我们可以注册为全局组件,如:注册一个全局的BaseBtn组件

  • BaseBtn 组件
  
这是一个按钮组件
  • main.js 中注册
import Vue from "vue";import App from "./App.vue";import router from "./router";import store from "./store";import BaseBtn from "@/common/BaseBtn";Vue.component("base-btn", BaseBtn);Vue.config.productionTip = false;new Vue({  router,  store,  render: h => h(App)}).$mount("#app");
  • 在页面中使用
  

This is an about page

  • 页面显示20c8c074b533290951e4895c452708ee.png一切OK,但是如果我们要注册多个去全局组件呢?是不是要重复上面的步骤?

自动注册全局组件

关键性方法require.context主要流程是:读取要注册为全局组件的文件路径->循环进行动态注册

import Vue from "vue";import App from "./App.vue";import router from "./router";import store from "./store";import upperFirst from "lodash/upperFirst";import camelCase from "lodash/camelCase";Vue.config.productionTip = false;const requireComponent = require.context(  // 其组件目录的相对路径  "./common",  // 是否查询其子目录  false,  // 匹配基础组件文件名的正则表达式  /Base[A-Z]\w+\.(vue|js)$/);requireComponent.keys().forEach(fileName => {  const componentConfig = requireComponent(fileName);  const componentName = upperFirst(    camelCase(      fileName        .split("/")        .pop()        .replace(/\.\w+$/, "")    )  );  Vue.component(componentName, componentConfig.default || componentConfig);});new Vue({  router,  store,  render: h => h(App)}).$mount("#app");

页面还是正常显示

0ac1b81659a489ccea8d70f31a529ff1.png

上文说过,基础组件使用Base开头进行命名,所以require.context的筛选正则才可以这样写 Base[A-Z]\w+\.(vue|js)$/这样以后只要我们在common文件夹下以Base开头的文件都会自动注册为全局组件?

自定义组件使用 v-model

比如我们要封装一个input组件,使用的时候这样使用

data(){    return{        name:'刘小灰'    }}

那我们BaseInput里面如何去接受name参数呢? 我们可以使用model选项,如:

  
type="text" :value="value" @input="$emit('change', $event.target.value)" /> {{ value }}
export default { props: { value: { type: String || Number } }, model: { prop: "value", event: "change" }};

子组件model中需要定义propevent

  • prop 给参数重命名,新的变量名需要在props中定义
  • event 触发的事件名称

默认情况下,一个组件上的 v-model 会把 value 用作 prop 且把 input 用作 event。

上面的代码还可以这样简化

  
type="text" :value="value" @input="$emit('input', $event.target.value)" /> {{ value }}
export default { props: { value: { type: String || Number } }};

.sync 修饰符

可能有的人不理解.sync有什么用,其实它就是一种子组件改变父组件传过来的prop并让父组件数据更新的一种语法糖

那什么时候使用呢?我们来封装一个自定义弹窗组件BaseAlert.vue

  
我是一个弹框
关闭弹窗
export default { props: { show: { type: Boolean, default: false } }, data() { return {}; }, methods: { close() { this.show = false; } }};

在父组件中使用

  
打开弹框
export default { data() { return { show: false }; }};

试验一下

0bfdc8954ac44f00068ce401eb869cef.png

25efbeb95749178b6a23ca4a38c30712.png这时候我们思考两个问题

  • 为什么可以关闭,但页面为什么会报错?

    因为我们在子组件中让show=false但是show是父组件传过来的,我们直接改变它的值vue会报错

  • 再次点击打开弹窗时,为什么没有反应?

    虽然在子组件中show的状态是false但是在父组件中show的状态还是true

上面的情况可以解决吗? 肯定可以, 我们只需要点击子组件的关闭按钮时通知父组件,让父组件把show的状态变为false即可:如

methods: {    close() {      this.$emit('close',false)    }  }  methods: {    close(status) {        this.show=status    }  }

问题是 父组件为了关闭弹窗这个简单的功能还需要用一个函数close,实在是不太优雅,而.sync就是来解决这样类似的场景

我们现在使用.sync重构代码

  
打开弹框 // 在 :show 改为:show.sync
export default { data() { return { show: false }; }, methods: { close(status) { this.show = status; } }};

BaseAlert组件改造

  
我是一个弹框
关闭弹窗
export default { props: { show: { type: Boolean, default: false } }, data() { return {}; }, methods: { close() { this.$emit("update:show", false); // 注意把show 改为 update:show } }};

c7cb2a7071eff65935827e09d376e51a.pngda96322c0ca1b0d1015eef42f6730c91.png这个时候页面功能正常,且没有报错,完美!

用插槽封装组件

公用逻辑的抽离和组合一直是项目中难题,同样也是框架设计者的难题,在Vue2.0中也提供了抽离公共逻辑的方法Mixins,但Mixins有着明显的缺陷 无法清楚得知数据来源 特别是在一个页面有多个Mixins时,页面维护起来简直是种灾难83d80c4ad3cd811d7596cb0b206ea856.png上面是尤雨溪在VueConf演讲中提到的有关Mixins问题f003993a58e9d42e87f69ce203ac1169.png上面是尤雨溪在VueConf演讲中提到插槽有关的问题,因为插槽是以组件为载体,所以有额外的组件实例性能消化,但也正是因为以组件为载体,所以也可以封装些样式相关的东西

可以看出在Vue2.0插槽是逻辑复用的最优解决方案,当然在Vue3.0中有更好的解决方案composition-api,现在你应该了解到为什么Vue3.0要出composition-api了,主要解决的问题就是 逻辑的分封装与复用

插槽的分类

  • 匿名插槽 没有名字的插槽
  • 具名插槽 有名字的插槽
  • 作用域插槽 可以通信的插槽
  • 动态插槽 就是动态插槽

下面我们再来改造下上面的BaseAlert.vue组件来学习下各种插槽之间的使用

需求:

  • 可以自定义头部
  • 弹窗中可以展示当前日期

假设我们在父组件中这样使用

                  重大提示                    这是一个弹窗        
{{ time }}

我们定义了一个具名插槽 v-slot:title 和作用域插槽接受子组件的time

BaseAlert.vue封装

  
关闭弹窗
export default { props: { show: { type: Boolean, default: false } }, computed: { time() { const d = new Date(); return d.getFullYear() + "-" + (d.getMonth() - 1) + "-" + d.getDate(); } }, data() { return {}; }, methods: { close() { this.$emit("update:show", false); } }};

作用域插槽的使用就是在slot中可以添加自定义属性,在父组件用v-slot接收即可

页面效果如下所示(请忽略样式)cfb127a072def0e7221e49c70458a643.png

关于匿名插槽动态插槽理解起来比较简单,就不举例子说明了

动态组件

我们可以通过is关键字动态加载组件,同时使用keep-alive可对组件进行缓存,在tab切换场景中使用较多,如:

  

keep-alive也可和路由搭配使用可以把项目的整体体验提升一个段,后续写重学vue-Router的时候会讲到

异步组件

当一个组件比较大的时候,为了不影响整个页面的加载速度,我们需要使用异步去加载整个组件,和异步路由写法一样,异步组件使用如下:

new Vue({  components: {    'my-component': () => import('./my-async-component')  }})

值得注意的是,官方还支持对异步组件加载状态进行配置

00773042664c70283d776eaf6a75d7cc.png但是我在vue-cli测试的时候delay属性一直无效,不知道你们如何配置异步组件加载状态的呢?欢迎留言谈论

组件之间通信

组件之间的通讯一直是vue的高频考点,也是在项目开发中比较重要的一个知识点,下面我就带领大家总结下,vue组件通讯都有哪些,且分别适用于哪些场景

常规通讯方式1(推荐使用)
  • 父组件给子组件传递数据

    父组件调用子组件用 :(v-bind) 绑定数据

    子组件用 props 接收

    export default {    props:{        item:{            type:Object        }    }}
  • 子组件给父组件传递数据

    子组件通过触发事件的方式 $emit 给父组件传递数据

      
    我是子组件 点击传递数据
    export default { methods: { btnClick() { this.$emit("childFn", "数据"); } }};

    父组件用对应的事件去接收

    点击传递数据     export default {      methods: {          childFn(val) {             console.log(val); //数据            }      }    };
  • 父组件触发子组件方法

    父组件通过ref的方式调用子组件方法

    点击传递数据     export default {      methods: {          btnClick(val) {            this.$refs['hellow'].子组件方法            }      }    };
  • 子组件触发父组件方法

    通过$emit触发父组件方法,和上面的 子组件给父组件传递数据 一样

常规通讯方式2(不推荐使用)

在父组件里想拿到子组的实例很简单this.$children 就可以拿到全部子组件的实例,只要拿到组件的实例,那事情都变的简单了

  • 通过实例改变子组件的数据及调用子组件的方法
this.$children['组件名称'].xxx  //改变子组件数据this.$children['组件名称'].xxx()  // 调用子组件方法

子组件调用父组件的道理也一样,用this.$parent即可

这种父子组件通讯的方式这么简单,为什么不推荐使用呢?刚开始学vue的时候我也有这样的疑问,但是通过做项目的时候发现,这样通讯最要命的弊端就是 数据状态改变不明了, 特别是一个父组件里面有很多子组件,当父组件数据改变时你并不知道是哪个子组件所为, 就和使用mixins所带来的尴尬一样

下面是官方给出的解释16e5869af5c7652386dd26a587e6f9c3.png

值得注意的是,官方并没给出父子组件隔代通讯及兄弟组件之间通讯的相关API,如果业务里面有这样的需求我们只能用vuex这种第三方状态管理器,但如果我们是封装项目的基础组件,或者自己做个组件库,这个时候并不能依赖vuex,那我们应该怎么样方便快捷的去实现呢?

下面所说的方式推荐在开发独立组件的时候使用,不推荐在项目组件中直接使用

独立组件之间的通信方式
  • provide / inject

    主要用于子组件获取父组件的数据/方法,如:

    export default {  name: "Home",  provide: {    name: "公众号码不停息" // 传数据  },}
    export default {  inject: ["name"], // 接受数据  mounted() {    console.log(this.name); //公众号码不停息  }};

并且provide / inject还支持隔代传递ca9e2f748733fd4ac2bdc2d3651b55ca.png官方不推荐provide/inject用于普通应用程序代码中,但如果你充分的了解了它的特性,有时候provide/inject在某种应用场景下可以代替vuex需要满足什么场景呢?

  • 应用不能太复杂,最好是简单的单页面
  • 没有权限判断,因为provide/inject只能用于vue组件,不能用于js文件中,而权限判断一般会写在路由拦截等js文件里面,这时用provide/inject就会显得比较乏力

那我们怎么使用provide/inject 来取代vuex呢?

vuex主要的作用就是管理应用中的数据,那按道说只要我们把provide写在一个应用 最大的父组件 里面,那应用里面所有的组件都可以使用provide所暴露出来的数据/方法了,显然这个 最大的父组件 就是app.vue组件

// app.vueexport default {  provide() {    return {      app: this   //把整个app实例暴露出去    };  },  data() {    return {      userInfo: {        name: "码不停息",        age: 18      }    };  },  methods: {    getUserInfo() {      console.log("请求接口");    }  }};

下面我们在任意一个组件中去拿app.vueuserInfo和调用getUserInfo方法

import BaseLoading from "@/common/BaseLoading.vue";export default {  inject: ["app"], // 把app实例导入  mounted() {    console.log(this.app.userInfo);    this.app.getUserInfo();  }};

可以看到数据可以拿到,方法也调用成功了7edcc0f880ecf088352a8373eab08214.png

使用provide/inject可以满足我们搭建小而美的应用

  • 自定义通讯方式

所谓的自定义,也就是自己封装一个通用的函数,来实现复杂情况下的数据传递,原理就是根据组件的name去遍历查找自己需要的组件,下面我们先弄清楚这个方法应给具备怎样的功能

向上找到最近的指定组件
向上找到所有的指定组件
向下找到最近的指定组件
向下找到所有指定的组件
找到指定组件的兄弟组件

代码如下:

// 由一个组件,向上找到最近的指定组件function findComponentUpward(context, componentName) {  let parent = context.$parent;  let name = parent.$options.name;  while (parent && (!name || [componentName].indexOf(name) < 0)) {    parent = parent.$parent;    if (parent) name = parent.$options.name;  }  return parent;}// 由一个组件,向上找到所有的指定组件function findComponentsUpward(context, componentName) {  let parents = [];  const parent = context.$parent;  if (parent) {    if (parent.$options.name === componentName) parents.push(parent);    return parents.concat(findComponentsUpward(parent, componentName));  } else {    return [];  }}// 由一个组件,向下找到最近的指定组件function findComponentDownward(context, componentName) {  const childrens = context.$children;  let children = null;  if (childrens.length) {    for (const child of childrens) {      const name = child.$options.name;      if (name === componentName) {        children = child;        break;      } else {        children = findComponentDownward(child, componentName);        if (children) break;      }    }  }  return children;}// 由一个组件,向下找到所有指定的组件function findComponentsDownward(context, componentName) {  return context.$children.reduce((components, child) => {    if (child.$options.name === componentName) components.push(child);    const foundChilds = findComponentsDownward(child, componentName);    return components.concat(foundChilds);  }, []);}// 由一个组件,找到指定组件的兄弟组件function findBrothersComponents(context, componentName, exceptMe = true) {  let res = context.$parent.$children.filter(item => {    return item.$options.name === componentName;  });  let index = res.findIndex(item => item._uid === context._uid);  if (exceptMe) res.splice(index, 1);  return res;}export {  findComponentUpward,  findComponentsUpward,  findComponentDownward,  findComponentsDownward,  findBrothersComponents};
此代码copy Aresn大神(iView 作者) 写的 Vue.js组件精讲小册,不是打广告,看完这个小册你的vue水平可以提升一个段

下面我们简单的使用下其中的一个方法findComponentUpward向上查找最近的父组件

import { findComponentUpward } from "@/units/findComponents";export default {  methods: {    btnClick() {      console.log(this.$parent);      this.$parent.aaaaa();    },    ceshi() {}  },  mounted() {    console.log(findComponentUpward(this, "Home"));  }};

可以看出父组件实例已经打印出来,有了父组件实例就可以为所欲为啦232e8cecc64260af33cb16d6ef451d26.png

注意:使用该方法时组件必须有name属性

总结

本文主要从官方文档出发,梳理了vue比较实用且容易被忽略的知识点,不是大神,如有错误请多多指教,该篇是__从文档开始重学vue__的上册,下册将带领大家回顾vue一些比较重要的api

1375a8cff4502b9e39fe7f53d0f37a0c.png

最后

交个朋友吧!

让代码

永不停息!

码不停息

bbfe77ccdb9bf061922c5bd05e3cb32b.png
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值