文章

Higher-order component

Higher-order component

高阶组件可以做什么?

增强回调和生命周期

  1. 增强回调
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
type Base = { onClick: () => void };
export const withLoggingOnClickWithProps = <TProps extends Base>(Component: ComponentType<TProps>) => {
  // our returned component will now have additional logText prop
  return (props: TProps & { logText: string }) => {
    const onClick = () => {
      // accessing it here, as any other props
      console.log('Log on click: ', props.logText);
      props.onClick();
    };

    return <Component {...props} onClick={onClick} />;
  };
};

const Page = () => {
  return (
    <ButtonWithLoggingOnClickWithProps onClick={onClickCallback} logText="this is Page button">
      Click me
    </ButtonWithLoggingOnClickWithProps>
  );
};
  1. 在挂载时发送数据而不是点击时
1
2
3
4
5
6
7
8
9
10
11
export const withLoggingOnMount = <TProps extends unknown>(Component: ComponentType<TProps>) => {
  return (props: TProps) => {
    // no more overriding onClick, just adding normal useEffect
    useEffect(() => {
      console.log('log on mount');
    }, []);

    // just passing props intact
    return <Component {...props} />;
  };
};

拦截 DOM 事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
useEffect(() => {
  const keyPressListener = (event) => {
    // do stuff
  };

  window.addEventListener('keypress', keyPressListener);

  return () => window.removeEventListener('keypress', keyPressListener);
}, []);

export const Modal = ({ onClose }: ModalProps) => {
  const onKeyPress = (event) => event.stopPropagation();

  return <div onKeyPress={onKeyPress}>...// dialog code</div>;
};

// same as

export const withSupressKeyPress = <TProps extends unknown>(Component: ComponentType<TProps>) => {
  return (props: TProps) => {
    const onKeyPress = (event) => {
      event.stopPropagation();
    };

    return (
      <div onKeyPress={onKeyPress}>
        <Component {...props} />
      </div>
    );
  };
};

通用 React 上下文选择器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// mini-redux
export const withContextSelector = <TProps extends unknown, TValue extends unknown>(
  Component: ComponentType<TProps & Record<string, TValue>>,
  selectors: Record<string, (data: Context) => TValue>,
): Component<Record<string, TValue>> => {
  // memoising component generally for every prop
  const MemoisedComponent = React.memo(Component) as ComponentType<Record<string, TValue>>;
  return (props: TProps & Record<string, TValue>) => {
    // extracting everything from context
    const data = useFormContext();

    // mapping keys that are coming from "selectors" argument
    // to data from context
    const contextProps = Object.keys(selectors).reduce((acc, key) => {
      acc[key] = selectors[key](data);

      return acc;
    }, {});

    // spreading all props to the memoised component
    return <MemoisedComponent {...props} {...contextProps} />;
  };

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// props are injected by the higher order component below
const CountriesWithFormId = ({
  formId,
  countryName,
}: {
  formId: string,
  countryName: string,
}) => {
  console.log("Countries with selector re-render");
  return (
    <div>
      <h3>List of countries for form: {formId}</h3>
      Selected country: {countryName}
      <ul>
        <li>Australia</li>
        <li>USA</li>
      </ul>
    </div>
  );
};

// mapping props to selector functions
const CountriesWithFormIdSelector = withContextSelector(CountriesWithFormId, {
  formId: (data) => data.id,
  countryName: (data) => data.country,
});

总结

  1. 使用附加功能增强回调和 React 生命周期事件,例如发送日志记录或分析事件
  2. 拦截 DOM 事件,例如在打开模式对话框时阻止全局键盘快捷键
  3. 提取一段上下文而不导致组件中不必要的重新渲染
本文由作者按照 CC BY 4.0 进行授权