背景
本文旨在分享,React hook
在中大型中台我的项目中的实际,适宜相熟 React hook
用法的同学,心愿能对你有帮忙。
用到的库
1. unstated-next
200 bytes to never think about React state management libraries ever again.永远不用再思考 React 状态治理了,仅仅 200 字节的状态治理解决方案。
unstated-next 次要是利用 React.createContext
状态共享,将须要注入 Provider
的状态以及状态更新操作形象到 hook
中,提供给 Function Component
用的一个状态治理库。ts源码只有 40 行。
源码剖析
export function createContainer(useHook) { let Context = React.createContext<Value | typeof EMPTY>(EMPTY); function Provider(props) { let value = useHook(props.initialState); // 将 hook 返回值 裸露给 Provider 的 value return <Context.Provider value={value}>{props.children}</Context.Provider>; } function useContainer() { // 用 useContext 获取 Context 上传递的 value let value = React.useContext(Context); if (value === EMPTY) { throw new Error("Component must be wrapped with <Container.Provider>"); } return value; } return { Provider, useContainer };}
2. use-immer
A hook to use immer as a React hook to manipulate state.一个用于将 immer 作为 React hook 来操纵状态的 hook。
use-immer
能够将 state 数据 immutable
,更新深层嵌套数据更为不便,且有函数编程的感觉。
const [value, setValue] = useImmer({ a: { b: { c: { d: 12 } }, b2: { c: 34 } },});// 某些场景下我只需扭转的的值setValue((draft) => { draft.a.b.c.d = 19;});// 数组某个值的变动setValue((draft) => { draft[2].name= 19;});
3. sunflower-antd
一些流程组件的自定义 hook,例如useModal
, useModalForm
等,晋升效率显著。 源码
我的项目实际
1. 烦不胜烦的 modal
在中台我的项目中,对一些列表的资源信息CRUD 弹窗是必不可少的,所以页面中table的治理必不可少,且很繁琐,容易凌乱。期初我是这样
<ConfigModal ... /><EditModal ... /><RenameModal ... />
很麻烦,且反复搬砖的代码很多。最终咱们用了 context 和 useHook 全局挂载激活的形式将 modal 和每个列表页解耦。
上面咱们逐渐剖析如何优雅的写modal。
Modal
的一次生命周期根本包含:
<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a0765b86ad4c479b9f622f943523e07c~tplv-k3u1fbpfcp-zoom-1.image" alt="图片替换文本" width="150" height="203" align="bottom" />
特点:
modal
的关上和敞开由用户操作决定。- 须要记录每次选中的数据,传给要操作的
modal
。 - 点击提交胜利后都须要敞开
modal
和页面触发刷新的操作。
每次只有一个激活的modal
和选中的数据一一对应,两者都是用操作的一瞬间确定的,且每次只有一个 modal 处于激活状态,所以用户的各种操作只是不断更新modal
和data
而已,所以如果全局有一个的专门记录 modal 的中央,这样咱们只需将用户要激活的modal
一直替换,而后在全局的某处挂载以后激活的modal
。在应用modal
的页面中,咱们只需一直去更新全局记录值,当modal
敞开时只需全局记录值置为空即可。这样在以后的页面中不须要再将烦人的泛滥modal
一次次的引入,也不须要保护一系列的visible
。问题来了,那如全局记录值modal
呢?
聪慧的你可能曾经想到了context
,没错就是它:
全局 Modal Context 记录的以后激活的 modal
const ModalContainer = createContainer(() => { const [modal, setModal] = useState<ReactNode>(null); return [modal, setModal] as const;});
指定全局挂载modal
的节点
const [modal] = ModalContainer.useContainer();<div id="active-modal">{modal}</div>;
激活以后要操作的 modal,咱们自定义了useAction
, 它的作用就是返回一匿名函数,他有两个参数key、 data
,Key 和 modal 一一对应,data示意以后操作行的数据。最初将data传入通过key确定的modal中,塞进全局的modalContext中。
// fn 是一个依据key返回对应 modal 的函数。export function useAction(fn) { const [, setModal] = ModalContainer.useContainer(); const fnRef = useRef(fn); // ... // key 标识对应的 modal, data为以后操作数据 return (key, data) => { // 依据key确定返回正在操作的modal。 const Result = fnRef.current(key, data); // 将 modal set进 context 里,就会激活modal显示。 setModal(<Result data={data} />); };}
接下来只须要,将useAction裸露给用户执行,通过传入的key和modal对应关系确定行将操作的modal,所以须要一一列举,他们的对应关系,咱们自定义了 useActionCallback
, 它接管列举所有 modal
的回调函数 fn,fn 依据传入的参数确定具体的 modal。
export function useActionCallback() { // 返回下面匿名函数 (key, data) => setModal(<Result data={data} />); return useAction((key, data) => { switch (key) { case Operations.Create: return CreateModal; // ... default: return null; })}
最终,在页面激活modal
只须要如下调用即可:
const onAction = useActionCallback();<Button type="primary" onClick={() => onAction(Operations.Edit, data)}> 编辑</Button>;
至此,context
和 hook
让页面和 modal
解耦,它们的分割只有 data
, 而 data
又作为参数随时能够传入。
这优雅的写法,是不是让你耳目一新,心动了。
最初再看下modal
的底细,modal
的 visible
参数默认是true
,当setModal
后挂载,它才会被弹出显示。当 modal 敞开时,须要将全局挂载的 modal
置空,所以把全局ModalContainer
记录的modal
置空即可。
// useActionModal 自定义hook 次要获取 modalProps// modal敞开事件中置空afterClose() { // ... setModal(null);},// ...
2. 高度一致化的 table
在中台我的项目中,table 列表是很多模块的首页,根本蕴含 table顶部输入框搜寻
、table列搜寻和筛选
、分页
、行右键操作
和 自定义列
等。
每个列表页面的不同点:列定义、数据、数据起源,其余的内容根本复制粘贴一把梭。如何解决这中无脑搬砖的活异样重要。
最终咱们层层封装,将数据和操作裸露进去,其余搬砖局部通过 useHook 和 Context 全副封装。
上面剖析一下咱们的解法。
table顶部输入框搜寻
table顶部输入框搜寻
输入框的内容和列表数据的起源相干。
需要: 在跳转详情后返回,输入框的内容须要放弃跳转前一样且执行查问数据,所以记录这个输入框的内容是要害。
解决办法: history state
、 localStorage
、页面公共局部暗藏域
和 URL queryString
。
不言而喻 URL
记录更容易分享、珍藏和更直观的展现。最终 table顶部输入框搜寻的关键字 被记录在 url querystring
中,只须要察看监听 URL的querystring变动即可,这里用到了 react-router
的 useLocation
即可监听。
那么问题来了,useLocation
是如何监听 url
的变动的?
浏览源码可知,react-router
也是通过 context
将 location history
对立治理及传递。而后通过监听浏览器 history
的 popstate
事件来触发更新 context
的 location
。
Router
组件局部源码如下:
this.unlisten = props.history.listen(location => { if (this._isMounted) { this.setState({ location }); } else { this._pendingLocation = location; } });}
history.listen
来自于 history
库,如下:
const PopStateEventType = 'popstate'; function handlePop() { // ... } window.addEventListener(PopStateEventType, handlePop); let history: MemoryHistory = { ... listen(listener) { return listeners.push(listener); }, }
扯远了,有趣味的能够看 react-router
的源码;
table列搜寻和筛选
同理,table列搜寻和筛选
和 分页
也须要有状态的(防止刷新和路由跳转返回搜寻条件失落的不便),也记录在 url
上,用 antd
能够通过 onChange
事件拿到。
行右键操作
右键的操作个别都是弹窗或者页面跳转,modal
和 table
的关系只有以后 table
选中要操作行的数据。
如何激活以后的列表项?而后将数据传给列表项对应的 modal 是要害。
antd table
裸露的 onRow
事件正好反对鼠标右键,所以只需右键就能激活操作列表,且能拿到操作行的数据,点击具体操作将对应的数据传入 modal
, 而后通过 setModal
到全局即可激活 modal 。
// tablePropsonRow(record, index) { ... return { onContextMenu(e) { setMenuState({ // 展现右键后以后的菜单 visible: true, // 设置以后的数据 currentRow: record }); } }}// 菜单单击事件handleMenuClick = () => { ... // 调用下面剖析的的useActionCallback显示modal onAction(key, currentRow); ...}
这样,页面和 modal
组件解耦,只跟依据用户的操作产生的数据来决定。用户点击操作项产生数据,将数据传入 modal
,而后全局激活modal
。
总结
react hook
和 context
联合会产生一些不堪设想的事件。
context
的创造就是为了父子孙...
组件间共享数据、全局记录数据。Provide
负责传递共享的数据,useContext
负责生产数据,这里的生产包含应用、更新和删除等操作。context
和react hook
能够让页面和一些反复的操作做一些解耦合操作。
【React Hook系列】
- React Hook 系列(一):彻底搞懂react hooks 用法(长文慎点)
- React Hook 系列(二):自定义hook的一些实际
客官,素质三连吧!????????????