https:///vueComponent/ant-design-vue/pull/6173
参照5W1H分析法解释下为什么要提这个pr?
- what?何事?ant-design-vue(下文以antv简称)是ant-design(下文以antd简称)的vue实现,组件的风格和antd保持同步。
- why?何因?因为,antd自@4.2.0版本提供了分段控制器(segmented)组件,而antv还没有更新该组件。分段控制器(Segmented/Segmented Control)用于展示多个选项及其相关的信息,并允许用户选择其中单个选项,查看信息。
- when?何时?想法产生大概是十一月下旬,中间偷懒+感染生病耽搁一段时间,大致时间就是十一月底到十二月底,是真能拖沓。
- where?何地?“今大道既隐,天下为家”-《礼记·礼运》,当然是哪里都可以。
- who?何人?嘿嘿!重铸xx荣光,吾辈义不容辞。
- how?怎样做?因为两个组件库都有在使用,当时发现antv没有segmented这个组件(现在更多了,antd更新了5.x版本,新增了一些组件),好奇翻了下github的Issue和Pullrequest,确认了确实没有这个组件,但当时没有任何想法,十一月下旬一次吃饭的路上突然有了尝试给组件库提pr这个想法,和朋友聊了下觉得切实可行,后来想想之所以会产生这个样的想法,是因为加了闲D阿强大佬的QQ群,群里的人间和深红大佬堪称开源积极份子,经常在群里分享给组件库提pr和造轮子的事宜,人间大佬还是antd的开发团队成员,应该是他们的行为触动到我了吧,扯远了…,关于怎样做,最初的想法就是1.antd已经有完整实现了;2.segmented其实与tabs相差不大;有antd珠玉在前,也可以参考antv的tabs是如何实现的,迁移这个组件应该不难,但是现实是图样图森破,人还是太年轻,不能太盲目自信。
遇到的问题一:
antd看起来是一个组件库,但实际上他的部分组件是在react-component这个仓库中,如下图的antd的package.json,以rc-segmented为例,他还同时引用了rc-motion与rc-util,也就是说看起来是一个组件库,实际上是四个组件库,层层套娃,不过这也不算太大的问题,挨个挨个找,也能达到目的
遇到的问题二:
通过浏览的控制台我们可以很方便的拿到组件的样式,可以省去很大的功夫,毕竟从零实现一个组件想想都很头大,但是有一点我很困惑,就是:where(.css-xxxx)
,这个.css-xx是什么东东,where语法我知道,里面是他的条件,但这个条件究竟是什么呢,求助了下人间大佬,原来这个值是hashid,为了不和v4冲突所以加的哈希值,不过这边貌似用不到,所以这个就可以直接pass了,哇真的有人可以问真的太爽了,不然光靠自己想怎么也想不到,因为不会考虑到版本冲突,有了样式我们就可以直接实现一个简单的demo了,嘿嘿,有几分相似了
<div class="ant-segmented">
<div class="ant-segmented-group">
<div id="test"></div>
<label class="ant-segmented-item ant-segmented-item-selected">
<div class="ant-segmented-item-label">
<div>vue</div>
</div>
</label>
<label class="ant-segmented-item">
<div class="ant-segmented-item-label">
<div>react</div>
</div>
</label>
<label class="ant-segmented-item">
<div class="ant-segmented-item-label">
<div>angular</div>
</div>
</label>
</div>
</div>
<script>
function segmentedSwitch() {
let segmented = document.querySelectorAll(".ant-segmented-group > label");
let test = document.querySelector('#test')
for (let i = 0; i < segmented.length; i++) {
segmented[i].index = i;
const calcThumbStyle = {
left: segmented[i].offsetLeft,
width: segmented[i].clientWidth
}
segmented[i].onclick = function () {
for (let j = 0; j < segmented.length; j++) {
segmented[j].classList.remove('ant-segmented-item-selected')
//test.classList.remove('ant-segmented-thumb', 'ant-segmented-thumb-motion-appear-active')
//test.removeAttribute("style")
}
this.classList.add('ant-segmented-item-selected')
//test.classList.add('ant-segmented-thumb', 'ant-segmented-thumb-motion-appear-active')
//test.style.cssText = `transform: translateX(${calcThumbStyle.left}); width: ${calcThumbStyle.width}`
}
console.log(calcThumbStyle)
}
}
segmentedSwitch()
</script>
<style>
.ant-segmented{
box-sizing:border-box;
margin:0;
padding:2px;
color:rgba(0, 0, 0, 0.65);
font-size:14px;
line-height:1.5714285714285714;
list-style:none;
font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif,'Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol','Noto Color Emoji';
display:inline-block;
background-color:#f5f5f5;
border-radius:6px;
transition:all 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
}
.ant-segmented .ant-segmented-group{
position:relative;
display:flex;
align-items:stretch;
justify-items:flex-start;
width:100%;
}
.ant-segmented .ant-segmented-item-selected{
background-color:#ffffff;
box-shadow:0 1px 2px 0 rgba(0, 0, 0, 0.03),0 1px 6px -1px rgba(0, 0, 0, 0.02),0 2px 4px 0 rgba(0, 0, 0, 0.02);
color:rgba(0, 0, 0, 0.88);
}
.ant-segmented .ant-segmented-item{
position:relative;
text-align:center;
cursor:pointer;
transition:color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
border-radius:4px;
user-select: none;
}
.ant-segmented .ant-segmented-item::after{
content:"";
position:absolute;
width:100%;
height:100%;
top:0;
inset-inline-start:0;
border-radius:4px;
transition:background-color 0.2s;
}
.ant-segmented-item:hover:not(.ant-segmented-item-selected):not(.ant-segmented-item-disabled) {
color:rgba(0, 0, 0, 0.88);
}
.ant-segmented-item:hover:not(.ant-segmented-item-selected):not(.ant-segmented-item-disabled)::after{
background-color:rgba(0, 0, 0, 0.06);
}
.ant-segmented .ant-segmented-item-label{
min-height:28px;
line-height:28px;
padding:0 11px;
overflow:hidden;
white-space:nowrap;
text-overflow:ellipsis;
}
.ant-segmented-disabled .ant-segmented-item,.ant-segmented-disabled .ant-segmented-item:hover,.ant-segmented-disabled .ant-segmented-item:focus{
color:rgba(0, 0, 0, 0.25);
cursor:not-allowed;
}
.ant-segmented .ant-segmented-item-disabled,.ant-segmented .ant-segmented-item-disabled:hover,.ant-segmented .ant-segmented-item-disabled:focus{
color:rgba(0, 0, 0, 0.25);
cursor:not-allowed;
}
.ant-segmented .ant-segmented-thumb{
background-color:#ffffff;
box-shadow:0 1px 2px 0 rgba(0, 0, 0, 0.03),0 1px 6px -1px rgba(0, 0, 0, 0.02),0 2px 4px 0 rgba(0, 0, 0, 0.02);
position:absolute;
inset-block-start:0;
inset-inline-start:0;
width:0;
height:100%;
padding:4px 0;
border-radius:4px;
}
.ant-segmented .ant-segmented-thumb~.ant-segmented-item:not(.ant-segmented-item-selected):not(.ant-segmented-item-disabled)::after{
background-color:#ffffff;
}
.ant-segmented .ant-segmented-thumb-motion-appear-active{
transition:transform 0.3s cubic-bezier(0.645, 0.045, 0.355, 1),width 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
will-change:transform,width;
}
</style>
遇到的问题三:
antv是使用的jsx构建组件,在vue中写jsx和在react中写jsx还是有些不一样的,这里可以直接看官方的vuejs/babel-plugin-jsx: JSX for Vue 3 ()就好,如何在jsx中使用v-show、v-model、slot这些眼熟的小伙伴们,网络不好可以看cnpmPackage - @vue/babel-plugin-jsx (npmmirror.com)
遇到的问题四:
我没有提过pr的经历(┬┬﹏┬┬),做过与开源最相关的事大概就是使用组件库吧,哈哈,百度了一番,我觉得这篇文章蛮好的如何参与开源项目 - 细说 GitHub 上的 PR 全过程 (),相关的可以从5.1看起走,过程大致如下:
- Fork项目仓库到自己的账号下
- 克隆自己账号下的项目到本地
- 下载依赖后开始魔改,但是建议新建一个分支,直接改main分支不太好
- 按照commit规范提交代码,然后push到仓库,推之前先拉取下最新代码
- GitHub会提示可以开一个Pullrequest,点击之后会跳转到Fork的仓库本体的Pullrequest区,按照pr模板填写描述,然后点击创建pr即可
- 最后就是等待了,通过被合入不通过被拒绝
总体来看流程还是蛮简单的,只要代码合规即可
遇到的问题五:
光通过jsx实现组件还不够,需要通过demo展示出来,通过摸索其他组件的demo,需要做如下的事情:
- 组件文件夹(如segmented)下新建demo文件夹,在其中新建demo案例,通过index.vue引入和暴露出去,官方已经做好了demo的展示样式,通过
demo-sort
包裹即可,category
应该是字面意思,type
就是放在左侧导航栏哪一块里面,title
和subTitle
就是左侧导航栏的正副标题,还需要把中、英文档引入,
-- components
|-- segmented
|-- demo
|-- basic.vue
|-- index.vue
|-- index.ts
|-- index.en-US.md
|--
// index.vue
<template>
<demo-sort>
<basic />
</demo-sort>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import CN from '../';
import US from '../index.en-US.md';
import Basic from './basic.vue';
export default defineComponent({
components: { Basic },
category: 'Components',
subtitle: '分段控制器',
type: 'Data Display',
title: 'Segmented',
CN,
US,
});
</script>
demo案例中的doc标签就是文档,order是demo-sort
用来排序的,title是demo与描述之间分割线中的内容,zh-CN、en-US是demo下方的描述,不是index.vue中引入的CN、US文件,CN、US中是组件的描述、分类和API这些
<docs>
---
order: 0
title:
zh-CN: 基本用法
en-US: Basic Usage
---
## zh-CN
最简单的用法。
## en-US
The most basic usage.
</docs>
<template>
<a-segmented options="['Daily', 'Weekly', 'Monthly', 'Quarterly', 'Yearly']" />
</template>
- components文件夹下有components.ts、style.ts两个文件,分别将组件、组件props和组件样式暴露和导入即可
-- components
|-- components.ts
|-- style.ts
// components.ts
export type { SegmentedProps } from './segmented';
export { default as Segmented } from './segmented';
// style.ts
import './segmented/style';
- antv的前端页面是放在site文件下,我们需要修改的地方有两处site/src/router/demoRouters.js(添加demo路由)、site/src/demo.js(添加demo描述)
-- site
|-- src
|-- router
|-- demoRoutes.js
|-- demo.js
// demoRoutes.js
export default [
{
path: 'segmented:lang(-cn)?',
meta: {"category":"Components","type":"数据展示","title":"Segmented","subtitle":"分段控制器"},
component: () => import('../../../components/segmented/demo/index.vue'),
},
]
// demo.js
export default {
segmented: {
category: 'Components',
subtitle: '分段控制器',
type: 'Data Display',
title: 'Segmented',
},
}
- 修改了这些就可以看到demo效果啦
遇到的问题六:
单元测试,这个是组件库必备的,antv采用了@vue/test-utils、@vue/vue3-jest配合jest来做,这块确实是我比较薄弱的,没有写过单元测试说老实话,后续会针对这块好好学习下
最后
这将近一个月下来,因为懒惰拖了很久,然后感染生病确实不在状态鸽了一周,好在还是跌跌撞撞把这个写完,当pr提交的那一刻,有些激动、有些释然,pr能不能被通过已经不重要了(大概率要pass,写文的时候发现错漏了一些(┬┬﹏┬┬),当时检查没太仔细),不过这次经历还是让我学到了很多,第一次提交pr、vue中jsx的运用、组件的封装、API的考虑等等,感谢编程路上为我提供过帮助的大佬们