如何使用 redux 状态更新 React HOC 组件?

How to update react HOC component with redux state?

下面是 HOC,它也连接到 redux store。 WrappedComponent 函数不会在存储数据更改时获取 redux 状态。这里可能有什么问题?

export function withCreateHOC<ChildProps>(
  ChildComponent: ComponentType,
  options: WithCreateButtonHOCOptions = {
    title: 'Create',
  },
) {
  function WrappedComponent(props: any) {
    const { createComponent, title } = options;
    const [isOpen, setisOpen] = useState(false);

    function onCreateClick() {
      setisOpen(!isOpen);
      Util.prevDefault(() => setisOpen(isOpen));
    }

    return (
      <div>
        <ChildComponent {...props} />
        <div>
          <Component.Button
            key={'add'}
            big={true}
            round={true}
            primary={true}
            onClick={Util.prevDefault(onCreateClick)}
            className={'float-right'}
            tooltip={title}
          >
            <Component.Icon material={'add'} />
          </Component.Button>
        </div>
        <OpenDrawerWithClose
          open={isOpen}
          title={title}
          setisOpen={setisOpen}
          createComponent={createComponent}
        />
      </div>
    );
  }

  function mapStateToProps(state: any) {
    console.log('HOC mapStateToProps isOpen', state.isOpen);
    return {
      isOpen: state.isOpen,
    };
  }
  // Redux connected;
  return connect(mapStateToProps, {})(WrappedComponent);
}

期望从 ReduxStore 使用 isOpen 并在此处使用 WrappedComponent 更新相同的内容。有什么机会应该将其更改为 class 组件?

以上HOC用作:

export const Page = withCreateHOC(
  PageItems,
  {
    createComponent: <SomeOtherComponent />,
    title: 'Create',
  },
);

概览

您不希望 isOpen 成为 WrappedComponent 的地方州。这个 HOC 的重点是从你的 redux store 访问 isOpen。请注意,在此代码中的任何地方都没有更改 redux 状态的值。您想放弃本地状态,从 redux 访问 isOpen,并 dispatch 在 redux 中更改 isOpen 的操作。

此外,我们必须用实际类型替换其中一些 any

我似乎有点怀疑您传递的是已解析的 JSX 元素,而不是作为 createComponent 的可调用组件(<SomeOtherComponent /> vs SomeOtherComponent),但这是否正确或错误取决于您的 OpenDrawerWithClose 组件中的内容。我假设这里写的是正确的。

从技术上讲,使用 connect 没有任何问题,但在 HOC 内部使用 HOC 感觉有点奇怪,所以我将使用钩子 useSelectoruseDispatch .

循序渐进

我们想要创建一个函数,它接受一个组件 ComponentType<ChildProps> 和一些选项 WithCreateButtonHOCOptions。您正在为 options.title 提供默认值,因此我们可以将其设为可选。 options.createComponent 是可选的还是必需的?

interface WithCreateButtonHOCOptions {
    title: string;
    createComponent: React.ReactNode;
}
function withCreateHOC<ChildProps>(
  ChildComponent: ComponentType<ChildProps>,
  options: Partial<WithCreateButtonHOCOptions>
) {

我们 return 一个采用相同属性的函数,但没有 isOpentoggleOpen,如果这些是 ChildProps 的属性。

return function (props: Omit<ChildProps, 'isOpen' | 'toggleOpen'>) {

我们需要在解构步骤中为 options 设置默认值,以便只设置一个 属性。

const { createComponent, title = 'Create' } = options;

我们从 redux 状态访问 isOpen

const isOpen = useSelector((state: { isOpen: boolean }) => state.isOpen);

我们创建了一个回调,将一个动作分派给 redux——您需要在您的 reducer 中处理它。我正在调度一个原始动作对象 {type: 'TOGGLE_OPEN'},但您可以为此创建一个动作创建器函数。

const dispatch = useDispatch();
const toggleOpen = () => {
    dispatch({type: 'TOGGLE_OPEN'});
}

我们将把 isOpentoggleOpen 这两个值作为 props 传递给 ChildComponent 以防它想使用它们。但更重要的是,我们可以将它们用作按钮和抽屉组件上的点击处理程序。 (注意:看起来抽屉想要一个带有 boolean 的道具 setIsOpen,因此您可能需要稍微调整一下。如果抽屉仅在 isOpen 为 [=48 时显示=] 那么只需切换就可以了)。

代码

function withCreateHOC<ChildProps>(
    ChildComponent: ComponentType<ChildProps>,
    options: Partial<WithCreateButtonHOCOptions>
) {
    return function (props: Omit<ChildProps, 'isOpen' | 'toggleOpen'>) {
        const { createComponent, title = 'Create' } = options;

        const isOpen = useSelector((state: { isOpen: boolean }) => state.isOpen);

        const dispatch = useDispatch();
        const toggleOpen = () => {
            dispatch({ type: 'TOGGLE_OPEN' });
        }

        return (
            <div>
                <ChildComponent
                    {...props as ChildProps}
                    toggleOpen={toggleOpen}
                    isOpen={isOpen}
                />
                <div>
                    <Component.Button
                        key={'add'}
                        big={true}
                        round={true}
                        primary={true}
                        onClick={toggleOpen}
                        className={'float-right'}
                        tooltip={title}
                    >
                        <Component.Icon material={'add'} />
                    </Component.Button>
                </div>
                <OpenDrawerWithClose
                    open={isOpen}
                    title={title}
                    setisOpen={toggleOpen}
                    createComponent={createComponent}
                />
            </div>
        );
    }
}

这个版本稍微好一点,因为它没有 as ChildProps 断言。我不想过多地关注“为什么”,但基本上我们需要坚持,如果 ChildProps 采用 isOpentoggleOpen 道具,这些道具必须具有相同的类型正如我们提供的那样。

interface AddedProps {
    isOpen: boolean;
    toggleOpen: () => void;
}

function withCreateHOC<ChildProps>(
    ChildComponent: ComponentType<Omit<ChildProps, keyof AddedProps> & AddedProps>,
    options: Partial<WithCreateButtonHOCOptions>
) {
    return function (props: Omit<ChildProps, keyof AddedProps>) {
        const { createComponent, title = 'Create' } = options;

        const isOpen = useSelector((state: { isOpen: boolean }) => state.isOpen);

        const dispatch = useDispatch();
        const toggleOpen = () => {
            dispatch({ type: 'TOGGLE_OPEN' });
        }

        return (
            <div>
                <ChildComponent
                    {...props}
                    toggleOpen={toggleOpen}
                    isOpen={isOpen}
                />
                <div>
                    <Component.Button
                        key={'add'}
                        big={true}
                        round={true}
                        primary={true}
                        onClick={toggleOpen}
                        className={'float-right'}
                        tooltip={title}
                    >
                        <Component.Icon material={'add'} />
                    </Component.Button>
                </div>
                <OpenDrawerWithClose
                    open={isOpen}
                    title={title}
                    setisOpen={toggleOpen}
                    createComponent={createComponent}
                />
            </div>
        );
    }
}

Playground Link