REACT组件抽象与复用

2,182 阅读7分钟

当我们在说组件抽象与复用的时候我们在说什么?本质就是组件的拆分。。

为什么拆

“分而治之”是一个好策略。但是不要滥用,只有必要的时候才去拆分组件, 不然可能得不偿失 。

拆分原则

高内聚低耦合 。

高内聚:就是把逻辑紧密相关的内容放在一个组件中。这段js,css,html都是为了实现同一个功能我们把它放在一个js文件里。react天生具有高内聚的特点。

低耦合:指的是不同组件之间的依赖关系要尽量弱化,也就是每个组件要尽量独立 。 保持整个系统的低耦合度,需要对系统中的功能有充分的认识,然后根据功能点划分模 块,让不同的组件去实现不同的功能

科普组件分类

函数组件(Functional Component )和类组件(Class Component)

划分依据是根据组件的定义方式。

// 函数组件
function Hello(props) {
  return <h1>Hello, {props.name}</h1>;
}

// 类组件
class Hello extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

类组件可以维护自身的状态变量,即组件的state,类组件还有不同的生命周期方法,可以让开发者能够在组件的不同阶段(挂载、更新、卸载),对组件做更多的控制。 类组件这么强大,首选类组件? 不,函数组件更加专注和单一,承担的职责也更加清晰,它只是一个返回React 元素的函数,只关注对应UI的展现。函数组件接收外部传入的props,返回对应UI的DOM描述,仅此而已。 函数组件的使用可以从思想上迫使你在设计组件时多做思考,更加关注逻辑和显示的分离,设计出更加合理的页面上组件树的结构。实际操作上,当一个组件不需要管理自身状态时,可以把它设计成函数组件,当你有足够的理由发现它需要“升级”为类组件时,再把它改造为类组件。

无状态组件(Stateless Component )和有状态组件(Stateful Component)

划分依据是根据组件内部是否维护state。无状态组件内部不使用state,只根据外部组件传入的props返回待渲染的React 元素。有状态组件内部使用state,维护自身状态的变化,有状态组件根据外部组件传入的props和自身的state,共同决定最终返回的React 元素。函数组件一定是无状态组件,类组件则既可以充当无状态组件,也可以充当有状态组件

展示型组件(Presentational Component)和容器型组件(Container Component)

划分依据是根据组件的职责.

展示型组件的职责是:组件UI长成什么样。展示型组件不关心组件使用的数据是如何获取的,以及组件数据应该如何修改,它只需要知道有了这些数据后,组件UI是什么样子的即可。外部组件通过props传递给展示型组件所需的数据和修改这些数据的回调函数,展示型组件只是它们的使用者。展示型组件一般是无状态组件,不需要state,因为展示型组件不需要管理数据,但当展示型组件需要管理自身的UI状态时,例如控制组件内部弹框的显示与隐藏,是可以使用state的,这时的state属于UI state。既然大部分情况下展示型组件不需要state,应该优先考虑使用函数组件实现展示型组件。

容器型组件的职责是:组件数据如何工作。容器型组件需要知道如何获取子组件所需数据,以及这些数据的处理逻辑,并把数据和逻辑通过props提供给子组件使用。容器型组件一般是有状态组件,因为它们需要管理页面所需数据。

class UserListContainer extends React.Component{
  constructor(props){
    super(props);
    this.state = {
      users: []
    }
  }
  
  componentDidMount() {
    var that = this;
    fetch('/path/to/user-api').then(function(response) {
      response.json().then(function(data) {
        that.setState({users: data})
      });
    });
  }

  render() {
    return (
      <UserList users={this.state.users} />
    )
  }
}

function UserList(props) {
  return (
    <div>
      <ul className="user-list">
        {props.users.map(function(user) {
          return (
            <li key={user.id}>
              <span>{user.name}</span>
            </li>
          );
        })}
      </ul>
    </div>
  )  
}

这三组概念都体现了关注点分离的思想:UI展现和数据逻辑的分离。函数组件、无状态组件和展示型组件主要关注UI展现,类组件、有状态组件和容器型组件主要关注数据逻辑。

它们之间的关联关系可以归纳为:函数组件一定是无状态组件,展示型组件一般是无状态组件;类组件既可以是有状态组件,又可以是无状态组件,容器型组件一般是有状态组件。

tips:受控组件与非受控组件,业务组件,ui组件,

实现

高阶组件( Higher Order Component, HOC)

并不是 React提供的某种 API,而是使 用 React 的一种模式,用于增强现有组件的功能 简单来说,一个高阶组件就是一个函数,这个函数接受一个组件作为输入,然后返回一个新的组件作为结果,而且,返回的新组件拥有了输入组件所不具有的功能 。 这里提到的组件指的并不是组件实例,而是一个组件类,也可以是一个无状态组件 的函数

import React from ’ react ’ J
function removeUserProp(WrappedComponent) {
  return class WrappingComponent extends React.Component {
    render() {
      const {user, ... otherProps} = this.props; 
      return <WrappedComponent {... otherProps) />
     }
}
export default removeUserProp;

分类

根据返回的新组件和传人组件参数的关系,高阶组件的实现方式可以分为两大类:

代理方式的 高阶组件 :

上面的 removeUserProp例子就是一个代理方式的高阶组件,特点是返回的新组件类 直接继承自 React.Component类。 新组件扮演的角色是传入参数组件的一个“代理”,在 新组建的 render 函数中,把被包裹组件渲染出来,除了高阶组件自己要做的工作,其余 功能全都转手给了被包裹的组件 。

function removeUserProp(WrappedComponent) { 
return function newRender(props) {
        const {user, ... otherProps) = props;
     return <WrappedComponent {... otherProps} />
  }
}

应用在下列场景中:

操纵 prop;

访问 ref:(访问 ref并不是值得推荐的 React组件使用方式)

抽取状态: 其实,我们已经使用过“抽取状态”的高阶组件了,就是 react-redux 的 connect 函 数,注意 connect 函数本身并不是高阶组件, connect 函数执行的结果是另 一个函数,这 个函数才是高阶组件 。

包装组件 。

const styleHOC = (WrappedComponent, style) => {
return class HOCComponent extends React.Component {
    render() { 
    return (
        <div style={style}>
         <WrappedComponent {... this.props}/>
        </div>
    }
 }
}

继承方式的高阶组件 。

class PureComponent extends Component{
    shouldComponentUpdate(nextProps,nextState){
     const {props,state} = this;
     return shallowCompare(nextProps,props) && shallowCompare(nextState,state)
        
    }
}
const modi fyProps HOC = (WrappedComponent) => {
    return class NewComponent extends WrappedComponent {
        render() {
        const elements= super.render() ;
        const newStyle = {
          color: (elements && elements.type ===’div’)?’red’:’green’
        }
        const newProps = { ... this.props, style: newStyle};
         return React.cloneElement(elements, newProps,elements.props.children);
        }
    }
}

代理方式更加容易实现和控制,继承方式的唯一优势是可以操纵特定组件的生命周期函数。 “优先考虑组合,然后才考虑继承 。”

以函数为子组件

const loggedinUser = ’ mock user ’;
class AddUserProp extends React.Component {
    render() {
        const user = loggedinUser ;
        return this.props.children(user);
    }
}
AddUserProp.propTypes = {
 children: React.PropTypes.func.isRequired
}

这个类的代码,和被增强组件的唯一联系就是 this.props.children,而且 this.props. children是函数类型,在 render 函数中 直接调用 this.props.children 函数 ,参数就是我们 希望传递下去的 user。 使用这个 AddUserProp 的灵活之处在于它没有对被增强组件有任何 props 要求,只 是传递一个参数过去,至于怎么使用,完全由作为子组件的函数决定

装饰器

其他

mixin

extands