Vue: 造轮子--03:Button组件

536 阅读2分钟

button

1. 需求

  • 可以有不同的等级(level)
  • 可以是链接,可以是文字
  • 可以 click、focus、鼠标悬浮
  • 可以改变 size:大中小
  • 可以禁用(disabled)
  • 可以加载中(loading)

2. 让button支持事件 @click @focus @mouseover

  1. vue做了自动处理事件, 默认传到组件的根元素上
  • buttonDemo.vue。 在上绑定 。会默认传到button组件的根元素上。
  • 触发事件会执行,但是button.vue没有做任何处理。
<template>
    <div>Button示例</div>
    <h1> 示例1</h1>
    <div>
        <Button @click="onClick" @focus="onClick" @mouseover="onClick">你好</Button>
    </div>

</template>

<script lang="ts">
    import Button from '../lib/button.vue'
    export default {
        name: "ButtonDemo",
        components:{Button},
        setup(){
            const onClick = ()=>{
                console.log('hi')
            }
            return {onClick}
        }
    }
</script>
  • 让button.vue的div不要继承父组件(buttonDemo.vue)的onclick属性。
  • 让button.vue设置inheritAttrs:false。表示不继承事件
// button.vue
<template>
    <div style="border:1px solid red" :size="size">
    <button>
        <slot></slot>
    </button>
    </div>
</template>

<script lang="ts">
    export default {
        name: "button",
        inheritAttrs:false ,//1. 不让div继承:继承属性,div就不在拥有外面importbutton绑定的继承事件了
     
    }
</script>
  1. 只想让 继承这些属性,其他的标签不继承。
  • 使用$attrs
// button.vue
<template>
    <div style="border:1px solid red" :size="size">
    <button v-bind="$attrs">
        <slot></slot>
    </button>
    </div>
</template>
<script lang="ts">
    export default {
        name: "button",
        inheritAttrs:false ,//1. 不让div继承:继承属性,div就不在拥有外面importbutton绑定的继承事件了
     
    }
</script>
  • 添加$attrs后, 所有的属性/方法 会在button上生效
  1. 现在想让div和button继承不同的属性,怎么办?
  • 使用setup里的context。谁用哪个属性,就绑定哪个。
  • g5wrlR.png
  • 改进: 使用...rest
<template>
    <div style="border:1px solid red" :size="size">
    <button v-bind="rest">
        <slot></slot>
    </button>
    </div>
</template>

<script lang="ts">
    export default {
        name: "button",
        inheritAttrs:false ,//1. 不让div继承:继承属性,div就不在拥有外面importbutton绑定的继承事件了
        setup(props, context){
            const {size,...rest} =context.attrs //rest是除了size其他的
            return {size,rest}

        }
    }
</script>

小结

  • Vue 3 属性绑定
  • 默认所有属性都绑定到根元素
  • 使用 inheritAttrs: false 可以取消默认绑定
  • 使用 $attrs 或者 context.attrs 获取所有属性
  • 使用 v-bind="$attrs" 批量绑定属性
  • 使用 const {size, level, ...xxx} = context.attrs 将属性分开

3. 让button支持theme属性

  1. 父组件(buttondemo)给子组件(button.vue)传递theme="button"
  • ButtonDemo.vue
<div>
        <Button >你好</Button>
        <Button theme="button">你好</Button>
        <Button theme="link">你好</Button>
        <Button theme="text">你好</Button>
    </div>
  • button.vue
<template>
    <button calss="gulu-button" :class="`theme-${theme}`">
        <slot></slot>
    </button>

</template>

<script lang="ts">
    export default {
        name: "button",
        props:{
            theme:{
                type:String,
                default:'button'
            }
        }
    }
</script>
  1. 根据class添加css样式

UI库的css注意事项

  • 不能使用 scoped
    1. 因为 data-v-xxx 中的 xxx 每次运行可能不同
    2. 必须输出稳定不变的 class 选择器,方便使用者覆盖
  • 必须加前缀
    1. .button 不行,很容易被使用者覆盖
    2. .wanwan-button 可以,不太容易被覆盖
    3. .theme-link 不行,很容易被使用者覆盖
    4. .wanwan-theme-link 可以,不太容易被覆盖
  1. CSS 最小影响原则 你的 CSS 绝对不能影响库使用者
  • 把css里加上自己的class(wanwan)
//以wanwan-开头,或者含有wanwan-
[class^="wanwan-"],[class*="wanwan-"] {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  font-size: 16px;
  // 为什么这样写 font-family
  // 答案见 https://github.com/zenozeng/fonts.css/
  font-family: -apple-system, "Noto Sans", "Helvetica Neue", Helvetica,
  "Nimbus Sans L", Arial, "Liberation Sans", "PingFang SC", "Hiragino Sans GB",
  "Noto Sans CJK SC", "Source Han Sans SC", "Source Han Sans CN", "Microsoft YaHei", "Wenquanyi Micro Hei", "WenQuanYi Zen Hei", "ST Heiti", SimHei, "WenQuanYi Zen Hei Sharp", sans-serif;

}

4. 让 button 支持 size 属性

  1. 父组件传递size,子组件props接收,在setup里使用computed计算属性计算class,设置button的class。
  • buttonDemo.vue
<div>
        <Button >normal</Button>
        <Button size="big">dadada</Button>
        <Button size="small">small</Button>
    </div>
  • button.vue
<template>
    <button class="wanwan-button" :class="classes">
        <slot></slot>
    </button>

</template>

<script lang="ts">
    import {computed} from 'vue'
    export default {
        name: "button",
        props:{
            theme:{
                type:String,
                default:'button'
            },
            size:{
                type:String,
                default:"normal"
            }
        },
        setup(props){
            const {theme,size} = props
            const classes = computed(()=>{
                return {
                    [`wanwan-theme-${theme}`]: theme,
                    [`wanwan-size-${size}`]: size
                }
            })
            return {classes}
        }
    }
</script>

让 button 支持 level 属性

level 的值为 main / normal / danger,原理同上

让 button 支持 disabled

  • buttonDemo.vue
<div>
        <Button disabled>禁用按钮</Button>
        <Button theme="link" disabled>禁用链接按钮</Button>
        <Button theme="text" disabled>禁用按钮</Button>
        
    </div>

给子组件增加disabled属性

  • button
<template>
    <button class="wanwan-button" :class="classes" :disabled="disabled">
        <slot></slot>
    </button>

</template>

<script lang="ts">
    import {computed} from 'vue'
    export default {
        name: "button",
        props:{
            theme:{
                type:String,
                default:'button'
            },
            size:{
                type:String,
                default:"normal"
            },
            level:{
                type:String,
                default:"normal"
            },
            disabled:{
                type:Boolean,
                default:false
            }
        },
        setup(props){
            const {theme,size,level} = props
            const classes = computed(()=>{
                return {
                    [`wanwan-theme-${theme}`]: theme,
                    [`wanwan-size-${size}`]: size,
                    [`wanwan-level-${level}`]: level,
                }
            })
            return {classes}
        }
    }
</script>
  • 由于props声明了disabled,所以button就不会自动继承disabled属性,所以需要在button里绑定一下:disabled="disabled"
  • 根据有无disabled,在button中设置css样式

让 button 支持 loading

  • buttonDemo.vue
 <Button loading>loading</Button>
  • button.vue
<template>
    <button class="wanwan-button"
            :class="classes"
            :disabled="disabled">
        <span  v-if="loading"class="wanwan-loadingIndicator"></span>
        <slot></slot>
    </button>

</template>

<script lang="ts">
    import {computed} from 'vue'
    export default {
        name: "button",
        props:{
            theme:{
                type:String,
                default:'button'
            },
            size:{
                type:String,
                default:"normal"
            },
            level:{
                type:String,
                default:"normal"
            },
            disabled:{
                type:Boolean,
                default:false
            },
            loading:{
                type:Boolean,
                default:false
            }
        },
        setup(props){
            const {theme,size,level} = props
            const classes = computed(()=>{
                return {
                    [`wanwan-theme-${theme}`]: theme,
                    [`wanwan-size-${size}`]: size,
                    [`wanwan-level-${level}`]: level,
                }
            })
            return {classes}
        }
    }
</script>
  • 增加一个span,作为loading
  1. loading的动画:
    @keyframes wanwan-spin {
        0%{transform: rotate(0deg)}
        100%{transform: rotate(360deg)}

    }
    
    animation: wanwan-spin 1s infinite linear;