目前組件化開發思想已經成爲了前端的主流。三大框架(Vue, React, Angular)都是組件化開發最好的引領者。html
社區有不少的文章告訴咱們如何作組件劃分,應該遵循哪些原則。而且在作項目的過程當中你也必定有一些本身的關於組件劃分的心得體會。前端
全部的這些,歸根結底,都是在教導咱們如何對組件進行更好的拆分,以使其更加的可複用。react
可是我要問你一個問題,組件是什麼?ios
組件是UI+邏輯的組合。git
這也就意味着,不管你怎麼拆分組件,最終都是把UI和邏輯封裝在一塊兒處理的。github
好比antd的Collapse,內置的UI樣式及邏輯代碼對使用者來講就是一個黑盒,用戶只能作有限的定製化修改。axios
這樣就會帶來一個問題,當使用者須要對UI部分作定製化處理,可能就須要再寫一個組件,而這兩個組件邏輯處理部分有多是同樣的,惟一的不一樣就在UI展現上。api
另外像一些獲取鼠標實時位置,獲取屏幕實時大小,獲取hover的狀態等等,全部的這些基礎的邏輯代碼,可能在咱們的項目中處處都是。bash
因此有沒有一種方式,能夠針對開發過程當中常常用到的基礎邏輯部分作封裝處理,而把UI渲染交給具體的使用者來作,從而實現更大的自由度。antd
在React Hooks出現以前,React有兩種辦法能夠作這件事: 高階組件,Render Props。
舉個例子,好比咱們想要得到鼠標的實時位置,而後作一些業務處理。 部分代碼以下:
<Mouse>
(mouse) => (<MyComponent mouse={mouse} />)
</Mouse>
複製代碼
Render Props的具體用法這裏不介紹了,能夠參考官網的用法。
咱們在Mouse組件中封裝了獲取鼠標位置的邏輯代碼,而後經過在props上掛載一個函數做爲參數返回鼠標位置的數據。 這樣子組件就能夠根據拿到的mouse隨意作一些處理了。
部分代碼以下:
function withMouse(WrappedComponent) {
// 獲取鼠標位置
const mouse = getMouse()
return class extends React.component {
...
render() {
// 返回包裝後的組件
return <WrappedComponent mouse={mouse} {...this.props}>
}
}
}
複製代碼
使用的時候可能像這樣:
const MyComponentWithMouse = withMouse(MyComponent)
複製代碼
如今若是需求變爲: 在知道鼠標位置的狀況下,還要知道window.size。 這時咱們就須要再建立一個獲取window.size的組件。代碼可能會變爲這樣:
<WindowSize>
(size) => (
<Mouse>
(mouse) => (<MyComponent size={size} mouse={mouse} />)
</Mouse>
)
</WindowSize>
複製代碼
以上代碼很容易會讓你想到嵌套地獄,這是不能容忍的。同時高階組件也存在相似的問題。這多是致使邏輯複用不能流行起來的一個很是重要的緣由。
React Hooks的出現,很好的解決了邏輯複用的難題。社區中也出現了不少的Hooks庫。
隨着Hooks誕生,利用它的Custom Hook能夠很方便的對邏輯進行封裝,並且使用起來和正常的hooks用法同樣,不須要任何額外的代碼。
下面咱們來看一個react use的例子。 仍是以上面的獲取鼠標位置信息來講明。看下react use是如何定義這個hooks的。
const useMouse = (ref: RefObject<Element>): State => {
const [state, setState] = useRafState<State>({
docX: 0,
docY: 0,
posX: 0,
posY: 0,
elX: 0,
elY: 0,
elH: 0,
elW: 0,
});
useEffect(() => {
const moveHandler = (event: MouseEvent) => {
if (ref && ref.current) {
const { left, top, width: elW, height: elH } = ref.current.getBoundingClientRect();
const posX = left + window.pageXOffset;
const posY = top + window.pageYOffset;
const elX = event.pageX - posX;
const elY = event.pageY - posY;
setState({
docX: event.pageX,
docY: event.pageY,
posX,
posY,
elX,
elY,
elH,
elW,
});
}
};
document.addEventListener('mousemove', moveHandler);
return () => {
document.removeEventListener('mousemove', moveHandler);
};
}, [ref]);
return state;
};
export default useMouse;
複製代碼
因爲這部分邏輯幾乎都是通用的,因此不須要咱們去關心具體的實現邏輯。具體業務中咱們只關心返回的位置信息是什麼,而後根據返回結果來作具體的業務處理。
因此,接下來咱們來重點看下用法,這部分纔是咱們須要關注的。
import {useMouse} from 'react-use';
const Demo = () => {
const ref = React.useRef(null);
const mouse = useMouse(ref);
return (
<div ref={ref}>
<div>Mouse position is: {JSON.stringify(mouse)} </div>
</div>
);
};
複製代碼
核心代碼只有這一行
const mouse = useMouse(ref);
複製代碼
再對比下上面提到的高階函數和Render Props,是否是感受清爽了好多。
更重要的是咱們不用去關心組件之間的層級關係,哪裏須要就能夠在哪裏用。並且開發人員能夠將精力都放在業務相關的代碼上,開發效率也會大大提高。
react-use是社區中比較流行的庫,它包含了衆多的基礎邏輯。在開發中必定會用到它。
ahooks是阿里的一個Hooks庫,也是目前使用比較多的一個庫,它裏面有些Hooks也是基於react-use來的。
因爲基礎邏輯幾乎都是同樣的,因此社區中不少的hooks庫,他們的代碼幾乎都是同樣的。
相比於其它的庫 ahooks有更加豐富的文檔及API規範,另外也豐富了一些其它庫沒有的hooks。 尤爲是useRequest的用法, 相較於react-use的useAsync用法,增長了輪詢、並行請求、防抖、節流等處理。
另外useRequest能夠經過配置requestMethod參數來使用本身的請求庫。 好比使用axios發送請求,默認的話會使用fetch發送請求。
import { useRequest } from 'ahooks';
import React from 'react';
import axios from 'axios';
export default () => {
const { data, error, loading } = useRequest('https://helloacm.com/api/random/?n=8&x=4', {
requestMethod: (param: any) => axios(param),
});
if (error) {
return <div>failed to load</div>;
}
if (loading) {
return <div>loading...</div>;
}
return <div>Number: {data?.data}</div>;
};
複製代碼
除此以外還能夠經過 UseRequestProvider 在項目的最外層設置全局 options。
import { UseRequestProvider } from 'ahooks';
export function ({children})=>{
return (
<UseRequestProvider value={{
refreshOnWindowFocus: true,
requestMethod: (param)=> axios(param),
...
}}>
{children}
</UseRequestProvider>
)
}
複製代碼
最後,鑑於其餘兩種語言語法層面的緣由,目前尚未一種相似React Hooks這種能夠方便的對邏輯進行共享的方式。
可是Hooks給了咱們一個很好的啓發,並向咱們展現了社區對於邏輯共享的熱情及需求。因此我相信,在不久的未來確定也會出現一種針對Angular及Vue的便利的邏輯共享方式。
到那個時候,全部底層通用的邏輯可能都有人幫你寫好了。開發人員就真的能夠將精力放在業務邏輯相關的代碼上了。