react高阶组件,是时候了解一下了

647 阅读6分钟

初学者看到高阶组件,可能就会有点怕,因为当年我也一样。而如今react都发布到16.13了,新的操作和理念层出不穷,还有理由不好好了解一下高端操作吗?

react组件分类

先来看下,react组件有哪几类。一是有状态组件(class组件),二是无状态组件(函数组件)。

有状态组件大家应该都比较熟悉,平时都用烂了。现在将着重看下无状态组件,理解无状态组件是理解高阶组件的基础。

无状态组件:本质是一个纯函数,用函数的形式来构造组件

纯函数:输出完全由输入来决定,没有任何副作用

render是一个纯函数,输出只和state和props有关

目前主流比较倾向于使用无状态函数组件,组件中只有props,无state

// 无状态组件
const Person = props => <div>{props.name}</div>

// 传统组件
class Person extends React.Component{
    render(){
        return(
            <div>{props.name}</div>
        )
    }
}

高阶函数 vs 高阶组件

高阶函数:接收一个函数,返回一个函数

const identity = func => func 

高阶组件:接收一个组件,返回一个组件

const identity = BaseComponent => BaseComponent

因为组件可以是函数形式,高阶组件本质上就是高阶函数。

描述的是接受React组件作为输入,输出一个新的React组件(增强型组件)的组件。

主要功能:封装并抽离组件的通用逻辑,让此部分逻辑更好地在组件之间复用。

高阶组件写法

根据react组件无状态和有状态的两种分类,高阶组件有两种写法。

const HOC = (BaseComponent) => class extends React.Component{
    render(){
        return(
            <BaseComponent
                {...this.props}
            />
        )
    }
}
const HOC = BaseComponent => props => <BaseComponent {...props} />

注意:要让props继承到返回的组件中

上手高阶组件

下面来看一些小而美的高阶组件,以此对高阶组件的写法和功能有整体的了解。

withLoading

比如现在已经有一个List列表组件,现在需要加入一个loading判断,以往我们是加在render方法里,但这种做法会改变List组件的结构。如果其他组件中又需要加入loading,这个逻辑又需要重写一遍。为此,我们可以通过写一个高阶组件把这个loading的逻辑抽离出来。

// withLoading.js
const withLoading = BaseComponent => ({isLoading, ...otherProps}) => (
  isLoading ? 
    <div>加载中...</div> : 
    <BaseComponent {...otherProps}/>
)
export default withLoading;

这样,高阶组件withLoading,可以给普通组件传入属性isLoading。

在List组件中,使用withLoading组件包裹该组件,withLoading(List),这样List变成增强型组件,可以传属性isLoading

// List.js
function List (props) {
  return (
      <ul>
        {
          props.data.map((item, idx) => {
            return <li key={idx}>{item}</p></li>
          })
        }
      </ul>
  )
}
export default withLoading(List)
// app.js

<List data={list} isLoading={false}></List> // 返回加载中...

<List data={list} isLoading={true}></List> // 返回List组件

withLocalStorage

在组件中,我们经常需要跟localStorage交互,可以做一个withLocalStorage的高阶组件,将交互的逻辑抽离出来。

const withLocalStorage = BaseComponent => props => {
  let getLocalStorage = key => localStorage.getItem(key)
  let setLocalStorage = (key, value) => localStorage.setItem(key, value)
  let removeLocalStorage = key => localStorage.removeItem(key)
  return <BaseComponent 
          getLocalStorage={getLocalStorage}
          setLocalStorage={setLocalStorage}
          removeLocalStorage={removeLocalStorage}
          {...props}
        />
}
export default withLocalStorage;

flattenProp

现在有一个User组件

const User = props => {
  return (
    <div>
      <div>姓名:{props.user.username}</div>
      <div>年龄:{props.user.age}</div>
    </div>
  )
}
export default User;

// 调用
<User user={user}></User> //const user = {username: 'zhangsan', age: 20}

现在想把user属性扁平化,可以做一个flattenProp组件。

const flattenProp = propKey => BaseComponent => props => <BaseComponent {...props} {...props[propKey]} />
export default flattenProp;

针对User组件,运用高阶组件flattenProp将user属性扁平化,flattenProps('user')(User)

const User = props => {
  return (
    <div>
      <div>姓名:{props.username}</div>
      <div>年龄:{props.age}</div>
    </div>
  )
}
export default flattenProp('user')(User);
![](https://user-gold-cdn.xitu.io/2020/6/22/172da1f3dc2eef45?w=478&h=276&f=jpeg&s=134318)

renameProp

同样,我们可以对props重命名。

const renameProp = (oldProp, newProp) => BaseComponent => props => {
  let hoc = {
    ...omit(props, [oldProp]),
    [newProp]: props[oldProp]
  }
  return <BaseComponent {...hoc} />
}

export default renameProp;

同样对于上面User组件,我们想要把username改成name,可以使用高阶组件处理,renameProp('username', 'name')(User)

const User = props => {
  return (
    <div>
      <div>姓名:{props.name}</div>
      <div>年龄:{props.age}</div>
    </div>
  )
}
export default flattenProp('user')(renameProp('username', 'name')(User))

上面可以注意到已经将两个高阶组件组合到一起。后面我们可以用recomposecompose方法组合多个高阶组件。

withProps

添加props

const withProps = ownerProps => BaseComponent => props => {
  if (typeof ownerProps == 'function') {
    ownerProps = ownerProps(props)
  }
  return <BaseComponent {...props} {...ownerProps}/>
}

export default withProps

withProps的参数可以是对象或函数。如果是函数,参数是父组件props,返回一个对象。

withProps({math: 90, english: 40})

withProps(({math, english, chinese}) => ({
    total: math + english + chinese
}))

mapProps

过滤props,最后只保留返回的props

const mapProps = func => BaseComponent => props => {
  return <BaseComponent {...func(props)}/>
}
export default mapProps

mapProps的参数是函数。函数的参数是父组件的props,返回一个对象。

mapProps(({math, english, chinese}) => ({
  total: math + english + chinese
}))

除了以上操纵props外,我们还可以抽取state状态。通常用class组件setState,会带来整个页面的渲染开销。抽取state,将state放在一个小组件内部管理,而不需要在外部调用setState带来不必要的开销。

withState

添加state和更新state。基本思路是将state和更新state的函数做成props,以便调用。

const withState = (stateName, stateUpdaterName, initialState) => BaseComponent => {
  const factory = React.createFactory(BaseComponent)
  class withState extends React.Component {
    state = {
      stateValue: typeof initialState === 'function' ? initialState(this.props) : initialState
    }

    updateStateValue = (updateFn, callback) => 
      this.setState(({stateValue}) => ({
        stateValue: typeof updateFn === 'function' ? updateFn(stateValue) : updateFn
      }), callback)
    
    render () {
      return factory({
        ...this.props,
        [stateName]: this.state.stateValue,
        [stateUpdaterName]: this.updateStateValue
      })
    }
  }
  return withState
}

export default withState;

withState第一个参数是state名称,第二个参数是改变state的函数名称,第三个参数是初始值。

withState('count', 'changeCount', 0)

withHandlers

处理事件交互。

const withHandlers = handlers => BaseComponent => {
  const factory = React.createFactory(BaseComponent)
  var mapValues = (obj, func) => {
    let result = {}
    for (const key in obj) {
      if (obj.hasOwnProperty(key)) {
        result[key] = func(obj[key], key)
      }
    }
    return result
  }

  class withHandlers extends React.Component {
    handlers = mapValues(
      typeof handlers === 'function' ? handlers(this.props) : handlers,
      handlerFunc => (...args) => {
        var handler = handlerFunc(this.props)
        if (typeof handler !== 'function') {
          return handler
        }
        return handler(...args)
      }
    )
    render () {
      return factory({
        ...this.props,
        ...this.handlers
      })
    }
  }
  
  return withHandlers
}
withHandlers({
  handleChangeCount: props => e => {
    props.changeCount(2)
  }
})
// 也可以这样写
withHandlers({
  handleChangeCount: props => props.changeCount(2)
})

通常将withStatewithHandlers配合使用

compose(
  withState('count', 'changeCount', 0)
  withHandlers({
    handleChangeCount: props => props.changeCount(2)
  })
)

使用recompose

上面讲述的这些高阶组件,其实recompose都已经做了封装。我们可以直接使用recompose包来调用这些组件。

安装

yarn add recompose

使用

import {compose, flattenProp, renameProp, withProps, withState, withHandlers} from 'recompose'

compose(
  flattenProp('user'),
  renameProp('username', 'name'),
  withProps({
    sex: 'male'
  }),
  withState('count', 'changeCount', 0)
  withHandlers({
    handleChangeCount: props => props.changeCount(2)
  })
)(User)

进入的props

{
    user: {username: 'zhangsan', age: 20}
}

加工后的props

{
    name: 'zhangsan',
    age: 20,
    sex: 'male',
    count: 0,
    changeCount,
    handleChangeCount
}

最后

recompose还有其他不少高阶组件,有兴趣的小伙伴可以再深入研究研究。

文档:recompose

github:recompose