vue组件开发菜鸟教程

时间:2023-10-01 22:54:48来源:互联网

下面小编就为大家分享一篇vue组件开发菜鸟教程,具有很好的参考价值,希望对大家有所帮助。

什么叫做组件化

vue.js 有两大法宝,一个是数据驱动,另一个就是组件化,那么问题来了,什么叫做组件化,为什么要组件化?接下来我就针对这两个问题一一解答,所谓组件化,就是把页面拆分成多个组件,每个组件依赖的 CSS、JS、模板、图片等资源放在一起开发和维护。 因为组件是资源独立的,所以组件在系统内部可复用,组件和组件之间可以嵌套,如果项目比较复杂,可以极大简化代码量,并且对后期的需求变更和维护也更加友好。

1696171532182013.jpg

参考: 组件基础

如何进行组件化开发

先看下图:

1696171538279362.png

这是 vue.js 中的一个报错,原因是使用了一个未经注册的组件 lx-xxx ,这个报错告诉我们一个道理:使用自定义组件之前必须注册。  
那么如何注册一个组件呢? Vue.js 提供了 2 种组件的注册方式,全局注册和局部注册。

1. 全局注册

在 vue.js 中我们可以使用 Vue.component(tagName, options) 进行全局注册,例如

Vue.component('my-component', {  // 选项})

2. 局部注册

Vue.js 也同样支持局部注册,我们可以在一个组件内部使用 components 选项做组件的局部注册,例如:

import HelloWorld from './components/HelloWorld'export default {  components: {    HelloWorld  }}

区别:全局组件是挂载在 Vue.options.components 下,而局部组件是挂载在 vm.$options.components 下,这也是全局注册的组件能被任意使用的原因。

组件化开发必备知识

所谓工欲善其事,必先利其器,在正式开发一个组件之前,我们先要掌握一些必备的知识,这里我只会简单介绍一下,详情参考官网。

name

组件的名称,必填

<lx-niu/><lx-niu></lx-niu/>name: 'lxNiu'

js 中使用驼峰式命令,HTML 使用kebab-case命名。

props

组件属性,用于父子组件通信,可通过this.msg访问

<div>{{msg}}</div>props: {  msg: {    type: String,    default: ''  }}show: Boolean // 默认falsemsg: [String, Boolean]  // 多种类型

computed

处理data或者props中的属性,并返回一个新属性

<div>{{newMsg}}</div>computed: {  newMsg() {    return 'hello ' + this.msg  }},

注:因为props,data和computed在编译阶段都会作为vm的属性合并,所以不可重名

render

用render函数描述template

<lx-niu tag='button'>hello world</lx-niu><script type="text/javascript">  export default {    name: 'lxNiu',    props: {      tag: {        type: String,        default: 'div'      },    },    // h: createElement    render(h) {      return h(this.tag,        {class: 'demo'},         this.$slots.default)    }  }</script>

render 中的 h 其实就是 createElement,它接受三个参数,返回一个 vnode  
h 参数解释:  
args1: {string | Function | Object} 用于提供DOM的html内容  
args2: {Object} 设置DOM样式、属性、绑定事件之类  
args3: {array} 用于设置分发的内容

注:vue编译顺序: template&ndash;> compile --> render --> vnode --> patch --> DOM

slot

分发内容,有传入时显示,无传入 DOM 时显示默认,分为无名和具名两种,this. s l o t s . d e f a u l t 默 认 指 向 无 名 插 槽 , 多 个 s l o t 时 用 法 t h i s . slots.default 默认指向无名插槽,多个 slot 时用法 this. slots.default默认指向无名插槽,多个slot时用法this.slots.name

<lx-niu>  <div slot='header'>header</div>  <div class="body" slot='body'>    <input type="text">  </div>  <div slot='footer'>footer</div>  <button class='btn'>button</button></lx-niu><template>  <div>    <slot name='header'></slot>    <slot name='body'></slot>    <slot name='footer'></slot>    <slot></slot>  </div></template><script>  export default {    name: 'lxNiu',    mounted() {      this.$slots.header // 包含了slot="foo"的内容      this.$slots.default // 得到一个vnode,没有被包含在具名插槽中的节点,这里是button    }  }</script>

class

定义子组件的类名

// 父组件<lx-niu round type='big'/>// 子组件<div :class="[  type ? 'lx-niu__' + type : '',  {'is-round': round},]">控制</div>//真实DOM<div class='lx-niu__big is-round'>hello</div>

style

向子组件传递样式

// 父组件<lx-niu :bodyStyle='{color: "red"}'/>// 子组件<template>  <div :style='bodyStyle'>hello world</div></template><script>  export default {    name: 'lxNiu',    props: {      bodyStyle: {},    },  }</script>

其他属性$attrs

v-bind="$attrs" 将除class和style外的属性添加到父组件上,如定义input:

<input v-bind="$attrs">

v-once

组件只渲染一次,后面即使数据发生变化也不会重新渲染,比如例子中val不会变成456

<template>  <div>    <button @click="show = !show">button</button>    <button @click="val = '456'">button</button>    <div v-once v-if="show">      <span>{{val}}</span>    </div>  </div></template><script>export default {  data() {    return{      show: false,      val: '123'    }  },};</script>

mixins

// mixin.jsexport default {  data() {    return{       msg: 'hello world'    }  },  methods: {    clickBtn() {      console.log(this.msg)    }  },}// index.vue<button @click="clickBtn">button</button>import actionMixin from "./mixin.js";export default {  methods: {},  mixins: [actionMixin]}

实例演示

比如我们要注册一个 lx-button 这样一个组件,那么目录和伪代码如下:

1696171559717834.jpg

index.vue

<template>  <button>lxButton</button></template><script>export default {  name: 'lxButton'}</script>

index.js

import lxButton from './src/index'lxButton.install = (Vue) => {  Vue.component(lxButton.name, lxButton)}export default lxButton

其中 install 是 Vue.js 提供了一个公开方法,这个方法的第一个参数是 Vue 构造器,第二个参数是一个可选的选项对象。
MyPlugin.install = function (Vue, options){}

watch-弹窗实现原理

<button @click="dialogVisible = true">显示</button><lx-niu :visible.sync="dialogVisible"></lx-niu><script>  export default {    data() {      return {        dialogVisible: false      }    },    watch: {      dialogVisible(val) {        console.log('father change', val)      }    }  }</script>

定义组件

<template>  <div v-show="visible">    <button @click="hide">关闭</button>  </div></template><script>  export default {    name: 'lxNiu',    props: {      visible: Boolean    },    watch: {      visible(val) {        console.log('child change:', val)      }    },    methods: {      hide() {        this.$emit('update:visible', false);      }    },  }</script>

点击父组件中的显示按钮,改变传入子组件中的值,点击子组件中的关闭,改变父组件中值。

注:@click=&ldquo;dialogVisible = true&rdquo; 点击时将dialogVisible的值改为true  
注::visible.sync: 双向数据绑定,配合update:visible使用,实现子组件修改父组件中的值

解释: sync

col组件实例

export default {  name: 'ElCol',  props: {    span: {      type: Number,      default: 24    },    tag: {      type: String,      default: 'div'    },    offset: Number,    pull: Number,    push: Number,    xs: [Number, Object],    sm: [Number, Object],    md: [Number, Object],    lg: [Number, Object],    xl: [Number, Object]  },  computed: {    gutter() {      let parent = this.$parent;      while (parent && parent.$options.componentName !== 'ElRow') {        parent = parent.$parent;      }      return parent ? parent.gutter : 0;    }  },  render(h) {    let classList = [];    let style = {};    if (this.gutter) {      style.paddingLeft = this.gutter / 2 + 'px';      style.paddingRight = style.paddingLeft;    }    ['span', 'offset', 'pull', 'push'].forEach(prop => {      if (this[prop] || this[prop] === 0) {        classList.push(          prop !== 'span'            ? `el-col-${prop}-${this[prop]}`            : `el-col-${this[prop]}`        );      }    });    ['xs', 'sm', 'md', 'lg', 'xl'].forEach(size => {      if (typeof this[size] === 'number') {        classList.push(`el-col-${size}-${this[size]}`);      } else if (typeof this[size] === 'object') {        let props = this[size];        Object.keys(props).forEach(prop => {          classList.push(            prop !== 'span'              ? `el-col-${size}-${prop}-${props[prop]}`              : `el-col-${size}-${props[prop]}`          );        });      }    });    return h(this.tag, {      class: ['el-col', classList],      style    }, this.$slots.default);  }};

col组件使用render函数,而不是template来实现组件,原因有两个:

该组件有大量的类判断,如果采用template代码比较冗余,使用js代码更加简洁 直接render描述性能更好

解释: render-function

button组件实例

<template>  <button    class="el-button"    @click="handleClick"    :disabled="buttonDisabled || loading"    :autofocus="autofocus"    :type="nativeType"    :class="[      type ? 'el-button--' + type : '',      buttonSize ? 'el-button--' + buttonSize : '',      {        'is-disabled': buttonDisabled,        'is-loading': loading,        'is-plain': plain,        'is-round': round,        'is-circle': circle      }    ]"  >    <i class="el-icon-loading" v-if="loading"></i>    <i :class="icon" v-if="icon && !loading"></i>    <span v-if="$slots.default"><slot></slot></span>  </button></template><script>  export default {    name: 'ElButton',    inject: {      elForm: {        default: ''      },      elFormItem: {        default: ''      }    },    props: {      type: {        type: String,        default: 'default'      },      size: String,      icon: {        type: String,        default: ''      },      nativeType: {        type: String,        default: 'button'      },      loading: Boolean,      disabled: Boolean,      plain: Boolean,      autofocus: Boolean,      round: Boolean,      circle: Boolean    },    computed: {      _elFormItemSize() {        return (this.elFormItem || {}).elFormItemSize;      },      buttonSize() {        return this.size || this._elFormItemSize || (this.$ELEMENT || {}).size;      },      buttonDisabled() {        return this.disabled || (this.elForm || {}).disabled;      }    },    methods: {      handleClick(evt) {        this.$emit('click', evt);      }    }  };</script>

局部组件实例

<template>  <div class="login">    <login-header />    <login-request />    <login-footer />  </div></template><script>import loginHeader from './login-header';import loginRequest from './login-request';import loginFooter from './login-footer';export default {  components: {    [loginHeader.name]: loginHeader,    [loginRequest.name]: loginRequest,    [loginFooter.name]: loginFooter  }};</script>

8. 分享总结 首先介绍了什么是组件化开发,以及为什么要进行组件化 其次介绍了组件开发的两种方式和适用场景,以及进行组件化开发的必备知识 最后实例演示了全局组件和局部组件的开发

本站部分内容转载自互联网,如果有网站内容侵犯了您的权益,可直接联系我们删除,感谢支持!