【组件封装】-【vue组件设计经验技巧】

思想

一、样式的思考:组件里写哪些样式?应该注意什么?
二、template的思考:slot还是组件里写好?
三、行为的思考:父组件定义还是子组件定义?如果在父组件中定义,那么组件的可扩展性就好了,但是会写很多代码,如果在子组件中定义,那么使用组件的人很方便但是不好扩展
四、props的思考:到底哪些东西作为props哪些东西作为组件本身的data

样式编写建议

一、容器的基本样式,内部内容的基本样式
二、尽量使用低权值,即使用标签名来定义样式(标签名的权值是最低的,为1),这样做的好处是,如果父组件想修改样式,直接给标签加class即可
三、定义一些内置类
自定义searchBar组件:

<template>
    <div class="search-wrap">
        <slot></slot>
        <button class="search-button">搜索</button>
        <button class="reset-button">重置</button>
    </div>
</template>
<script>
    export default{
        name:"search",
        props:{
            msg:String
        },
        mounted() {
            
        },
        methods: {
            search(){

            }
        },
    }
</script>
<style scoped>
.search-wrap{
    width: 600px;
    display: flex;
    box-shadow: 2px 3px 4px #9f9b9b;
    padding: 20px;
}
.search-item{
    margin-right: 15px;
}

/* 父组件传进来的内容中肯定有input标签,要给input标签低权值的样式,所以使用input标签名来添加样式,此时他的样式权值是1 */
input{

}
/* 如果用下面这种方法给input标签添加样式,那么当父组件想修改样式时,就需要用important,因为类名选择器的样式权值为10,所以下面这种写法的权值为11
.search-wrap input{
}
*/

/* 预置一些类名 */
mini-wrap{
	wisth: 200px;
}

button{
    border: none;
    color: white;
    padding: 4px 15px;
    cursor: pointer;
}

.search-wrap .search-button{
    background-color: rgb(23,23,185);
}
</style>

父组件使用:

<searchBar>
	<div class="search-item">
		<label></label>
		<input class="search-input"/>
	</div>
	<!-- 如果想使用小一点的搜索框 -->
	<div class="mini-wrap">
		<label>姓名</label>
		<input class="search-input"/>
	</div>
</searchBar>

template建议

一、基本原则:内容固定就写死不确定的部分使用slot传入,此外还要考虑数据传递的问题,如果把数据写死,之后传出去会不会比较麻烦
二、如果平衡一点,可以这样:父组件传了slot,子组件就用slot,没有传就用默认的。如果全部写死,组件的便捷性会很高,如果用slot,组件的可扩展性会很高

<template>
    <div class="search-wrap">
        <slot></slot>
        <!-- 上面的slot是默认插槽,下面这行代码判断所有插槽中是否有默认插槽,即父组件是否传入了默认插槽,如果不存在(没传),则使用组件的内容 -->
        <div v-if="!('default) in $slots)" class="search-item">
        	<label>姓名</label>
        	<!-- 如果要把用户输入的内容(input输入框的值)传给父组件,需要使用$emit -->
			<input class="search-input" v-model="name" @change="change(name)"/>
        </div>
        <button class="search-button">搜索</button>
        <button class="reset-button">重置</button>
    </div>
</template>
<script>
    export default{
        name:"search",
        props:{
            msg:String
        },
        mounted() {
            
        },
        methods: {
            search(){

            },
            change(val){
            	this.$emit('nameChange',val)
            }
        },
    }
</script>
<style scoped>
.search-wrap{
    width: 600px;
    display: flex;
    box-shadow: 2px 3px 4px #9f9b9b;
    padding: 20px;
}
.search-item{
    margin-right: 15px;
}

button{
    border: none;
    color: white;
    padding: 4px 15px;
    cursor: pointer;
}

.search-wrap .search-button{
    background-color: rgb(23,23,185);
}
</style>

行为的建议

一、把某一个行为分成基本部分和业务部分,建议每一个行为都留给父组件监听。比如弹窗组件的弹出和关闭两个行为属于基本部分,可以写死在组件内,点击确定、取消这两个行为属于业务部分,需父组件传入,但是这个四个行为建议都留给父组件监听。但是搜索组件所有的行为基本都是业务部分的,因为搜索功能在不同场景下调用的接口不一样,组件可以监听搜索按钮的点击,然后发送事件告知父组件,让父组件去调用接口
二、为了更好的扩展,可以拆分某一个行为的前后中几个周期,比如,以搜索这一行为为例,在搜索前、搜索中、搜索后这三个阶段都给父组件发送事件

props的建议

一、组件前端行为需要的数据内部定义,业务相关数据父组件传入。比如说控制弹窗组件显示隐藏状态的数据放在组件内部data,弹窗内部具体展示的内容由父组件传入
二、props也是一个组件扩展的重要接口,比如有的搜索组件有重置按钮,有的没有,那么组件可以在props中接收一个参数,用以控制是否展示重置按钮

总结

组件封装其实就是扩展性和便捷性的权衡,组件写死的部分多了,便捷性提升了,但是扩展性下降了,组件内很多内容依靠父组件传入,那么扩展性增强了,但是便捷性下降了

如何在项目中思考组件封装

一、思想:在实际项目中,我们大多封装复用的整体操作,区别于编写组件库组件,项目中往往已经有基础组件库了,我们不会再去封装基础功能组件(比如input、button、table等),所以项目中我们更多的会去观察项目中常见的整体业务操作,去封装成组件以便复用,即封装可复用的整体操作。在后台管理系统的项目中经常会遇到一些table+页码的页面,如下图所示,而elementui提供的table和页码是分开的,所以可以将二者封装成一个整体,便于使用,本节就以页码列表组件为例。
请添加图片描述
二、项目中封装业务组件的两大原则:

  1. 我们写的组件不要去结合具体业务逻辑:
    (1)组件只作为数据的容器,数据统一父组件传入
    (2)只编写ui逻辑,具体的数据操作这样的业务逻辑,触发父组件的监听交给父组件处理
    请添加图片描述
  2. 尽量提供简便:在不结合具体业务逻辑的前提下,让父组件使用组件尽可能方便,我们的原则是观察大部分页面的设计,能方便的满足大多数页面的需求,少数页面有差距,也有扩展方案

设计组件时,针对除基本功能以外的扩展功能都要考虑不用时能关上,要用时样式如何调整?该功能如何进行?(比如说搜索功能的模糊搜索功能,是父组件给组件一个url然后组件自动查询还是将模糊搜索的功能开发出去,让父组件自己定义请求哪个地址?)该功能所涉及的数据如何归属?(是归属组件还是父组件?)

重复小组件的处理经验

一、什么是重复小组件:组件不复杂但是在很多页面上都会出现,如弹窗
二、需求:
请添加图片描述
三、实现方案:

  1. 思路一:
    请添加图片描述
  2. 思路二:希望能像elementui的messagebox方法一样,点击即可弹窗,假如我们自己封装的方法叫showSign:
showSign('xxx',{
	confirm:()=>{
		// 确认行为
	},
	cancel:()=>{
		// 取消行为
	}
})

四、具体实现思路:

  1. 基本思路:
    请添加图片描述
  2. 代码:
    (1)使用createVNode实现(简易版的,没写完)
import {createVNode,render} from 'vue'
export function signPop(content){
	let pop = createVNode('div',{
		class:"divcover"
	},[
		createVNode('div',{
			class:"popcontent"
		},content)
	])
	render(pop,document.body)// 将虚拟dom挂载到document.body上
}

(2)使用jsx实现:

import {render} from 'vue'
export function signPop(content){
	let pop = <div class='divcover'>
		<div class="popcontent">
			<div>{content}</div>
			<div>
				<button onclick={() =>{
					document.body.removeChild(pop)
				}}>不同意</button>
				<button>签署</button>
			</div>
		</div>
	</div>
	render(pop,document.body)// 将虚拟dom挂载到document.body上
}

报错:
请添加图片描述
第一个参数必须是真实dom节点,在vue中,真实dom放在虚拟dom的el属性中:document.body.removeChild(pop.el)即可获取真实el
继续报错:第二次及之后点击按钮弹窗都不出来了。因为虚拟dom只能挂载一次,通过原生的document.body.removeChild(pop.el)将真实dom移除,但是在vue看来,pop的虚拟dom已经挂载好了,没有隐藏,所以第二次及之后点击按钮让他再次挂载挂不上去了
解决:借鉴elementui的做法,根据let div = document.createElement('div')创建一个真实的div,然后将pop弹窗(虚拟dom)渲染到这个真实的div中:render(pop,div),然后通过原生的加入子节点的方式将div加载进去:document.body.appendChild(div),然后移除时,直接移除这个真实的div:document.body.removeChild(div)。完整代码:

import {render} from 'vue'
export function signPop(content,handler){
	let div = document.createElement('div')
	let pop = <div class='divcover'>
		<div class="popcontent">
			<div>{content}</div>
			<div>
				<button onclick={() =>{
					document.body.removeChild(div)
					handler && handler.cancel && handler.cancel()
				}}>不同意</button>
				<button onclick={() =>{
					document.body.removeChild(div)
					handler && handler.confirm && handler.confirm()
				}}>签署</button>
			</div>
		</div>
	</div>
	render(pop,div)
	document.body.appendChild(div)
}
  1. 外部使用:
    请添加图片描述

利用v-model提升组件的方便性

一、v-model主要用于什么情况下:如果我们需要把组件内部的值和副组件的一个数据相绑定,v-model会是一个很好的帮手
二、v-model的本质:

<input v-model="a" />

等价于:

<input :value="a" @input="(e)=>{a=e.target.value}" />

所以,v-model不是一个vue指令而是一个语法糖,一个让我们传递值,并监听修改的简便语法糖
三、v-model作用于组件上

  1. vue2中和原生等价:
<son v-model="a"></son>

等价于:

<son :value="a" @input="(e)=>{a=e.target.value}"></son>
  1. vue3中,在原生组件上还是不变(同前述),在自定义组件上,传递的是modelValue,监听的是update:modelValue:
<son v-model="a"></son>

等价于:

<son :modelValue="a" @update:modelValue="(e)=>{a=e.target.value}"></son>

三、举例一些可以使用v-model的组件:

  1. 弹窗(直接v-model绑定父组件的值,这样一个v-model就能轻松的控制弹窗的显隐)
  2. 业务里的一些功能操作(直接把操作结果绑定到组件的data,不用付组件传值和监听)

四、弹窗原始实现:弹窗组件自己肯定不知道自己啥时候显隐,所以控制弹窗显隐的变量应该放在父组件,如果不使用v-model,那么我们应该这样做:点击父组件的按钮展示弹窗,点击弹窗的取消关闭弹窗,此时弹窗会像父组件发送一个事件,父组件监听到这个事件后隐藏弹窗,父组件代码:

<template>
<button @onclick="()=>{popShow=true}">显示弹窗</button>
<pop v-if="popShow" @hidden="()=>{popShow=false}">
	<div>我是一个弹窗</div>
</pop>
</template>
<script setup>
import {ref} from "vue"
import pop from "./components/Pop.vue"
let popShow = ref(false)
</script>

Pop.vue代码:

<template>
<div>
	<div>
		<slot></slot>
		<div>
			<button>确定</button>
			<button @click="hiddenPop">取消</button>
		</div>
	</div>
</div>
</template>
<script setup>
let emit = defineEmits(["hidden"])
function hiddenPop(){
	emit("hidden")
}
</script>

五、弹窗v-model实现:又要写v-if又要写监听,很麻烦,如果使用v-model可以一步到位实现:
在父组件App.vue中使用:

<template>
<button @onclick="()=>{popShow=true}">显示弹窗</button>
<pop v-model="popShow">
	<div>我是一个弹窗</div>
</pop>
</template>
<script setup>
import {ref} from "vue"
import pop from "./components/Pop.vue"
let popShow = ref(false)
</script>

Pop.vue:

<template>
<div :style="{display:modelValue ? 'block' : 'none'}">
	<div>
		<slot></slot>
		<div>
			<button>确定</button>
			<button @click="hiddenPop">取消</button>
		</div>
	</div>
</div>
</template>
<script setup>
let emit = defineEmits(["hidden"])
// 相当于父组件给自组件传过来了一个modelValue,子组件先在props中接收一下
let {modelValue} = defineProps({
	modelValue:{
		type:Boolean,
		default:false
	}
})
function hiddenPop(){
	// 使用v-model,父组件会自动监听update:modelValue事件,子组件直接emit并把值传过去即可
	emit("update:modelValue",false)
}
</script>

六、扩展:

  1. 可以不传modelValue,改写为其他值:v-model默认是传modelValue,也可以传其他的值:v-model:popControl="popShow",传的就是popControl,上述pop.vue中将modelValue改为popControl即可
  2. 可以传多个v-model,但需要每个v-model的名字不一样:<pop v-model="popShow" v-model:a="123" v-model:b="b">...,相当于父组件给自组件传了modelValue、a、b三个值
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值