目前组件化开发思想已经成为了前端的主流。三大框架(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的便利的逻辑共享方式。
到那个时候,全部底层通用的逻辑可能都有人帮你写好了。开发人员就真的能够将精力放在业务逻辑相关的代码上了。