强制Function类型组件刷新

执行reducer的dispatch 也可以使用useState 效果类似

  1. export default function useForceUpdate() {
  2. const [, forceUpdate] = React.useReducer(x => x + 1, 0);
  3. return forceUpdate;
  4. }

检查是否支持 flex的gap样式设定

  1. let flexGapSupported: boolean | undefined;
  2. export const detectFlexGapSupported = () => {
  3. if (!canUseDocElement()) {
  4. return false;
  5. }
  6. if (flexGapSupported !== undefined) {
  7. return flexGapSupported;
  8. }
  9. // create flex container with row-gap set
  10. const flex = document.createElement('div');
  11. flex.style.display = 'flex';
  12. flex.style.flexDirection = 'column';
  13. flex.style.rowGap = '1px';
  14. // create two, elements inside it
  15. flex.appendChild(document.createElement('div'));
  16. flex.appendChild(document.createElement('div'));
  17. // append to the DOM (needed to obtain scrollHeight)
  18. document.body.appendChild(flex);
  19. flexGapSupported = flex.scrollHeight === 1; // flex container should be 1px high from the row-gap
  20. document.body.removeChild(flex);
  21. return flexGapSupported;
  22. };

infer关键字的应用

  1. export const tuple = <T extends string[]>(...args: T) => args;
  2. // eslint-disable-next-line import/prefer-default-export
  3. export const PresetColorTypes = tuple(
  4. 'pink',
  5. 'red',
  6. 'yellow',
  7. );
  8. // T extends xx T属于xx类型吗 判断语句 如果是 E是待推断类型 如果是E[]
  9. // 下面例子中T 是['pink', 'red', 'yellow'] 所以里面的元素类型为 'pink' | 'red' | 'yellow' 所以E='pink' | 'red' | 'yellow'
  10. export type ElementOf<T> = T extends (infer E)[] ? E : T extends readonly (infer F)[] ? F : never;
  11. // 'pink' | 'red' | 'yellow'
  12. export type PresetColorType = ElementOf<typeof PresetColorTypes>;

useSyncState构建同步state

应用场景 表格选中的等keys的处理 等我看完源码来补充

useState不能保证同步 所以自己创建一个同步 使用过程无风险且需要强制更新一次
确保用的时候就是最新的状态量

  1. export default function useSyncState<T>(initialValue: T): UseSyncStateProps<T> {
  2. const ref = React.useRef<T>(initialValue);
  3. const forceUpdate = useForceUpdate();
  4. return [
  5. () => ref.current,
  6. (newValue: T) => {
  7. ref.current = newValue;
  8. // re-render
  9. forceUpdate();
  10. },
  11. ];
  12. }

usePatchElement 通过state状态量维护一个数组 完成塞入和有返回值函数可清理自身塞入的元素

  1. export default function usePatchElement(): [
  2. React.ReactElement[],
  3. (element: React.ReactElement) => Function,
  4. ] {
  5. const [elements, setElements] = React.useState<React.ReactElement[]>([]);
  6. const patchElement = React.useCallback((element: React.ReactElement) => {
  7. // append a new element to elements (and create a new ref)
  8. setElements(originElements => [...originElements, element]);
  9. // return a function that removes the new element out of elements (and create a new ref)
  10. // it works a little like useEffect
  11. return () => {
  12. setElements(originElements => originElements.filter(ele => ele !== element));
  13. };
  14. }, []);
  15. return [elements, patchElement];
  16. }

wave波浪纹组件开发

https://ant.design/components/button-cn/

常用场景是:按钮点击后 边缘有一圈类似波纹扩散的效果 功能点:

  1. 希望提供波浪扩展的高阶组件 不能直接修改用户提供的用例组件 比如提供的button,我们不能修改button本身,而是在外层包裹wave包装
  2. 动态获取按钮button组件的border颜色或者背景颜色用作波浪纹的取色
  3. button的ref指向原生button,在jsx编译下onClick都会走react事件系统,在原生的button上加一个onClick来触发波浪纹,也就是说click事件触发两套事件处理逻辑,一个是原定的handleClick,一个是原生addEventListener(‘click’, wave)来触发波浪纹效果
  4. 动画样式控制 透明度 0.2 => 0 时间间隔 2s 动画函数 cubic-bezier(0.08, 0.82, 0.17, 1)
  5. 动画样式控制 阴影宽度 0 => 6px 时间间隔 0.4s 动画函数 cubic-bezier(0.08, 0.82, 0.17, 1)
  6. 插入傀儡节点div,相对于目标标签绝对定位,然后 “left:0;top:0;bottom:0;right:0;” 该样式能在尺寸上全等于目标,动态添加style标签,内容就是此次动画的颜色什么的描述 不同按钮颜色不同
  7. 收尾 清除各种冗余设置
  1. // 1. 通过克隆来扩展组件 截取ref,拿到button原生dom对象
  2. const { children } = this.props;
  3. let ref: React.Ref<any> = this.containerRef;
  4. if (supportRef(children)) {
  5. ref = composeRef((children as any).ref, this.containerRef as any);
  6. }
  7. return cloneElement(children, { ref });
  8. // 2. 获取颜色
  9. const waveColor =
  10. getComputedStyle(node).getPropertyValue('border-top-color') || // Firefox Compatible
  11. getComputedStyle(node).getPropertyValue('border-color') ||
  12. getComputedStyle(node).getPropertyValue('background-color');
  13. // 3.onClick事件绑定
  14. node.addEventListener('click', onClick, true);
  15. // 4. 标记当前dom参与了波浪操作 便于后续取消操作
  16. const { getPrefixCls } = this.context;
  17. extraNode.className = `${getPrefixCls('')}-click-animating-node`;
  18. const attributeName = this.getAttributeName();
  19. node.setAttribute(attributeName, 'true');
  20. // 5. 设置相关的动态动画属性 用作视觉欺骗
  21. styleForPseudo = updateCSS(
  22. `
  23. [${getPrefixCls('')}-click-animating-without-extra-node='true']::after, .${getPrefixCls(
  24. '',
  25. )}-click-animating-node {
  26. --antd-wave-shadow-color: ${waveColor};
  27. }`,
  28. 'antd-wave',
  29. { csp: this.csp, attachTo: nodeBody },
  30. );
  31. // 6. 插入傀儡标签
  32. node.appendChild(extraNode);
  33. // 经过两秒后动画完成
  34. // 7. 清理现场
  35. const attributeName = this.getAttributeName();
  36. node.setAttribute(attributeName, 'false'); // edge has bug on `removeAttribute` #14466
  37. if (styleForPseudo) {
  38. styleForPseudo.innerHTML = '';
  39. }
  40. if (insertExtraNode && this.extraNode && node.contains(this.extraNode)) {
  41. node.removeChild(this.extraNode);
  42. }