据说你还在用HOC?一键改为Hooks行不行

前言

在React的使用中,开发者老是以一种“懒惰”的精神来进行着组件化,模块化的工做,从最开始的mixins,到HOC,render props,无一不是为了这个目的而奋斗,但是它们又有弊病,从16.8开始,React Hooks横空出世,HOC的多层嵌套,props的覆盖等问题也被拎了出来,那么,该如何从HOC过渡到Hooks呢。javascript

什么是高阶组件(HOC)

官方文档和我的理解

react官方文档中是这么定义HOC的:html

高阶组件(HOC)是 React 中可复用组件逻辑的一种高级技巧。 HOC 自身不是 React API 的一部分,他是一种基于 React 的组合特性而造成的设计模式。前端

高阶组件自己是一个函数,能提供的功能也和函数相同,即输入与输出,它经过对输入组件和其余参数的处理,输出一个新的具备咱们所需的通用数据和方法的组件。java

一个简单的需求

如今网课比较火,就用网课的做业平台做为一个例子,设想这个一个需求,须要将老师布置的做业以列表的方式显示出来,若是当前用户是老师,则增长添加做业的功能,大约会写出这样的代码:react

class HomeWorkList extends React.Component {
  constructor(props){
    super(props)
    this.state = {
      list: []
    }
  }
  
  getList = () => {
    http.get('/homework/list')
    	.then(result => {
      	if (result && result.success) {
         this.setState({
           list: result.data,
         })
         return;
        }
      	console.log('error:', result)
    	})
    	.catch(ex => {
      	console.log('ex:', ex)
    	})
  }
  /** * @param {number} type 标志更新类型,1 为新增,2 为修改 * @param {object} data 须要更新的数据 */
  updateList = (type, data) => {
    if (type === 1) {
	    this.setState({ list: this.state.list.concat(data) })
    } else if (type === 2) {
      this.setState({ 
        list: this.state.list.map(item => 
          item.id === data.id ? data : item)
      	})
    }
  }
  
  componentDidMount() {
    this.getList();
  }
  render() {
    const { isTeacher } = this.props;
    return (
    	<LayoutContainer>
      	{
          this.state.list.map(item => (
          	<HomeWorkItem
              data={item}
              isTeacher={isTeacher}
              update={data => this.updateList(2, data)}
            />
            {/* 该组件提供修改方法,修改为功后调用update操做 */}
          ))
        }
        {
          isTeacher ? (
          	<AddHomeWork update={data => this.updateList(1, data)} />
            {/* 该组件提供新增方法,新增成功后,调用update操做 */}         
          ) : null
        }
      </LayoutContainer>
    )
  }
}
复制代码

以上代码中规中矩,没有什么特别的地方,也不太值得被挑剔,因此就到此为止了吗?设计模式

另外一个简单的需求

可是,这时候,你发现学生提交的做业列表的需求也是将全部学生提交的做业进行列表呈现,为老师提供每条做业的评分,为未提交做业的学生提供提交做业的入口时,你会写出和上面雷同的代码:数组

class SubmitList extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      list: []
    }
  }
  getList = () => {
    http.get('/result/list/')
    	.then(result => {
      	// 省略部分判断
      	this.setState({ list: result.data })
    	})
  }
  updateList = (type, data) => {
    // 同上
  }
  
  render() {
    const { isTeacher, isStudent, submited } = this.props;
    return (
    	<LayoutContainer>
      	{
          this.state.list.map(item => (
          	<ResultItem data={item} update={data => this.updateList(2, data)} />
          ))
        }
        {
          isStudent && !submited ?
            <SubmitHomework update={data => this.updateList(1, data)} /> :
          	null
        }
      </LayoutContainer>
    )
  }
}
复制代码

能够看出,这两个组件之间存在有不少相同的代码,若是这时候还有相似的列表需求,还须要写出不少相似的重复代码,这种状况下,考虑将公共部分拆为HOC。ide

高阶组件改造

const withList = ({ url }) => RenderComponent => {
	return class extends React.Component {
    constructor(props) {
      super(props)
      this.state = {
        list: []
      }
    }
    getList = () => {
      http.get(url)
      	.then(result => {
        	if (result.success) {
	        	this.setState({ list: result.data })
          } else {
            console.log('error:', result)
          }
      	})
      	.catch(ex => {
        	console.log('ex:', ex)
      	})
    }
    updateList = (type, data) => {
      if (type === 1) {
        this.setState({ list: this.state.list.concat(data) })
      } else if (type === 2) {
        this.setState({ 
          list: this.state.list.map(item => 
            item.id === data.id ? data : item)
          })
      }
    }
    componentDidMount() {
      this.getList()
    }
    render() {
      const { list } = this.state
      return <RenderComponent list={list} update={this.update} /> } } } 复制代码

这时候,就能够很简单的实现上面两个功能和其相似功能了,我通常使用装饰器(注解)的方式:模块化

// 做业列表
@withList({ url: '/homework/list' })
class HomeWorkList extends React.Component {
  render() {
  	const { list, isTeacher, update } = this.props;
    return (
    	<LayoutContainer>
      	{
         	list.map(item => (
          	<HomeWorkItem
              data={item}
              isTeacher={isTeacher}
              update={data => update(2, data)}
            />
            {/* 该组件提供修改方法,修改为功后调用update操做 */}
          ))
        }
        {
          isTeacher ? (
          	<AddHomeWork update={data => update(1, data)} />
            {/* 该组件提供新增方法,新增成功后,调用update操做 */}         
          ) : null
        }
      </LayoutContainer>
    )
  }
}

// 提交做业列表
@withList({ url: '/homework/result' })
class ResultList extends React.Component {
  render() {
    const { isTeacher, isStudent, submited, update, list } = this.props;
    return (
    	<LayoutContainer>
      	{
          list.map(item => (
          	<ResultItem data={item} update={data => update(2, data)} />
          ))
        }
        {
          isStudent && !submited ?
            <SubmitHomework update={data => update(1, data)} /> :
          	null
        }
      </LayoutContainer>
    )
  }
}
复制代码

这样,对于增删改查的相似需求,就再也不须要每次写一堆相同的冗余代码,而只须要使用HOC对相应内容进行项进行渲染就能够了。函数

HOC出现以前,通常使用mixin的方式,针对mixin所带来的一系列问题,早已达成共识,这里就再也不赘述。

最后,以上🌰中的更新数据也能够拆为高阶组件,这里就再也不赘述,下面,来看一下关于React Hooks的内容。

什么是React Hooks

文档解读

官方文档说,Hook 是 react 16.8 的新增特性,它可让你在不编写 class 的状况下使用 state 以及其余的 React 特性。

Hook的中文意思是钩子,其中一共有四个经常使用的钩子,分别是:

useState()
useContext()
useReducer()
useEffect()
复制代码

以上几个钩子顾名思义,很容易知道其意思。

好比useState是一个状态钩子,针对一个纯函数组件(木偶组件,也就是 dumb 组件),是没有 state 的,因此将其状态放在钩子下面。

useContext是一个共享状态钩子,context 自己是一个组件顶层 API ,这个钩子的做用就是让你在不使用 connect 的状况下,直接订阅 Context。

useReducer 则是一个 action 钩子,React 自己不提供状态管理功能,一般使用 Redux 来作状态管理,这个钩子则是引入了 Redux 中 reducer 功能。

userEffect 和字面意思一致,是一个反作用钩子,在前端,最多见的反作用是向服务端请求数据,这个组件能够代替 class 组件中的 componentDidMount 等功能。

用React Hooks改写HOC

通常来讲,介绍hooks的文章几乎就到上面就戛然而止了,那么,怎么用 Hook 来代替 HOC 呢,简单来说,如何用 Hook 来改写第一部分那个关于记录点击的例子呢,我写了这样的一部分代码:

import { useState, useEffect } from 'react'

export default function(url) {
    let [list, setList] = useState([]);

    useEffect(() => {
        http.get(url)
            .then(result => {
                if (result.success) {
                    setList(result.data)
                } else {
                    console.log('error:', result)
                }
            })
            .catch(ex => {
                console.log('ex:', ex)
            })
    }, []) // 这里将第二个参数设置为空,其效果与componentDidMount相同

    const update = (type, data) => {
        if(type === 1) {
            setList(list.concat(data))
        } else if (type === 2) {
            setList(list.map(item => item.id === data.id ? data : item))
        }
    }

    return [list, update]
}
复制代码

用法也很简单

// 做业列表
const HomwWorkList = ({ isTeacher }) => {
  const [list, update] = useList('/homework/list')
  return (
    <LayoutContainer>
      {
        list.map(item => (
          <HomeWorkItem
            data={item}
            isTeacher={isTeacher}
            update={data => update(2, data)}
            />
          {/* 该组件提供修改方法,修改为功后调用update操做 */}
        ))
      }
      {
        isTeacher ? (
          <AddHomeWork update={data => update(1, data)} />
          {/* 该组件提供新增方法,新增成功后,调用update操做 */}         
        ) : null
      }
    </LayoutContainer>
  )
}

// 提交做业列表
const ReulstList = ({ isTeacher, isStudent, submited }) => {
  const [list, update] = useList('/homework/result')
  return (
    <LayoutContainer>
      {
        list.map(item => (
          <ResultItem data={item} update={data => update(2, data)} />
        ))
      }
      {
        isStudent && !submited ?
          <SubmitHomework update={data => update(1, data)} /> :
        null
      }
    </LayoutContainer>
  )
}
复制代码
相关文章
相关标签/搜索