Re从零开始的UI库编写生活-步骤管理组件Steps

2,269 阅读5分钟

鼓捣一下文字,讲一讲工作中遇到的趣事,今日来聊一聊步骤管理器。话说一日,产品经理带着和蔼的眼神缓步踱来,对着前端的页面这样分析道,前端同学啊,你看,我们的页面操作对用户来讲是零散的,用起来有好多不便,缺乏一个有效的引导,应该怎么去解决呢?哈哈,心想这问题不大。就说,我看不如这样吧,我们可以把这些零散又很有必要的操作集中起来,做成一个操作引导页,这样用户就无需跳转不同的页面去完成那些零散的操作了,是对用户体验的一个很大提升。与此同时,朋友A说他也想要用这样的方案去提升用户体验。ok,那么一个便捷可配置,通用性良好的步骤管理组件应该怎样去设计呢?

开始设计

以下均已React为例,但用Vue,Angular的实现大同小异。

静下心来想一想,要满足可灵活配置,高通用性,高可用性可并不容易,灵活配置和使用便捷是两个对立的面,如何去平衡好是一件重要的事情。例如我想要灵活配置,就免不了要多写代码,这样组件使用起来就会麻烦,参数容易忘记,要时不时看文档。

我们先从保证通用性的角度出发,一步步去解决这个问题。

组成

image

一般来说一个步骤管理组件由三个部分组成,分别是给与每一步步骤信息的标题部分,内容展示部分以及操控上/下一步的控制部分。

数据结构

根据组成来拆分,我们可以这样去配置每一个步骤。

// demo.jsx
<Steps stepset={[{
    title: '1',//标题
    content: <p>1</p>,//内容
    handleNext: () => { return true;}//控制true or false
}, {
    title: '2',
    content: <p>2</p>,
    handleNext: () => { return true;}
}, {
    title: '3',
    content: <p>3</p>,
    handleNext: () => { return true;}
}]} />

嗯,这很直观。

状态

image

每一个步骤都有三种状态,待选状态,当前状态,已选状态。我们可以轻易地通过当前步骤currentStep去判断每一步究竟属于哪种状态。

// steps.jsx
...
handleTitleStyle(stepIndex, currentStep) {
    if (stepIndex > currentStep) {
        return 'step-sign-default';
    }
    if (stepIndex < currentStep) {
        return 'step-sign-ed';
    }
    return 'step-sign-active';
}
...
<div className={['orther-style', this.handleTitleStyle(index,currentStep)].join(' ')}>{item.title}</div>
...

布局

image

布局的要点在于步骤标题栏的自适应和通用性上。

使用flex布局令标题栏上的‘零件’整齐地自适应

// steps.scss
.steps-box{
    display: flex;
}
.steps-item{
    flex: 1;
}
...

使用相邻兄弟选择符巧妙地识别出首个步骤,令横杠不在第一个步骤前显示。

// steps.scss
...
.step-item+.step-item .sign-box {
    &:before {
        content: '';
        position: absolute;
        display: inline-block;
        border-style: solid;
        border-width: 1px;
        border-color: #cdcdcd;
        width: 100%;
        height: 0;
        left: -50%;
        top: 46%;
        transition: all .2s;
    }
}
...

内容展示

除当前步骤外,其他步骤使用position:absolute;脱出文档流,仅留下当前步骤所占据的空间。

// steps.jsx
...
this.props.stepset.map((step, index) => {
    return (
        <div key={index} className="p-r">
            <div className="step-content" style={index == this.state.currentStep ? { position: 'relative', visibility: 'visible', zIndex: 1, left: 0 } : {}}>
                {
                    step.content
                }
            </div>
        </div>
    )
})
...

控制栏

image

没错,这里就是灵活配置与方便使用的平衡点。如果为了最大灵活配置考虑,控制栏的存在是没有必要的,完全可以将上一步or下一步每一个时刻的控制权交给开发者。但这样未免要开发者写很多控制代码,所以笔者认为这些控制代码可以交由组件去完成,我们只要在合适的地方提供相关的API,开发者也能完全控制步骤组件的每一个时刻。这样的折中方案即考虑到了灵活配置,又兼顾到了易用性。

通常在处理下一步的响应时,经常有异步的场景,例如点击下一步时需要远程校验一下验证码。嗯,我们可以用async/await去构建一个支持异步的处理程序。

// steps.jsx
...
async handelNextStep() {
    const len = this.props.stepset.length;
    if (this.props.stepset[this.state.currentStep].handleNext) {
        // 支持异步操作 return Promise
        const AllowNext = await this.props.stepset[this.state.currentStep].handleNext(this.props.stepset[this.state.currentStep], this.state.currentStep);
        if (AllowNext) {
            if (this.state.currentStep + 1 < len) {
                this.setState({
                    currentStep: this.state.currentStep + 1
                });
            }
        }
    } else {
        if (this.state.currentStep + 1 < len) {
            this.setState({
                currentStep: this.state.currentStep + 1
            });
        }
    }
}

handelPreStep() {
    if (this.state.currentStep - 1 >= 0) {
        this.setState({
            currentStep: this.state.currentStep - 1
        }, () => {
            this.props.stepset[this.state.currentStep].handlePre && this.props.stepset[this.state.currentStep].handlePre(this.props.stepset[this.state.currentStep], this.state.currentStep);
        });
    }
}
...

朋友A这时又问了,那我想在content中去控制步骤怎么办呢?嗯,这不是问题,只要将相关的操作API暴露出来,通过props传入即可。就像这样:

// demo.jsx
<Steps stepset={[{
    title: '1',//标题
    content: (handelNextStep, handelPreStep) => {
        <p onClick={() => handelNextStep()}>hello</p>
    },
    isCustomCtrl: true//是否自定义控制栏
}, {
    title: '2',
    content: <p>2</p>,
    handleNext: () => { return new Promise((resolve) => { resolve(true); }) }//支持异步操作
}]} />

ok,一个步骤管理组件就成型了。

源码

源码已放到GitHub 组件&&样式,可以在SluckyUI中的‘步骤管理Steps’标签下查看在线Demo。

这个步骤管理组件的故事到此就结束啦,产品经理与朋友A都很高兴。这个步骤管理组件现已集成在SluckyUI For React中,SluckyUI的理念是打造一个组件库种子,让其他开发者能够进行快速二次开发,减少不必要的造轮子,但当中的编写任重而道远,还有很多尚未完善的地方,欢迎多多交流加入SluckyUI。

从零开始系列传送门