为什么要拆分组件

提高可读性、可维护性

如果不拆分

  • 代码量大,所有内容集中在一起
  • 相同组件无法复用
  • 业务开发分工不明确,开发人员要关心非业务的代码
  • 改代码时,可能会影响其他业务,牵一发动全身(耦合)
  • 任何一个操作都导致整个应用重新render

目标

  • 架构清晰
  • 相同组件能够复用
  • 业务分工明确,开发人员仅专注与自己的业务
  • 每个组件负责独立的功能,与其他组件解耦合
  • 可使用SCU、memo减少不必要渲染

如何拆分组件

把相关联的东西放一起(按功能、业务)

  • 横向(按业务、功能模块划分)
  • 纵向(应用、系统层级划分)

一个React组件的功能

  • 维护局部数据: state、ref、后台返回等
  • 获取、修改全局数据
  • 事件处理、数据监听处理(useEffect/componentDidUpdate等)
  • IO: 网络请求/本地读写
  • 数据处理
  • render

组件分类

展示组件

只有render方法、简单的交互事件处理和state管理。比如Input/CheckBox等。

划分标准: 根据UI稿,不同的展示模块分为不同的组件。比如顶部、底部、导航、列表等

容器组件

业务组件

与数据源(redux/后台/本地存储)进行数据传输操作(不止是IO)

划分标准: 根据业务功能划分。比如登录、登出、支付、表单校验等

连接组件

连接业务组件和展示组件, 主要用于处理数据后传给展示组件。

组件树结构

展示组件内可以有容器组件,容器组件内也可以有展示组件

案例

逻辑、展示分离

把渲染和功能拆分成不同组件,提高复用性

不拆分

登录组件处理了2件事情:

  1. 渲染登录表单
  2. 记录用户输入和登录状态,向后台发送登录请求
class Login extends Component {  constructor(props) {    super(props)    this.state = {      account: '',      password: '',      status: 'init',    }  }  handleAccountChange(e) {    this.setState({account: e.target.value})  }  handlePasswordChange(e) {    this.setState({password: e.target.value})  }  handleLoginClick() {    this.setState({ status: 'ing' })    request('/login', {      params: {        account: this.state.account,        password: this.state.password,      }    }).then(() => {      this.setState({status: 'succ'})    }).catch(() => {      this.setState({status: 'fail'})    })  }  render() {    return (      <div>        <input          placeholder="账号"          value={this.state.account}          onChange={(...args) => this.handleAccountChange(...args)}        />        <input          placeholder="密码"          value={this.state.password}          onChange={(...args) => this.handlePasswordChange(...args)}        />        <button onClick={() => this.handleLoginClick()}>登录</button>      </div>    )  }}

拆分后

容器组件负责实现登录功能,展示组件负责渲染内容。

如果要实现另一套登陆组件时,可直接复用容器组件,只需要实现新的展示组件即可。

// 业务组件 可复用性比较高function withLogin(config) {  const { mapStateToProps, mapDispatchToProps } = config  return (Comp) => {    class Container extends Component {      constructor(props) {        super(props)        this.state = {          account: '',          password: '',          status: 'init',        }      }      handleAccountChange = (e) => {        this.setState({account: e.target.value})      }      handlePasswordChange = (e) => {        this.setState({password: e.target.value})      }      handleLoginClick = () => {        this.setState({ status: 'ing' })        request('/login', {          params: {            account: this.state.account,            password: this.state.password,          }        }).then(() => {          this.setState({status: 'succ'})        }).catch(() => {          this.setState({status: 'fail'})        })      }      render() {        const propsFromState = mapStateToProps(this.state, this.props)        const propsFromDispatch = mapDispatchToProps({          onAccountChange: this.handleAccountChange,          onPasswordChange: this.handlePasswordChange,          onSubmit: this.handleLoginClick,        }, this.props)        return (          <Comp            {...this.props}            {...propsFromState}            {...propsFromDispatch}          />        )      }    }    return LoginContainer  }}// 展示组件class Login extends Component {  render() {    const { account, password, onAccountChange, onPasswordChange, onSubmit }    return (      <div>        <input          placeholder="账号"          value={account}          onChange={(...args) => onAccountChange(...args)}        />        <input          placeholder="密码"          value={password}          onChange={(...args) => onPasswordChange(...args)}        />        <button onClick={() => onSubmit()}>登录</button>      </div>    )  }}// 连接组件const LoginContainer = withLogin({  mapStateToProps: (state, props) => {    return {      account: state.account,      password: state.password,    }  },  mapDispatchToProps: (dispatch, props) => {    return {      onAccountChange: dispatch.onAccountChange,      onPasswordChange: dispatch.onPasswordChange,      onSubmit: dispatch.Submit,    }  }})

渲染优化

把UI上相互独立的部分,划分成不同组件,防止渲染时相互影响。最常见的是列表组件。

拆分前

点击一个li, 其他li全都重新渲染

class List extends Component {  state = {    selected: null  }  handleClick(id) {    this.setState({selected: id})  }  render() {    const { items } = this.props    return (      <ul>      {        items.map((item, index) => {          const {text, id} = item          const selected = this.state.selected === id          return (            <li              key={id}              className={selected ? 'selected' : ''}              onClick={() => this.handleClick(id)}            >              <span>{text}</span>            </li>          )        })      }      </ul>    )  }}

拆分后

子组件使用PureComponentmemo,并且click事件回调函数直接使用this.handleClick,而不是每次都创建新函数。

点击li,最多只会有2个子组件渲染。

// onClick时需要的参数,要传进来class Item extends PureComponent {  render() {    const { id, text, selected, onClick } = this.props    return (      <li        className={selected ? 'selected' : ''}        onClick={onClick(id)}      >        <span>{text}</span>      </li>    )  }}class List extends Component {  state = {    selected: null  }  handleClick(id) {    this.setState({selected: id})  }  render() {    const { items } = this.props    return (      <ul>      {        items.map((item, index) => {          const {text, id} = item          return (            <Item              key={id}              id={id} // 传进去              selected={this.state.selected === id}              text={text}              onClick={this.handleClick}            />          )        })      }      </ul>    )  }}