文章目录
1.整体目标
- 了解组件开发的整体流程
- 掌握组件事件和标签事件的区别
- 掌握在组建上使用 v-model 的方式
2.Button 组件开发
2.1 确定组件API
属性
属性名 | 说明 | 类型 | 默认值 |
---|---|---|---|
type | 设置按钮类型,可选值为 primary danger 或者不设 | String | default |
size | 设置按钮大小,可选值为small large 或者不设置 | String | default |
事件
事件名称 | 说明 | 回调参数 |
---|---|---|
click | 按钮点击事件 | (event)=>void |
2.2 编写测试基础Button
组件有很多的功能,但是这些功能都是一个最原始的组件逐渐拓展而来的,所以我们先完成一个最基础的button组件,然后逐渐增加功能
// components/button/index.vue
<template>
<button class="btn-default">
<slot></slot>
</button>
</template>
<script>
export default {
name: "HButton",
};
</script>
<!-- 开发组件的时候 scoped 是必须要加的 不能影响其他的样式 !-->
<style scoped lang="less">
.btn-default {
line-height: 1.499;
position: relative;
display: inline-block;
font-weight: 400;
white-space: nowrap;
text-align: center;
background-image: none;
box-shadow: 0.5px 2px 0 rgba(0, 0, 0, 0.05);
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
transition: all 0.3s cubic-bezier(0.6, 0.04, 0.3, 1);
touch-action: manipulation;
padding: 5px 10px;
font-size: 14px;
border-radius: 4px;
color: rgba(0 0 0 / 0.65);
//outline: none;
border: 1px solid #ddd;
background-color: #fff;
&:focus {
color: rgab(0, 0, 0, 1);
box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.2);
}
&:hover {
color: rgba(133, 13, 3, 0.31);
}
}
</style>
2.3 完成tpye配置
1.准备对应的class类
// primary
.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
.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);
}
2.编写props
setup(props) {
console.log(props);
let activeClass = computed(() => {
return "btn-" + props.type;
});
return {
activeClass,
};
},
2.4 完成size的配置
1.准备对应的class 类
//size :small
.btn-size-small {
padding: 4px 8px;
font-size: 12px;
}
//size :large
.btn-size-large {
padding: 6px 12px;
font-size: 16px;
}
2.编写props
props: {
type: {
type: String,
default: "default",
},
size: {
type: String,
default: "default",
// 参数校验
validator: function (value) {
// value 代表当前传来的值
//console.log(value);
// 对传入的参数做校验 满足条件返回true 不满足就返回false
// 如果传入的参数不在可选参数的列表中 给出用户提示 告诉哪个参数传错了
// 如果传入的参数 在可选的参数列表中 直接通过 不会报错
const sizeList = ["small", "large", "default"];
return sizeList.includes(value);
},
},
setup(props) {
console.log(props);
let typeClass = computed(() => {
return "btn-type-" + props.type;
});
let sizeClass = computed(() => {
return "btn-size-" + props.size;
});
return {
typeClass,
sizeClass,
};
},
2.5 完成事件绑定
// 父组件
</template>
<Button @click="clickHandler">click me</Button>
</template>
<script>
setup() {
const clickHandler = (e) => {
console.log("click me", e.target);
};
return {
clickHandler,
};
},
</script>
注意:
- vue2 中 事件如果写到原生支持的标签身上会被直接识别为原生事件,
<button @click='handler'></button>
- 事件如果写到自定义组件身上 默认状态会被识别为自定义事件
<Button @click='handler'></Button>
- 增加
.native
修饰符 可以让vue 系统帮助我们把事件绑定成为浏览器支持的原生事件 - 就当成自定义事件识别 然后按照父子组件通信的手段 通过子组件内事件触发 然后调用$emit 方法触发即可
- 增加
- 在vue3 中不会有这种问题 内置的事件就是内置的事件 除了内置的事件都会被当作自定义事件进行解析
2.6 总结
- 编写组件时 应该API先行 先确定组件该如何给用户使用 再根据API编写逻辑
- props 的名称应该具备语义化,类型应该规范,并且可以添加自定义校验
- 组件上绑定的类似于原生的事件 默认是不会被识别的,需要额外处理
- 组件有一些设计需要整体把控,比如props 与对应类名的匹配
3. Editor编辑器组件开发
3.1 确定基础API
指令名 | 说明 | 类型 | 默认值 |
---|---|---|---|
v-model | 提供编辑器数据的双向绑定 | String | 无 |
3.2 编写测试基础Editor
Editor/index.js
<template>
<div style="border: 1px solid #ccc">
<Toolbar
style="border-bottom: 1px solid #ccc"
:editor="editorRef"
:defaultConfig="toolbarConfig"
:mode="mode"
/>
<Editor
style="height: 500px; overflow-y: hidden"
v-model="valueHtml"
:defaultConfig="editorConfig"
:mode="mode"
@onCreated="handleCreated"
/>
</div>
</template>
<script>
import "@wangeditor/editor/dist/css/style.css"; // 引入 css
import { onBeforeUnmount, ref, shallowRef, onMounted } from "vue";
import { Editor, Toolbar } from "@wangeditor/editor-for-vue";
export default {
components: { Editor, Toolbar },
name: "costume-editor",
setup() {
// 编辑器实例,必须用 shallowRef
const editorRef = shallowRef();
// 内容 HTML
const valueHtml = ref("<p>hello</p>");
// 模拟 ajax 异步获取内容
onMounted(() => {
console.log(valueHtml);
console.log(editorRef);
setTimeout(() => {
valueHtml.value = "<p>模拟 Ajax 异步设置内容</p>";
}, 1500);
});
const toolbarConfig = {};
const editorConfig = { placeholder: "请输入内容..." };
// 组件销毁时,也及时销毁编辑器
onBeforeUnmount(() => {
const editor = editorRef.value;
if (editor == null) return;
editor.destroy();
});
const handleCreated = (editor) => {
editorRef.value = editor; // 记录 editor 实例,重要!
};
return {
editorRef,
valueHtml,
mode: "simple", // 或 'simple'
toolbarConfig,
editorConfig,
handleCreated,
};
},
};
</script>
<style scoped></style>
3.3 完成v-model 双向绑定
//vue2 中 v-model 语法唐
<!--
1. 编辑器组件可以接收我传入的数据 并且可以显示到编辑器内部 实现数据的回显
2. 一旦编辑器中进行内容编辑之后 绑定的content 属性 也可以得到修改 变成编辑器中最新的值
体现出来的就是双向数据绑定的特性
实现步骤:
1.实现回显
v-model 一旦写到一个组件身上 默认情况下 相当于传入了名为value 的自定属性 以及名为input的自定义事件
v-model 其实是一个语法糖 可以写一次完成两件事情
2.编辑器一旦编辑内容就把绑定的content属性修改掉
难点1 如果知道内容已经遍及了 三方插件提供了方法
难点2 如果拿到最新的编辑内容然后传出来给content(子传父)
-->
// vue 2
<Editor v-model="content"></Editor>
// 相当于
<Editor :value="content" @input="content = $event" />
// Editor 组件
<template>
<div>
<input type="content" :value="value" @input="$emit('input',$event.target.value)">
</div>
</template>
<script>
export default {
props: {
content: String, // 默认接收一个名为 content 的 prop
}
}
</script>
// vue3 中 v-model
<template>
<div>
<input
type="text"
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)" // 事件名改为 update:modelValue
/>
</div>
</template>
<script>
export default {
props: {
modelValue: String, // 默认 prop 从 value 改为 modelValue
},
};
</script>