據說你還在用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>
  )
}
複製代碼
相關文章
相關標籤/搜索