乐趣区

关于react.js:React-Hook-系列三记一次中台项目的Hook沉淀

背景

本文旨在分享,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 处于激活状态,所以用户的各种操作只是不断更新 modaldata而已,所以如果全局有一个的专门记录 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>;

至此,contexthook 让页面和 modal 解耦,它们的分割只有 data, 而 data 又作为参数随时能够传入。
这优雅的写法,是不是让你耳目一新,心动了。

最初再看下 modal 的底细 modalvisible 参数默认是true,当setModal 后挂载,它才会被弹出显示。当 modal 敞开时,须要将全局挂载的 modal 置空,所以把全局 ModalContainer 记录的 modal 置空即可。

// useActionModal 自定义 hook 次要获取 modalProps
// modal 敞开事件中置空
afterClose() {
  // ...
  setModal(null);
},
// ...

2. 高度一致化的 table

在中台我的项目中,table 列表是很多模块的首页,根本蕴含 table 顶部输入框搜寻 table 列搜寻和筛选 分页 行右键操作 自定义列 等。
每个列表页面的不同点:列定义、数据、数据起源,其余的内容根本复制粘贴一把梭。如何解决这中无脑搬砖的活异样重要。
最终咱们层层封装,将数据和操作裸露进去,其余搬砖局部通过 useHook 和 Context 全副封装。
上面剖析一下咱们的解法。

table 顶部输入框搜寻

table 顶部输入框搜寻 输入框的内容和列表数据的起源相干。
需要: 在跳转详情后返回,输入框的内容须要放弃跳转前一样且执行查问数据,所以记录这个输入框的内容是要害。
解决办法: history statelocalStorage页面公共局部暗藏域 URL queryString

不言而喻 URL 记录更容易分享、珍藏和更直观的展现。最终 table 顶部输入框搜寻的关键字 被记录在 url querystring 中,只须要察看监听 URL 的 querystring 变动即可,这里用到了 react-routeruseLocation 即可监听。

那么问题来了,useLocation 是如何监听 url 的变动的?

浏览源码可知,react-router 也是通过 contextlocation history 对立治理及传递。而后通过监听浏览器 historypopstate 事件来触发更新 contextlocation

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 事件拿到。

行右键操作

右键的操作个别都是弹窗或者页面跳转,modaltable 的关系只有以后 table 选中要操作行的数据。
如何激活以后的列表项?而后将数据传给列表项对应的 modal 是要害。

antd table 裸露的 onRow 事件正好反对鼠标右键,所以只需右键就能激活操作列表,且能拿到操作行的数据,点击具体操作将对应的数据传入 modal,而后通过 setModal 到全局即可激活 modal。

// tableProps
onRow(record, index) {
  ...
  return {onContextMenu(e) {
      setMenuState({
        // 展现右键后以后的菜单
        visible: true,
        // 设置以后的数据
        currentRow: record
      });
    }
  }
}

// 菜单单击事件
handleMenuClick = () => {
  ...
  // 调用下面剖析的的 useActionCallback 显示 modal
  onAction(key, currentRow);
  ...
}

这样,页面和 modal 组件解耦,只跟依据用户的操作产生的数据来决定。用户点击操作项产生数据,将数据传入 modal,而后全局激活modal

总结

react hookcontext 联合会产生一些不堪设想的事件。

  • context 的创造就是为了 父子孙...组件间共享数据、全局记录数据。
  • Provide 负责传递共享的数据,useContext 负责生产数据,这里的生产包含应用、更新和删除等操作。
  • contextreact hook 能够让页面和一些反复的操作做一些解耦合操作。

【React Hook 系列】

  • React Hook 系列(一):彻底搞懂 react hooks 用法(长文慎点)
  • React Hook 系列(二):自定义 hook 的一些实际

客官,素质三连吧!????????????

退出移动版