vue--组件开发

2023-11-16

目录

一、button 组件开发

1.1 整体目标

1.2 确定组件API

1.3 编写测试基础Button

1.4 完成type配置

1.5 完成size配置

1.6 完成事件绑定

1.7 总结

二、Editor编辑器组件开发

2.1 确定基础API

2.2 编写测试基础Editor

2.3 完成v-model双向绑定

2.4 总结


一、button 组件开发

1.1 整体目标

  •  了解组件开发的整体流程
  •  掌握组件事件和标签事件的区别
  •  掌握在组件上使用v-model的方式

1.2 确定组件API

属性

属性名 说明 类型 默认值
type 设置按钮类型,可选值为 primary danger 或者不设 String default
size 设置按钮大小,可选值为 small large 或者不设 String default

事件

事件名称 说明 回调参数
click 按钮点击事件 (event) => void

1.3 编写测试基础Button

组件有很多的功能,但是这些功能都是由一个最原始的组件逐渐扩展而来的,所以我们先完成一个最基础的button组件,然后逐渐往上添加功能

编写Button组件

目的:完成基础结构 + 基础样式

components/Button/index.vue,

<template>
  <button class="h-btn">
    <slot></slot>
  </button>
</template>
<style scoped lang="less">
    // 默认背景色 默认大小 基础样式 不管传什么参数这个样式都在
    .h-btn {
      line-height: 1.499;
      position: relative;
      display: inline-block;
      font-weight: 400;
      white-space: nowrap;
      text-align: center;
      background-image: none;
      box-shadow: 0 2px 0 rgba(0, 0, 0, 0.015);
      cursor: pointer;
      transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
      -webkit-user-select: none;
      -moz-user-select: none;
      -ms-user-select: none;
      user-select: none;
      touch-action: manipulation;
      padding: 5px 10px;
      font-size: 14px;
      border-radius: 4px;
      color: rgba(0, 0, 0, 0.65);
      background-color: #fff;
      border: 1px solid #d9d9d9;
    }
    .h-btn:focus {
      outline: 0;
    }
</style>

由于button中的文字是动态的,完全是由用户使用时决定,所以我们需要设计一个插槽,用来渲染传入的自定义文字

测试基础Button--在App.vue里

App.vue,

<template>
  <div>
    <h-button>Default</h-button>
    <h-button>Danger</h-button>
    <h-button>Primary</h-button>
  </div>
</template>

<script>
import HButton from '@/components/Button'
export default {
  components:{
    HButton
  }
}
</script>

进过测试,我们编写的button组件可以进行正常使用,并且插槽功能是生效的

1.4 完成type配置

核心思路:通过prop传入的值的不同切换需要渲染的类名,达到显示不一样背景色的目的

1. 准备对应class类

 components/Button/index.vue,

<style scoped lang="less">
    // primary类
    .h-btn-primary {
      color: #fff;
      background-color: #1890ff;
      border-color: #1890ff;
      text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.12);
      box-shadow: 0 2px 0 rgba(0, 0, 0, 0.045);
    }
    // danger类
    .h-btn-danger {
      color: #fff;
      background-color: #ff4d4f;
      border-color: #ff4d4f;
      text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.12);
      box-shadow: 0 2px 0 rgba(0, 0, 0, 0.045);
    }
</style>

2. 编写props并根据不同的prop切换class

因为要添加的类名是根据prop的不同计算得来的,所以我们可以使用计算属性来完成匹配计算,然后我们找到匹配规则,类名为 h-btn-danger,prop值为danger,所以计算公式为:h-btn-prop

components/Button/index.vue, 

<template>
  <!-- <button class="h-btn" :class="typeClass"> 或 -->
  <!-- 动态class的数组语法可以同时支持多个动态class的绑定 -->
  <button class="h-btn" :class="[typeClass]">
    <slot></slot>
  </button>
</template>

<script>
export default {
  // 在子类里通过 props 进行接收
  // 根据不同的 type 值,给 button 绑定不同的类名
  // default --> h-btn-default
  // primary --> h-btn-primary
  // danger --> h-btn-danger
  // 通用计算公式:type -> h-btn-type
  props: {
    type: {
      type: String,
      // 默认值
      default() {
        return 'default'
      }
    }
  },
  computed: {
    typeClass() {
      return `h-btn-${this.type}`
    }
  }
}
</script>
 

3. 测试type属性

App.vue

<template>
  <div>
    <!-- 将 type 属性传递给子类 -->
    <h-button>Default</h-button>
    <h-button type="danger">Danger</h-button>
    <h-button type="primary">Primary</h-button>
  </div>
</template>

<script>
import HButton from '@/components/Button'
export default {
  components:{
    HButton
  }
}
</script>

1.5 完成size配置

1. 准备对应class类

 components/Button/index.vue,

<style scoped lang="less">
    // size:small
    .h-btn-small {
      padding: 4px 8px;
      font-size: 12px;
    }
    // size:large
    .h-btn-large {
      padding: 6px 12px;
      font-size: 16px;
    }
</style>

2. 编写props并根据不同的prop切换class

 components/Button/index.vue,

<template>
  <button class="h-btn" :class="[typeClass, sizeClass]">
    <slot></slot>
  </button>
</template>

<script>
export default {
// 在子类里通过 props 进行接收
  props: {
    size: {
      type: String,
      default(){
         return 'default'
      },
    }
  },
  computed: {
    sizeClass(){
      return `h-btn-${this.size}`
    }
  }
}
</script>

4. 测试size属性

App.vue,

<template>
  <div>
    测试size属性:
    <h-button>Default</h-button>
    <!-- 将 type 属性传递给子类 -->
    <h-button size="small">Small</h-button>
    <h-button size="large">Large</h-button>
  </div>
</template>

<script>
import HButton from '@/components/Button'
export default {
  components:{
    HButton
  }
}
</script>

5.实现参数的校验

有时候用户并不会按照要求来,比如我们这里只有 type="danger" 以及 type="primary" 但是用户非要将其改成其他值 type="big"

因此就需要用到参数校验

 components/Button/index.vue,

  props: {
    type: { 
      type: String,
      default: 'default' // props 的默认配置,如果不传以默认为主
    },
    size: {
      type: String,
      default: 'default',
      // 参数校验
      // value 表示当前传递过来的值
      validator: function (value) {
        // 对传入的参数值做校验,满足返回true,否则返回false
        // 如果传入的参数不在可选参数中,给出用户提示,告知哪个参数传错了
        const sizeList = ['small', 'large', 'default']
        return sizeList.includes(value)
      }
    }

1.6 完成事件绑定

1.组件直接绑定click事件

 App.vue,

<template>
  <div>
    <h-button size="large" @click="clickHandler">Large</h-button>
  </div>
</template>

<script>
import HButton from '@/components/Button'
export default {
  components:{
    HButton
  },
  methods:{
    clickHandler(){
     console.log('按钮点击了')
    }
  }
}
</script>

测试发现,点击事件并没有绑定成功,接下来我们说一下,vue系统中的事件系统

  1. 浏览器原生事件 (在浏览器支持的原生标签上绑定的事件)

    <button @click="handler"></button>
  2. 组件事件 (在组件身上绑定的事件)

    <h-button @click="handler"></h-button>

组件绑定的事件默认是不会被浏览器识别的,我们需要做额外的处理让事件生效,有俩种方案:

  1. 添加 .native 修饰符

    添加修饰符之后,事件会被绑定到组件的根元素身上

  2. 把click当成自定义事件通过 $emit 执行(推荐)

2. 使用$emit方法触发事件

用户的本意是想在点击button按钮的时候,触发组件身上绑定的click回调函数

App.vue,

<h-button @click="clickHandler">Defualt</h-button>

  methods: {
    clickHandler(e){
      console.log('按钮点击了', e);
    }
  }

  components/Button/index.vue, 

<template>
  <button @click="clickHandler">
    <slot></slot>
  </button>
</template>

<script>
export default {
  methods: {
    clickHandler(e) {
      // 触发自定义事件click,并传递事件对象e
      this.$emit('click', e)
    }
  }
}
</script>

1.7 总结

  1. 编写组件时应该API先行,先确定组件该如何给用户用,再根据API编写逻辑
  2. props的名称应该具备语义化,类型应该符合规范,并且可以添加自定义校验
  3. 组件上绑定的类似于原生的事件,默认是不会被识别的,需要额外处理
  4. 组件有一些设计需要整体把控,比如props与对应类名的匹配,这是我们故意设计的

完整代码:

components/Button/index.vue, 

<template>
  <!-- <button class="h-btn" :class="typeClass"> 或 -->
    <!-- 动态class的数组语法可以同时支持多个动态class的绑定 -->
  <button class="h-btn" :class="[typeClass, sizeClass]" @click="clickHandler">
    <slot />
  </button>
</template>

<script>
export default {
  // 在子类里通过 props 进行接收
  // 根据不同的 type 值,给 button 绑定不同的类名
  // default --> h-btn-default
  // primary --> h-btn-primary
  // danger --> h-btn-danger
  // 通用计算公式:type -> h-btn-type
  props: {
    type: { 
      type: String,
      default: 'default' // props 的默认配置,如果不传以默认为主
    },
    size: {
      type: String,
      default: 'default',
      // 参数校验
      // value 表示当前传递过来的值
      validator: function (value) {
        // 对传入的参数值做校验,满足返回true,否则返回false
        // 如果传入的参数不在可选参数中,给出用户提示,告知哪个参数传错了
        const sizeList = ['small', 'large', 'default']
        return sizeList.includes(value)
      }
    }
  },
  computed: {
    typeClass(){
      return `h-btn-${this.type}`
    },
    sizeClass(){
      return `h-btn-${this.size}`
    }
  },
  methods: {
    clickHandler(e){
      // 触发父组件传下来的自定义事件 click
      this.$emit('click', e)
    }
  }
}
</script>

<style scoped lang="less">
// 默认背景色 默认大小 基础样式 不管传什么参数这个样式都在
.h-btn {
  line-height: 1.499;
  position: relative;
  display: inline-block;
  font-weight: 400;
  white-space: nowrap;
  text-align: center;
  background-image: none;
  box-shadow: 0 2px 0 rgba(0, 0, 0, 0.015);
  cursor: pointer;
  transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
  touch-action: manipulation;
  padding: 5px 10px;
  font-size: 14px;
  border-radius: 4px;
  color: rgba(0, 0, 0, 0.65);
  background-color: #fff;
  border: 1px solid #d9d9d9;
}
.h-btn-default:focus {
  outline: 0;
}
// primary 确定样式
.h-btn-primary {
  color: #fff;
  background-color: #1890ff;
  border-color: #1890ff;
  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.12);
  box-shadow: 0 2px 0 rgba(0, 0, 0, 0.045);
}
// danger 危险样式
.h-btn-danger {
  color: #fff;
  background-color: #ff4d4f;
  border-color: #ff4d4f;
  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.12);
  box-shadow: 0 2px 0 rgba(0, 0, 0, 0.045);
}

// size:small
.h-btn-small {
  padding: 4px 8px;
  font-size: 12px;
}
// size:large
.h-btn-large {
  padding: 6px 12px;
  font-size: 16px;
}
</style>

App.vue,

<template>
  <div id="app">
    测试 type 属性:
    <h-button>Defualt</h-button>
    <!-- 将 type 属性传递给子类 -->
    <h-button type="primary">Primary</h-button>
    <h-button type="danger">Danger</h-button>

    <br>
    <br>

    测试 size 属性:
    <h-button>Defualt</h-button>
    <!-- 将 type 属性传递给子类 -->
    <h-button size="small">Primary</h-button>
    <h-button size="large">Danger</h-button>

    <br>
    <br>
    事件测试:
    <h-button @click="clickHandler">Defualt</h-button>
  </div>
</template>

<script>
import HButton from './components/Button'
export default {
  name: 'App',
  components: {
    HButton
  },
  methods: {
    clickHandler(e){
      console.log('按钮点击了', e);
    }
  }
}
</script>


二、Editor编辑器组件开发

Button组件的编写,我们是从零开始的,接下来我们借助一些开源的三方基础插件,完成我们自己编辑器组件的编写

组件依赖:wangEditor

安装依赖: npm i wangeditor --save

2.1 确定基础API

指令

指令名 说明 类型 默认值
v-model 提供编辑器数据的双向绑定 String

2.2 编写测试基础Editor

编写Editor组件

components/Editor/index.vue

<template>
  <div class="editorContainer" ref="editor"></div>
</template>

<script>
import E from 'wangeditor'
export default {
  methods: {
    initEditor(){
      // 编辑器初始化操作
      const editor = new E(this.$refs.editor)
      // 或者 const editor = new E( document.getElementById('editorContainer') )
      editor.create()
    }
  },
  mounted(){
    // 在这里初始化保证dom是存在的
    this.initEditor()
  }
}
</script>

测试组件

App.vue

<template>
  <div>
    <Editor/>
  </div>
</template>

<script>
import Editor from '@/components/Editor'
export default {
  components:{
    Editor
  }
}
</script>

2.3 完成v-model双向绑定

前置知识

当我们在一个组件身上通过v-model绑定一个响应式数据时,记住,他是一个语法糖,实际上相当于完成了俩件事情

  1. 组件上绑定了一个名为value 的自定义属性
  2. 组件身上绑定了一个名为input的自定义事件

1. 接受数据传入

App.vue,
<template>
  <div>
    <Editor v-model="content"/>
  </div>
</template>

<script>
import Editor from '@/components/Editor'
export default {
  components:{
    Editor
  },
  data(){
    return {
      content:'我是编辑器'
    }
  }
}
</script>

Editor/index.vue,

<template>
  <div class="editorContainer" ref="editor"></div>
</template>

<script>
import E from 'wangeditor'
export default {
  props: {
    // 接收数据传入
    value: {
      type: String
    }
  },
  methods: {
    initEditor() {
      const editor = new E(this.$refs.editor)
      editor.create()

    }
  },
  mounted() {
    console.log('传过来的数据为', this.value)
    this.initEditor()
  }
}
</script>

2. 实现数据回显

数据我们拿到了,然后就可以把它渲染到编辑器内部了,编辑器有一个方法是专门用来设置内容的,我们找到它,editor.txt.html('富文本内容')

Editor/index.vue, 

initEditor() {
  const editor = new E(this.$refs.editor)
  // 监听编辑器改动事件,把最新内容传出去
  editor.config.onchange = (newHtml) => {
    console.log('change 之后最新的 html', newHtml)
    this.$emit('input', newHtml)
  }
  editor.create()
  editor.txt.html(this.value)
}

4. Bug修复

看起来我们实现了数据的传入回显和修改时的内容传出,接下来我们在 App.vue 中动态的修改一下传入的content,看看编辑器有没有实时响应得到显示

我们通过调试工具,修改content属性的值,发现编辑器并没有得到显示,然后再查看props数据,发现最新的数据已经传进去了

之所以没有显示到编辑器中,是因为编辑器类似一个独立的个体,它并不知道props已经变成新内容了,所以我们的思路是: 监听props的变化,然后把props的值设置到编辑器里

如何监听 - watch

如何设置 - editor.txt.html()

Editor/index.vue, 

initEditor() {
  const editor = new E(this.$refs.editor)
  editor.config.onchange = (newHtml) => {
    console.log('change 之后最新的 html', newHtml)
    this.$emit('input', newHtml)
  }
  editor.create()
  editor.txt.html(this.value)
  // 为了能使用editor对象,我们采取一个更加灵活的命令式监听写法
  this.$watch('value', () => {
    editor.txt.html(this.value)
  })
}

再次测试,发现双向绑定已经完全正常,nice~

2.4 总结

通过这一节的学习,我们应该掌握以下知识点

  1. 组件上绑定v-model 等同于做了什么
  2. watch监听的另外一种调用方法命令式的监听方法,功能一样
  3. 使用三方现成的开源插件编写自己组件的流程(基础使用 、三方方法调用)

完整代码:

Editor/index.vue, 

<template>
  <div ref="editorRef"></div>
</template>

<script>
import E from 'wangeditor'
export default {
  methods: {
    initEditor(){
      // 编辑器初始化操作
      const editor = new E(this.$refs.editorRef)

      // 配置 onchange 回调函数
      // 目的是为了能够知道什么时候内容发生了变化
      editor.config.onchange =  (newHtml) => {
        // newHtml 拿到的就是编辑器中最新的内容
        // 我们需要把这个内容抛出去交给父组件
        this.$emit('input', newHtml);
        console.log("change 之后最新的 html", newHtml);
      };

      editor.create()
      // 重新设置编辑器内容
      editor.txt.html(this.value)

      /**
       * bug 修复:
       * 当传入的数据发生变化的时候,props中的value已经拿到了最新的值
       * 但是并没有及时的同步到编辑器中
       * 实现思路:
       * 每一次content发生变化,props中都能拿到新值
       * 我们可以利用 watch 进行监控 props 中的 value ,一旦 value
       * 发生变化,就重新执行一下设置编辑器内容的方法即可
       */
      // 命令式监听,灵活,可以在任何位置使用
      // 参数一:要监听的属性
      // 参数二:监听的属性发生变化之后的回调函数

      /**
       * $watch 和 watch 配置项内部实现的核心原理是一致的,但是 $watch 更加
       * 灵活,它并不是组件一进行初始化就立刻监听的而是在任意时刻也就是你想要
       * 监听的时候才监听的
       */
      this.$watch('value', () => {
        console.log('value发生了变化');
        editor.txt.html(this.value)
      })
    }
  },
  mounted(){
    // 在这里初始化保证dom是存在的
    this.initEditor()
  },
  props: {
    value: {
      type: String
    }
  }
}
</script>

<style>

</style>

App.vue,

<template>
  <div id="app">
    <!-- 
      1. 编辑器组件可以接收我传入的数据,并且可以显示到编辑器内部,实现数据的回显
      2. 一旦编辑器中进行内容编辑之后,绑定的 content 字段也可以得到修改,变成编辑器中最新的值

      体现出来的就是双向绑定的特性
     -->
     <!-- 
       实现步骤:
       1. 实现回显
        v-model 一旦写到一个组件上,默认情况下相当于传入了名为 value 的自定义属性,以及名为 input 的自定义事件
      
        2. 编辑器一旦编辑内容就把绑定的 content 属性修改掉
          难点1:如何知道内容已经编辑了 第三方插件提供了方法
          难点2:如何拿到最新的编辑器内容,然后传出来交给 content(子传父)
      -->
    <Editor v-model="content"></Editor>
  </div>
</template>

<script>
import Editor from './components/Editor'
export default {
  name: 'App',
  data(){
    return {
      content: '我是编辑器'
    }
  },
  components: {
    Editor
  },
  methods: {
  }
}
</script>

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

vue--组件开发 的相关文章

随机推荐

  • react井字棋---最全井字棋小游戏教程

    上一期我们利用create react app搭建了好了一个react项目 这期我们通过跟随React官方教程 编写一个 井字棋 小游戏 来熟悉react的基本用法 首先来看下 井字棋 的最终实现效果 从演示中我们可以看到 这个游戏大致有以
  • JavaScript Math

    JavaScript Math 算数 对象 Math 算数 对象的作用是 执行常见的算数任务 在线实例 round 如何使用 round random 如何使用 random 来返回 0 到 1 之间的随机数 max 如何使用 max 来返
  • 14.进程间通信

    一 进程间通信概述 1 目的 1 数据传输 一个进程需要将它的数据发送给另一个进程 2 资源共享 多个进程之间共享同样的资源 3 通知事件 一个进程需要向另一个或一组进程发送消息 通知它们发生了某种事件 4 进程控制 有些进程希望完全控制另
  • 再谈Linux epoll惊群问题的原因和解决方案

    差别是什么 差别只是西装 缘起 近期排查了一个问题 epoll惊群的问题 起初我并不认为这是惊群导致 因为从现象上看 只是体现了CPU不均衡 一共fork了20个Server进程 在请求负载中等的时候 有三四个Server进程呈现出比较高的
  • Git进行pull时,出现please enter the commit message for your changes...

    在服务端更新代码时 git pull时总是出现需要编辑一个commit message git status 查看了下 原来是服务端有部分代码需要commit后尚未push导致 这种问题 解决办法如下 如果你本地仓库不需要push 这里编辑
  • 自定义指令、具名卡槽的使用与演示

    目录 一 v model简化代码 二 sync修饰符 三 ref 与 refs 四 自定义指令 五 插槽 默认插槽 六 具名卡槽 一 v model简化代码 1 目标 父组件通过v model简化代码 实现子组件和父组件数据双向绑定 2 如
  • np.array()函数

    函数调用方法 numpy array object dtype None 各个参数意义 object 创建的数组的对象 可以为单个值 列表 元胞等 dtype 创建数组中的数据类型 返回值 给定对象的数组 普通用法 import numpy
  • 【C语言】用迭代法求平方根。

    include
  • jar包启动、停止、重启脚本

    启动命令 sh start sh start 停止命令 sh start sh stop 重启命令 sh start sh restart 注意 1 把test jar改成自己的jar包名 2 把文件命名为start sh 在linux环境
  • JavaScript(7)本地存储,函数深入理解

    1 本地存储 1 1本地存储特性 数据存储在用户浏览器中 设置和读取数据方便 而且页面刷新不丢失数据 容量较大 sessionStorage约5M localStorage约20M 只能存储字符串 可以将对象JSON stringify 编
  • css - 选择器

    css 选择器 css选择器用于选择html元素 为其设置css样式 选择器不会选择纯文本 只选择html元素 ID选择器 html标签的唯一编号由id属性指定 通过使用id的形式可以选择指定的元素对象 慎用id选择器 因为css不会检测i
  • 我所理解的DRM显示框架

    什么是DRM DRM全称是DirectRenderingManager 是linux主流的一种显示框架 支持多图层合成 为用户图层提供统一的API libdrm 来访问GPU 实现统一管理 它是为了解决多个程序对video card访问协同
  • 数据结构 数学知识复习

    文章目录 指数 对数 级数 模运算 证明方法 归纳法证明 反例法证明 指数 X A X B
  • 无监督聚类评价指标

    无监督聚类评价指标 文章目录 无监督聚类评价指标 SEE SC和CH 寻找k 评价指标 轮廓系数法 SC 评价指标 CH系数法 无监督聚类算法结果好坏的评价指标 Compactness 紧密性 CP Separation 间隔性 SP Da
  • 三子棋小游戏(纯C)

    N子棋 以三子棋为例 一 代码的初步框架 二 棋盘的初始化与棋盘的打印 玩家下棋与电脑下棋 输赢的判断 完整代码展现 一 代码的初步框架 我们接下来都是对game 的封装 逐步的完善 二 棋盘的初始化与棋盘的打印 上图为棋盘的打印 我们注释
  • VMware Workstation 无法连接到虚拟机。请确保您有权运行该程序、访问该程序使用的所有目录以及访问所有临时文件目录。 未能将管道连接到虚拟机: 系统找不到指定的文件。...

    安装好之后不能运行虚拟机 网上的办法说以管理员方式运行 每次点太麻烦 所以打开了设置 永久配置 一键开启 哈哈具体如下 右键vmware的属性 更改所有用户设置 这里打上勾 确定保存 ok 转载于 https www cnblogs com
  • PostgreSQL数据库用户规划

    在SQL标准里 同一个模式下的对象是不能被不同的用户拥有的 而且有些数据库系统不允许创建和它们的所有者不同名的模式 如Oracle数据库 实际上 在那些只实现了标准中规定的基本模式的数据库系统里 模式和用户的概念几乎是一样的 比如Oracl
  • webpack5进阶-学习笔记

    学习连接 https www bilibili com video BV1964y1k7Hm p 19 spm id from pageDriver 1 区分环境打包 1 1 通过环境变量区分 执行webpack命令时可携带环境变量 并在w
  • 计算机视觉项目实战-背景建模与光流估计(目标识别与追踪)

    欢迎来到本博客 本次博客内容将继续讲解关于OpenCV的相关知识 作者简介 目前计算机研究生在读 主要研究方向是人工智能和群智能算法方向 目前熟悉python网页爬虫 机器学习 计算机视觉 OpenCV 群智能算法 深度学习等内容 以后可能
  • vue--组件开发

    目录 一 button 组件开发 1 1 整体目标 1 2 确定组件API 1 3 编写测试基础Button 1 4 完成type配置 1 5 完成size配置 1 6 完成事件绑定 1 7 总结 二 Editor编辑器组件开发 2 1 确