<div class="article-content">
<p>一个成熟的组件库通常都由数十个常用的 UI 组件构成,这其中既有按钮(Button),输入框(Input)等基础组件,也有表格(Table),日期选择器(DatePicker),轮播(Carousel)等自成一体的复杂组件。</p>
<p>这里我们提出一个<b>组件复杂度</b>的概念,一个组件复杂度的主要来源就是其自身的状态,即组件自身需要维护多少个不依赖于外部输入的状态。参考原先文章中提到过的木偶组件(dumb component)与智能组件(smart component),二者的区别就是是否需要在组件内部维护不依赖于外部输入的状态。</p>
<h2>实战案例 - 轮播组件</h2>
<p>在本篇文章中,我们将以轮播(Carousel)组件为例,一步一步还原如何实现一个交互流畅的轮播组件。</p>
<h3>最简单的轮播组件</h3>
<p>抛去所有复杂的功能,轮播组件的实质,实际上就是在一个固定区域实现不同元素之间的切换。在明确了这点后,我们就可以设计轮播组件的基础 DOM 结构为:</p>
<div>
<pre class="hljs bash"><code class="copyable"><Frame>
<SlideList>
<SlideItem />
...
<SlideItem />
</SlideList>
</Frame><span class="copy-code-btn">复制代码</span></code></pre>
</div>
<p>如下图所示:</p>
<p><a href="https://link.juejin.im?target=https%3A%2F%2Fuser-images.githubusercontent.com%2F3828728%2F36074835-cbb7eb1c-0f80-11e8-8350-b5a8a843a2f9.jpg"></a></p>
<p><code>Frame</code> 即轮播组件的真实显示区域,其宽高为内部由使用者输入的 <code>SlideItem</code> 决定。这里需要注意的一点是需要设置 <code>Frame</code> 的 <code>overflow</code> 属性为 <code>hidden</code>,即隐藏超出其本身宽高的部分,每次只显示一个 <code>SlideItem</code>。</p>
<p><code>SlideList</code> 为轮播组件的轨道容器,改变其 <code>translateX</code> 的值即可实现在轨道的滑动,以显示不同的轮播元素。</p>
<p><code>SlideItem</code> 是使用者输入的轮播元素的一层抽象,内部可以是 <code>img</code> 或 <code>div</code> 等 DOM 元素,并不影响轮播组件本身的逻辑。</p>
<h3>实现轮播元素之前的切换</h3>
<p>为了实现在不同 <code>SlideItem</code> 之间的切换,我们需要定义轮播组件的第一个内部状态,即 <code>currentIndex</code>,即当前显示轮播元素的 <code>index</code> 值。上文中我们提到了改变 <code>SlideList</code> 的 <code>translateX</code> 是实现轮播元素切换的关键,所以这里我们需要将 <code>currentIndex</code> 与 <code>SlideList</code> 的 <code>translateX</code> 对应起来,即:</p>
<div>
<pre class="hljs bash"><code class="copyable">translateX = -(width) * currentIndex<span class="copy-code-btn">复制代码</span></code></pre>
</div>
<p><code>width</code> 即为单个轮播元素的宽度,与 <code>Frame</code> 的宽度相同,所以我们可以在 <code>componentDidMount</code> 时拿到 <code>Frame</code> 的宽度并以此计算出轨道的总宽度。</p>
<div>
<pre class="hljs bash"><code class="copyable"><span class="hljs-function"><span class="hljs-title">componentDidMount</span></span>() {
const width = get(this.container.getBoundingClientRect(), <span class="hljs-string">'width'</span>);
}
<span class="hljs-function"><span class="hljs-title">render</span></span>() {
const rest = omit(this.props, Object.keys(defaultProps));
const classes = classnames(<span class="hljs-string">'ui-carousel'</span>, this.props.className);
<span class="hljs-built_in">return</span> (
<div
{...rest}
className={classes}
ref={(node) => { this.container = node; }}
>
{this.renderSildeList()}
{this.renderDots()}
</div>
);
}<span class="copy-code-btn">复制代码</span></code></pre>
</div>
<p>至此,我们只需要改变轮播组件中的 <code>currentIndex</code>,即可间接改变 <code>SlideList</code> 的 <code>translateX</code>,以此实现轮播元素之间的切换。</p>
<h3>响应用户操作</h3>
<p>轮播作为一个常见的通用组件,在桌面和移动端都有着非常广泛的应用,这里我们先以移动端为例,来阐述如何响应用户操作。</p>
<div>
<pre class="hljs bash"><code class="copyable">{map(children, (child, i) => (
<div
className=<span class="hljs-string">"slideItem"</span>
role=<span class="hljs-string">"presentation"</span>
key={i}
style={<!-- -->{ width }}
onTouchStart={this.handleTouchStart}
onTouchMove={this.handleTouchMove}
onTouchEnd={this.handleTouchEnd}
>
{child}
</div>
))}<span class="copy-code-btn">复制代码</span></code></pre>
</div>
<p>在移动端,我们需要监听三个事件,分别响应滑动开始,滑动中与滑动结束。其中滑动开始与滑动结束都是一次性事件,而滑动中则 |
|