Appearance
项目常见写法
Hidden Title
说明
- 该页面用于记录
sybz-components仓库里常见且推荐复用的写法。 - 新增组件、补文档、封装工具方法时,建议优先参考这里和现有同类目录。
1. 新增一个组件时,目录通常这样放
txt
packages/components/foo/
├─ index.ts
└─ src/index.vue
docs/components/foo/
├─ home.md
├─ base.vue
└─ usually.vuepackages/components/foo/
├─ index.ts
└─ src/index.vue
docs/components/foo/
├─ home.md
├─ base.vue
└─ usually.vue- 通用基础组件通常放在
packages/components/*。 - 偏业务的业务组件通常放在
packages/components/company/*。 - 文档示例通常放在
docs/components/*,一个组件一个目录。
2. 组件源码一般放在 src/index.vue
vue
<script setup lang="ts">
import { computed, ref, useAttrs, useSlots, watch } from 'vue'
defineOptions({
name: 'SFoo',
})
const attrs = useAttrs()
const slots = useSlots()
const emits = defineEmits(['change', 'update:modelValue'])
interface FooProps {
modelValue?: string | number | any[]
width?: string | number
}
const props = withDefaults(defineProps<FooProps>(), {
modelValue: undefined,
width: '',
})
</script><script setup lang="ts">
import { computed, ref, useAttrs, useSlots, watch } from 'vue'
defineOptions({
name: 'SFoo',
})
const attrs = useAttrs()
const slots = useSlots()
const emits = defineEmits(['change', 'update:modelValue'])
interface FooProps {
modelValue?: string | number | any[]
width?: string | number
}
const props = withDefaults(defineProps<FooProps>(), {
modelValue: undefined,
width: '',
})
</script>props主要定义当前封装自己新增的能力。attrs通常继续透传给底层element-plus组件。- 如果组件支持
v-model,一般会同时保留update:modelValue和业务事件。
3. 常见的 attrs 透传写法
vue
<el-select
v-bind="{
clearable: true,
filterable: true,
...Object.entries($attrs).reduce((obj, [key, value]) => {
if (key !== 'class' && key !== 'style') {
obj[key] = value
}
return obj
}, {}),
}"
/><el-select
v-bind="{
clearable: true,
filterable: true,
...Object.entries($attrs).reduce((obj, [key, value]) => {
if (key !== 'class' && key !== 'style') {
obj[key] = value
}
return obj
}, {}),
}"
/>- 常见做法是先在本地设置默认值,再把剩余属性透传下去。
class和style往往会单独处理,避免覆盖组件自身样式。
4. 包装组件时,常见的插槽转发写法
vue
<template v-for="(arg, name, index) in noDefaultSlots" v-slot:[name]>
<slot :name="name" v-bind="arg" :index="index" />
</template><template v-for="(arg, name, index) in noDefaultSlots" v-slot:[name]>
<slot :name="name" v-bind="arg" :index="index" />
</template>ts
const noDefaultSlots = computed(() => {
const copySlots = proxy.clone(slots)
delete copySlots.default
delete copySlots.label
return copySlots
})const noDefaultSlots = computed(() => {
const copySlots = proxy.clone(slots)
delete copySlots.default
delete copySlots.label
return copySlots
})- 这种写法常见于封装
el-select、el-table一类本身插槽很多的组件。 - 目的通常是让外部还能继续使用底层组件的大部分插槽能力。
5. 组件对外导出时,通常走 withInstall
ts
import Foo from './src/index.vue'
import { withInstall } from '@/components/utils/withInstall.ts'
const SFoo = withInstall(Foo)
export default SFooimport Foo from './src/index.vue'
import { withInstall } from '@/components/utils/withInstall.ts'
const SFoo = withInstall(Foo)
export default SFoo- 这样组件既能被全局注册,也能支持按需引入。
- 基础组件目录里通常都会有一个
index.ts专门做这件事。
6. 配置组件全局默认值
组件全局默认值直接写在 app.use 的第二个参数里,风格和 Element Plus 保持一致。
ts
// 组件内部统一走 hook
const mergedProps = useGlobalComponentConfig('select', props)// 组件内部统一走 hook
const mergedProps = useGlobalComponentConfig('select', props)ts
// main.ts 中全局传入
const app = createApp(App)
app.use(SybzComponents, {
theme: 'chenghua',
size: 'small',
select: {
showPrefix: true,
},
table: {
showIndex: false,
showPage: false,
pageSizes: [10, 30, 50, 100],
},
})// main.ts 中全局传入
const app = createApp(App)
app.use(SybzComponents, {
theme: 'chenghua',
size: 'small',
select: {
showPrefix: true,
},
table: {
showIndex: false,
showPage: false,
pageSizes: [10, 30, 50, 100],
},
})- 这种写法适合做“组件默认行为”的统一配置,比如
showPrefix、showQuick一类开关。 theme、size这类和组件平级的字段会作为公共默认值,分发给声明了同名 prop 且接入了useGlobalComponentConfig的组件。select、table这类组件名字段只对对应组件生效,可用于覆盖公共默认值或补充组件自己的默认行为。- 如果一个组件也要支持这种能力,通常做法是:组件内部直接调用
useGlobalComponentConfig('组件key', props)。 - 如果只希望某个页面或某个模块下生效,也可以在父组件的
setup里继续使用provide('GLOBAL_COMPONENT_CONFIG', { select: { ... } })做局部覆盖。 - 同名字段遵循“全局配置作为默认值,本地显式传入的 prop 优先”的规则。
7. 文档页通常拆成 home.md + 多个 demo.vue
md
### 基础用法
:::demo
foo/base
:::### 基础用法
:::demo
foo/base
:::home.md负责目录、说明、属性表和事件表。- 具体交互示例通常拆成同级
.vue文件,避免一个文档页过长。 - 文档页顶部一般会保留
## Hidden Title {.md-hidden}这一行,和当前仓库风格保持一致。
8. 工具方法通常按“实现文件 + 统一导出 + 对应文档”组织
txt
packages/utils/src/base.ts
packages/utils/src/index.ts
docs/components/utils/isEmpty/home.mdpackages/utils/src/base.ts
packages/utils/src/index.ts
docs/components/utils/isEmpty/home.md- 单个函数实现通常放在
packages/utils/src/*下。 - 对外统一从
packages/utils/src/index.ts导出。 - 文档通常放在
docs/components/utils/*/home.md,路径尽量和函数名保持一致。
9. 新增文档或组件后,通常别忘了检查这些地方
docs/.vitepress/config.ts里是否补了侧边栏入口。- 组件目录下是否有
index.ts,这样才能被packages/index.ts自动收集。 docs/components/xxx/home.md是否已经引用了对应示例。- 提交前建议至少跑一次
pnpm build;如果本次改动涉及文档结构,也建议补跑pnpm docsbuild。
说明
- 本页偏“仓库协作约定”和“高频套路”,不是严格规范。
- 如果后续团队形成了更稳定的约定,可以继续在这个页面上补充和调整。