组件库设计实战系列:复杂组件设计

论坛 期权论坛     
选择匿名的用户   2021-5-30 00:18   73   0
<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">&lt;Frame&gt;
  &lt;SlideList&gt;
    &lt;SlideItem /&gt;
    ...
    &lt;SlideItem /&gt;
  &lt;/SlideList&gt;
&lt;/Frame&gt;<span class="copy-code-btn">复制代码</span></code></pre>
</div>
<p>如下图所示:</p>
<p><a href="https://link.juejin.im?target&#61;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 &#61; -(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 &#61; get(this.container.getBoundingClientRect(), <span class="hljs-string">&#39;width&#39;</span>);
}

<span class="hljs-function"><span class="hljs-title">render</span></span>() {
  const rest &#61; omit(this.props, Object.keys(defaultProps));
  const classes &#61; classnames(<span class="hljs-string">&#39;ui-carousel&#39;</span>, this.props.className);
  <span class="hljs-built_in">return</span> (
    &lt;div
      {...rest}
      className&#61;{classes}
      ref&#61;{(node) &#61;&gt; { this.container &#61; node; }}
    &gt;
      {this.renderSildeList()}
      {this.renderDots()}
    &lt;/div&gt;
  );
}<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) &#61;&gt; (
  &lt;div
    className&#61;<span class="hljs-string">&#34;slideItem&#34;</span>
    role&#61;<span class="hljs-string">&#34;presentation&#34;</span>
    key&#61;{i}
    style&#61;{<!-- -->{ width }}
    onTouchStart&#61;{this.handleTouchStart}
    onTouchMove&#61;{this.handleTouchMove}
    onTouchEnd&#61;{this.handleTouchEnd}
  &gt;
    {child}
  &lt;/div&gt;
))}<span class="copy-code-btn">复制代码</span></code></pre>
</div>
<p>在移动端,我们需要监听三个事件,分别响应滑动开始,滑动中与滑动结束。其中滑动开始与滑动结束都是一次性事件,而滑动中则
分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:3875789
帖子:775174
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP