如何写好一个vue组件,老夫的一年经验全在这了(推荐)


Posted in Javascript onMay 18, 2019

一个适用性良好的组件,一种是可配置项很多,另一种就是容易覆写,从而扩展功能

Vue 组件的 API 来自三部分——prop、事件和插槽:

  1. prop 允许外部环境传递数据给组件
  2. event 允许从组件内触发外部环境的副作用
  3. slot 允许外部环境将额外的内容组合在组件中

prop

组件具有自身状态,当没有相关 porps 传入时,使用自身状态完成渲染和交互逻辑;当该组件被调用时,如果有相关 props 传入,那么将会交出控制权,由父组件控制其行为

仅一个值传入组件

如果该组件设计上支持双向绑定,可使用v-model将该参数传入组件,减少记忆成本(毕竟 vue 官方的语法糖,不用白不用)

<my-component v-model="foo" />

如果该组件可以独立运行,不依赖父组件时,还是给这个值起个名字吧

<component-no-sync :childNeed="foo" />

很多值需要传入组件

我们原先的父组件写法:

<child-component :prop1="var1" :prop2="var2" :prop="var3" ... />

其实可以在父组件上直接使用v-bind={子组件props集合}

传入一个对象

比如当一个组件有诸多配置项,且当没有传入配置项取用组件内部默认项的时候,为了方便覆写子组件的内部配置项,不妨使用一个对象将配置收集到一起,但是这种做法有两个缺点,谨慎使用

  1. 不能利用 props 验证对象里面每个的值类型
  2. 如果你子组件修改了父组件传入的对象A,父组件的对象A也会发生修改,所以我一般只有子组件不会修改父组件传入的值的情况下,我才会传入对象。父组件把对象传入子组件,是实现双向绑定的hack方式,但不推荐,vue 3.0可能就要对这种方式说拜拜了,为了以后代码好改,还是不要用这种方式实现双向绑定
<child-component v-model="text" :setting="{color:'bule'}" />

// 子组件内部读取配置,通过扩展运算符替换掉默认配置
const setting ={
 ...defaultSetting,
 ...this.setting
}

 还有一种鱼和熊掌兼得的方法,可以给子组件包一层,叫中间组件。父组件将配置项作为一个对象传入中间组件,在中间组件里面对默认配置项进行初始化和覆写,然后再以v-bind={生成好的配置}的方式传入子组件,在子组件里面进行验证。

computed 属性

vue 的 computed 属性默认是只读的,你可以提供一个 setter。它可以优化我写组件的逻辑,适用于父组件处理的值和子组件处理的值是同一个的情况

<template>
 <el-select v-model="email">
  <el-option
   v-for="item in adminUserOptions"
   :key="item.email"
   :label="item.email"
   :value="item.email"
  />
 </el-select>
</template>
export default {
 props: {
  value: {}
 },
 computed: {
  email: {
   get() {
    return this.value
   },
   set(val) {
    this.$emit('input', val)
    this.$emit('change', val)
   }
  }
 }
}

灵活的 prop

我们常看到一些优秀的组件库,传入的值既可以是一个 String/Number,也可以是一个函数。

比如ElementUI的Table组件,当你想要显示树形数据的时候,必须传入row-key。看它的介绍就知道是有多灵活:

row-key的作用:行数据的 Key,用来优化 Table 的渲染;在使用 reserve-selection 功能与显示树形数据时,该属性是必填的。类型为 String 时,支持多层访问:user.info.id,但不支持 user.info[0].id,此种情况请使用 Function

处理 rowKey 生成 RowIdentity 的函数源码:

//https://github.com/ElemeFE/element/blob/dev/packages/table/src/util.js
export const getRowIdentity = (row, rowKey) => {
 if (!row) throw new Error('row is required when get row identity')
 // 行数据的key
 if (typeof rowKey === 'string') {
  if (rowKey.indexOf('.') < 0) {
   return row[rowKey]
  }
  // 支持多层访问:user.info.id
  let key = rowKey.split('.')
  let current = row
  for (let i = 0; i < key.length; i++) {
   current = current[key[i]]
  }
  return current
  // 通过函数自定义
  // 我处理过父和子id可能相同的情况,只好通过Function自定义
  // 不可以通过时间或者随机字符串生成ID
 } else if (typeof rowKey === 'function') {
  return rowKey.call(null, row)
 }
}

由于业务场景多变,组件的设计者很难考虑完全,不妨设计灵活的 prop,由开发者自行定义

事件

emit/on

读者肯定知道 emit/on 如何使用,我就简单说一下 vue 的 v-model和sync的语法糖,我们可以利用这些语法糖,帮助我们写出简洁的代码(父组件可以少写监听子组件的事件,比如你不用写@input)

v-model

看一下下面的代码示例,就能懂这句话了。v-model 会忽略所有表单元素的 value、checked、selected 特性的初始值而总是将 Vue 实例的数据作为数据来源。你应该通过 JavaScript 在组件的 data 选项中声明初始值

<input v-model="searchText" />

<input
 v-bind:value="searchText"
 v-on:input="searchText = $event.target.value"
/>

// 当把v-model用在组件上

<custom-input
 v-bind:value="searchText"
 v-on:input="searchText = $event"
></custom-input>

为了让它正常工作,这个组件内的 <input> 必须:将其 value 特性绑定到一个名叫 value 的 prop 上在其 input 事件被触发时,将新的值通过自定义的 input 事件抛出,即this.$emit('input',changedValue)

自定义 v-model

为啥要自定义组件的 v-model 呢,因为数据不符合要求呗。你的输入值不可能总是 value ,你的事件不可能总是 input,具体详见文档

sync(双向绑定语法糖)

vue 真的是方便了开发者很多,站在开发者的角度考虑,很大的提升开发效率

以  update:myPropName  的模式触发事件取代双向绑定this.$emit('update:title', newTitle),具体详见文档

Function 通过 prop 传入

本来想放在 prop 部分的,但是个人觉得其实它和 emit/on 更有关系一点

有读者可能会问,为什么不能把子组件里面的事件 emit 出来,通过父组件处理?然后传入一个控制子组件的 prop 属性。
我想说的是,可以,但是这样真的很麻烦,子组件内部的状态却要依赖父组件传值。

该组件内部的状态,我们需要把它暴露出来嘛?我觉得不需要,组件内部的状态就让它处于组件内部

但是可以通过传入 function(你可以理解为一个钩子),参与组件状态变更的行为。比如很好用的拖拽库,Vue.Draggable控制元素是否被拖动的行为。

Vue.Draggable可以传入一个 move 方法,我们看一下它如何处理的。

 

onDragMove(evt, originalEvent) {
   const onMove = this.move;
   // 如果没有传入move,那么返回true,可以移动
   if (!onMove || !this.realList) {
    return true;
   }

   const relatedContext = this.getRelatedContextFromMoveEvent(evt);
   const draggedContext = this.context;
   const futureIndex = this.computeFutureIndex(relatedContext, evt);
   Object.assign(draggedContext, { futureIndex });
   const sendEvt = Object.assign({}, evt, {
    relatedContext,
    draggedContext
   });
   // 组件行为由传入的move函数控制
   return onMove(sendEvt, originalEvent);
}

 这样做的好处,就是组件内部自由一套运行逻辑,但是我可以通过传入 function 来干预。我没有直接修改组件内部状态,而是通过函数(你可以称它为钩子)去触发,方便调试组件,使得组件行为具有可预测性

父组件直接操作子组件

很少有这样的骚操作,但是由于数据和操作的复杂性,当数据结构复杂,嵌套过深的情况下,父组件很难对于子组件的数据的精细控制

因此,如果不得已而为之,请在文档里,把子组件可以调用的方法暴露出来,供使用者使用。使用这种组件比较麻烦,得去看文档,没有文档的只好去看源码

ElementUI的tree组件提供了很多方法,用于父组件去操作子组件。

eg:this.$refs.tree.setCheckedKeys([]);

插槽

HTML <slot> element 是 Web Components 技术的一部分,是自定义 web 组件的占位符,vue 里面的 slot 的灵感来自 Web Components 规范草案,具体见文档

默认插槽

能用默认插槽就不要使用具名插槽,我真的不想使用你这个组件的时候还去翻看你的插槽叫什么名字

之前我司一个网页模板 三个插槽,header,body,footer,我用的是真的难受,每次都记不得,看似三个单词都挺熟悉的,但是其实 head,content,foot 这些单词也都行啊,谁知道用啥(可能我老了吧,组件如果不是必要尽量不要让人有记忆成本)。

后备内容

就是给组件里面的插槽定义默认值,它只会在没有提供内容的时候被渲染。建议用上插槽就给它添加默认内容

封装他人组件

有些时候我们可能是对他人的组件进行封装,这里强烈推荐使用v-bind="$attrs" 和 v-on="$listeners"。 vm.$attrs 是一个属性,其包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 (class 和 style 除外)。这些未识别的属性可以通过 v-bind="$attrs" 传入内部组件。未识别的事件可通过v-on="$listeners"传入

举个例子,比如我创建了我的按钮组件myButton,封装了 element-ui 的 el-button 组件(其实什么事情都没做),在使用组件 <my-button />时,就可以直接在组件上使用 el-button 的属性,不被 prop 识别的属性会传入到 el-button 元素上去

<template>
 <div>
  <el-button v-bind="$attrs">导出</el-button>
 <div>
</template>
// 父组件使用
<my-button type='primary' size='mini'/>

组件命名

这里推荐遵循 vue 官方指南,值得一看

我们构建组件的时候通常会将其入口命名为 index.vue ,引入的时候,直接引入该组件的文件夹即可。

但是这样做会有一个问题,当你编辑多个组件的时候,所有的组件入口都叫做index.vue,容易糊涂

vscode 显然意识到了这个问题,所以当文件名相同的文件被打开时,它会在文件名旁边显示文件夹名

如何解决呢,我们可以把 index.js 当作一个单纯的入口,不承担任何逻辑。仅仅负责引入component-name-container以及export default component-name-container

my-app
└── src
    └── components
        └── component-name
          ├── component-name.css
          ├── component-name-container.vue
          └── index.js

 tips(个人喜好)

template,把一个<template> 元素当做不可见的包裹元素,并在上面使用 v-if。最终的渲染结果将不包含 <template> 元素
能用 computed 计算属性的,尽量就不用 watch

模板里面写太多 v-if 会让你的模板很难看,v-else-if尽量还是别用了吧。一长串的 if else,在模板里面看的很乱

以上所述是小编给大家介绍的如何写好一个vue组件详解整合,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
让焦点自动跳转
Jul 01 Javascript
javascript 表单的友好用户体现
Jan 07 Javascript
jQuery控制的不同方向的滑动(向左、向右滑动等)
Jul 18 Javascript
基于豆瓣API+Angular开发的web App
Jan 02 Javascript
JavaScript跨浏览器获取页面中相同class节点的方法
Mar 03 Javascript
JavaScript中的数组遍历forEach()与map()方法以及兼容写法介绍
May 19 Javascript
jquery uploadify隐藏上传进度的实现方法
Feb 06 Javascript
js实现做通讯录的索引滑动显示效果和滑动显示锚点效果
Feb 18 Javascript
深入浅析ES6 Class 中的 super 关键字
Oct 20 Javascript
Vue一次性简洁明了引入所有公共组件的方法
Nov 28 Javascript
微信小程序使用for循环动态渲染页面操作示例
Dec 25 Javascript
vue 通过 Prop 向子组件传递数据的实现方法
Oct 30 Javascript
微信小程序云开发 生成带参小程序码流程
May 18 #Javascript
详解小程序开发经验:多页面数据同步
May 18 #Javascript
JavaScript实现星级评价效果
May 17 #Javascript
JavaScript实现美化滑块效果
May 17 #Javascript
vue中使用mxgraph的方法实例代码详解
May 17 #Javascript
vue中引入mxGraph的步骤详解
May 17 #Javascript
微信小程序云开发 搭建一个管理小程序
May 17 #Javascript
You might like
PHP PDOStatement::fetchColumn讲解
2019/01/31 PHP
仅img元素创建后不添加到文档中会执行onload事件的解决方法
2011/07/31 Javascript
js鼠标滑轮滚动事件绑定的简单实例(兼容主流浏览器)
2014/01/14 Javascript
javascript实现Table间隔色以及选择高亮(和动态切换数据)的方法
2015/05/14 Javascript
JavaScript使用键盘输入控制实现数字验证功能
2016/08/19 Javascript
Web打印解决方案之普通报表打印功能
2016/08/29 Javascript
nodejs实现发出蜂鸣声音(系统报警声)的方法
2017/01/18 NodeJs
jQuery实现链接的title快速出现的方法
2017/02/20 Javascript
jQuery插件echarts去掉垂直网格线用法示例
2017/03/03 Javascript
JS实现微信摇一摇原理解析
2017/07/22 Javascript
vue webpack开发访问后台接口全局配置的方法
2018/09/18 Javascript
NodeJS读取分析Nginx错误日志的方法
2019/05/14 NodeJs
vue中使用mxgraph的方法实例代码详解
2019/05/17 Javascript
[46:50]Liquid vs Mineski 2018国际邀请赛小组赛BO2 第二场 8.18
2018/08/19 DOTA
python去掉行尾的换行符方法
2017/01/04 Python
Python异常对代码运行性能的影响实例解析
2018/02/08 Python
对Python3之进程池与回调函数的实例详解
2019/01/22 Python
基于python的socket实现单机五子棋到双人对战
2020/03/24 Python
python判断一个对象是否可迭代的例子
2019/07/22 Python
如何使用Flask-Migrate拓展数据库表结构
2019/07/24 Python
python 实现性别识别
2020/11/21 Python
HTML5 Canvas渐进填充与透明实现图像的Mask效果
2013/07/11 HTML / CSS
英国乐购杂货:Tesco Groceries
2018/11/29 全球购物
机电一体化专业应届本科生求职信
2013/09/27 职场文书
文明学生标兵事迹
2014/01/21 职场文书
英语专业学生个人求职信
2014/01/28 职场文书
投资合作协议书
2014/04/17 职场文书
难忘的一天教学反思
2014/04/30 职场文书
四风对照检查剖析材料
2014/10/07 职场文书
先进党支部事迹材料
2014/12/24 职场文书
小升初自荐信怎么写
2015/03/26 职场文书
2015年大学生暑期实习报告
2015/07/13 职场文书
考教师资格证不要错过的4个最佳时机
2019/07/17 职场文书
导游词之日本富士山
2020/01/06 职场文书
python单元测试之pytest的使用
2021/06/07 Python
帮你提高开发效率的JavaScript20个技巧
2021/06/18 Javascript