组件开发实战-组件分类与组件设计原则

随着Vue 和 React的不断推广,带来的是组件化和数据驱动的开发方式,抛弃了依托于jQuery那种意大利面的开发方式。

依托于 Webpack 等构建工具,使得前端代码具备了后端编程语言的代码组织能力,摆脱了传统的「一泻而下」式的代码编写。至此,作为前端也该对自己的代码有更高的要求,尤其是对组件的设计、规划方面。

组件分类

一个组件只做一件事,基于功能做好职责划分。

类似于一个方法只做一件事的思想。

无状态组件

React
// 无状态组件
const noStatus = props => <h1>{props.title}</h1>

复制代码

看起来就像一个简单的模版渲染过程。

Vue 中没有无状态组件的概念,但实际上也存在类似功能的组件形式。比如图标组件,只接收 props 渲染模版,不做多余的动作。

Vue
<template>
    <i class="icon" :class="'icon-' + name"></i>
</template>
<script>
export default {
    props: {
        name: String
    }
}
</script>
复制代码
UI组件(木偶组件)

UI 组件指的是界面扩展类组件,比如:输入框、表格、树、下拉框等。像 Element、Vux 等组件库均属于此类组件。

此类组件的特点是:复用性强,只通过 props、events 和 slots 等组件接口与外部通信。 更像是一个对 HTML 的扩展标签。

业务组件(智能组件)

业务组件通常是根据最小业务状态抽象而出,有些业务组件也具有一定的复用性,但大多数是一次性组件。

那么业务组件的数据来自哪呢?

  1. props
  2. global state
容器组件(一个模块)

这类组件就是一个盒子,一般当作一个业务子模块的入口,比如一个路由指向的组件。

通常是这种形式:

<div>
    <moduleA></moduleA>
    <moduleB></moduleB>
    <moduleC></moduleC>
</div>
复制代码
  • 容器组件内的子组件通常具有业务或数据依赖关系。
  • 如果没有使用全局状态管理,那么容器组件就是负责通过 props 分发数据到各个子组件,在通过 events 处理各个子组件的业务响应。此时容器组件需要做数据请求工作。
  • 如果使用了全局状态管理,那么容器内部的业务组件可以自行调用全局状态处理业务。但并不是说此时容器组件什么都不用干了。即使不需要请求数据,还是有许多组件间或一个业务模块内的诸多统筹工作要做。

把上面的各类组件组装到一起就组成一个业务模块。

对应的文件目录结构实例:

注:仅供参考
复制代码

组件设计原则

尽可能的减少状态
  1. 如果一个数据可以由另一个 state 变换得到,那么这个数据就不是一个 state。只需要写一个变换的处理函数,在 Vue 中可以使用计算属性。
  2. 如果你的 state 是一个数组,而模版最外层是渲染这个数组,那么你需要做的事是把渲染的项作为一个组件,只接受一个单级对象形式的数据,由外部决定这个组件的展示次数。
  3. 如果一个数据是固定的,不会变化的常量,那么这个数据就如同 HTML 固定的站点标题一样,写死或作为全局配置属性等,不属于 state。
  4. 如果一个数据需要从外部得到,它应该属于 props。
  5. 如果组件和兄弟组件拥有相同的 state,那么这个 state 应该放到更高的层级中,使用 props 传递到两个组件中。
合理的依赖关系
  1. 父组件不依赖子组件。要做到当我们把子组件删除后,只是丢失了一个功能,或一个模块等,而不会造成父组件及兄弟组件功能异常。
  2. 子组件基于父组件传递 props 作出个性化展示。
扁平化参数

像 HTML 原生元素那样,只接受原始类型(字符串、数值、布尔值和函数)作为属性,避免复杂的对象。当然,数据除外。

<!-- good -->
<my-component
  label="hello"
  :actived="true"
  :width="600"
  :on-show="show">
</my-component>
<!-- bad -->
<my-component :config="myConfig"></my-component>

复制代码
良好的接口设计
  1. 把组件内部可以完成的工作做到极致。虽然提倡拥抱变化,但接口不是越多越好。
  2. 如果常量变为 props 能应对更多的场景,那么就可以作为 props。原有的常量可作为默认值。
  3. 如果组件不能提供调用者所需求的功能,那么这个组件的接口还不够完善。
  4. 如果需要为了某一调用者编写大量特定需求的代码,那么可以考虑通过扩展等方式构建一个新的组件。
  5. 保证组件的属性和事件足够的给大多数的组件使用。
信息冗余

尽量避免信息冗余,如果某个 state 可以由其他 state 计算得到,那么就删除这个 state,例如

错误

getInitialState: function(){
  return {
    fullName: this.props.firstName + this.props.lastName;
  }
}

render: function(){
  return {this.state.fullName}
}
复制代码

正确

render: function(){
  return {this.props.firstName + this.props.lastName}
}
复制代码
api 尽量和已知概念保持一致

如果 api 可以和已知概念保持一致,那么就用已知的 api

错误

<Tabs activeKey="1">
<TabPane tab="title1" eventKey="2" />
<TabPane tab="title2" eventKey="1" />
</Tabs>
复制代码

eventKey 用来唯一标示 tabs 的 tabpane,同时 tabs 通过 activeKey 来匹配 eventKey 来确定哪个 tabpane 是当前 active 的。

正确

<Tabs activeKey="1">
<TabPane key="2" />
<TabPane key="1" />
</Tabs>
复制代码

我们可以复用 key 的 api,key 唯一标示了 tabs 的 某个 tabpane,并且对于后期更新也更高效

使用标签嵌套

尽量使用标签嵌套而不是属性配置。

错误:

<tabs panels={[{tab:'t1',pane:<a>1</a>}, {tab:'t2',pane:<a>2</a>}]}/>
复制代码

正确

<Tabs>
<TabPane tab="title1" key="2"><a>1</a></TabPane>
<TabPane tab="title2" key="1"><a>2</a></TabPane>
</Tabs>
复制代码
避免使用 ref

使用父组件的 state 控制子组件的状态而不是直接通过 ref 操作子组件

错误

{
  handleClick(){
    this.refs.x.setState({count:count});
  }

  render(){
    return <div onClick={this.handleClick}>
    <X ref='x'/>
    </div>
  }
}
复制代码

正确

{
  handleClick(){
    this.setState({count:count});
  }

  render(){
    return <div onClick={this.handleClick}>
    <X count={this.state.count}/>
    </div>
  }
}
复制代码
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值