0

我正在尝试在打字稿中编写 HOC 以在用户必须等待时呈现微调器。

我从这篇文章开始。

这是WithFullScreenSpinnerHOC.ts我的打字稿代码:

import RX = require('reactxp');

const styles = {
    semiTransparentBlackBbackground: RX.Styles.createViewStyle(
        { backgroundColor: 'rgba(0, 0, 0, 0.7)', justifyContent: 'center' }
    ),
};

export interface withFullScreenSpinnerProps {
};

export interface withFullScreenSpinnerState {
    showSpinner: boolean
};

// Higher-Order component that will allow any component to display a fullscreen activity indicator
const withFullScreenSpinner = <P extends withFullScreenSpinnerProps, S extends withFullScreenSpinnerState>(
    WrappedComponent: new (props: P) => RX.Component<P, S>
) =>
    class WithFullScreenSpinner extends RX.Component<P & withFullScreenSpinnerProps, S & withFullScreenSpinnerState> {
        constructor(props) {
            super(props);

            this.state = {
                showSpinner: false
            } as S & withFullScreenSpinnerState;
        }

        render() {
            return (
                <RX.View style={{ flex: 1 }}>
                    <WrappedComponent {...this.props}>
                    </WrappedComponent>
                    {this.state.showSpinner &&
                        <RX.View
                            style={styles.semiTransparentBlackBbackground}
                        >
                            <RX.ActivityIndicator
                                size="large"
                                color="black"
                            />
                        </RX.View>}
                </RX.View>
            );
        }
    };

export default withFullScreenSpinner;

将使用此 HOC 的组件必须具有 showSpinner 状态变量。

所以我有一个登录页面,例如用这个 HOC 包装。

import WithFullScreenSpinner from './WithFullScreenSpinnerHOC'

// The state of the sign-in component (data entered by the user)
interface signInState {
  username: string,
  password: string,
  errorMessage?: string
  ready: boolean,
  session: any,
  showSpinner: boolean
};

class SignIn extends RX.Component<signInProps, signInState> {
  constructor(props) {
    super(props);

    this.state = {
      username: '',
      password: '',
      errorMessage: null,
      ready: false,
      session: null,
      showSpinner: false
    };
  }
private showSpinner() {
    this.setState((prevState, props) => {
      let newState = { ...prevState };
      newState.showSpinner = true;
      return newState;
    })
  }

  private hidepinner() {
    this.setState((prevState, props) => {
      let newState = { ...prevState };
      newState.showSpinner = false;
      return newState;
    })
  }
  // Do the Sign In using the auth API that comes through the props of the WithAuth HOC
  doSignIn = async (username: string, password: string) => {
    const { authLayer } = this.props;

    try {
      this.showSpinner();
      const user = await authLayer.signIn(username, password);

      const requireMFA = (user.Session !== null);
      this.setState((prevState, props) => {
        let newState = { ...prevState };
        newState.showMFAPrompt = requireMFA;
        newState.session = user.session;
        return newState;
      });
      // Should be null if MFA enabled
      return user.signInUserSession;
    }
    catch (err) {
      console.log(err);
      this.setState((prevState, props) => {
        let newState = { ...prevState };
        newState.errorMessage = err.message;
        return newState;
      });
    }
    finally {
      this.hidepinner();
    }
  }

render() {
    return (

 );
  }
};
export default WithFullScreenSpinner(SignIn);

但是我在这里遗漏了一些东西,因为更改包装组件中的状态不会触发 HOC 渲染方法。

我在 HOC 的 render 方法中设置了一个断点,以确认它只被调用一次。

任何帮助表示赞赏。

编辑1:这是设计使然,组件不相关,因此我需要一种相互通信的方式。如果可行,我将尝试Resub并回答我自己的问题。

编辑 2:我在下面发布了我的解决方案。我还没有接受它作为正确答案,因为我想提出建议/意见。

4

1 回答 1

0

我找到的解决方案是使用Resub

我已经定义了一个存储微调器的可见性值的商店:

import { autoSubscribe, AutoSubscribeStore, StoreBase } from "resub";

    // Resub Store that will allow any component to communicate with the FullScreenSpinner HOC
    // https://github.com/Microsoft/ReSub

    @AutoSubscribeStore
    class SpinnerStateStore extends StoreBase {
        private visible: boolean = false;

        // Call this from the Wrapped Component to ask for the spinner to be shown/hidden
        public toggleVisibility(visible: boolean) {
            this.visible = visible;
            // Do not forget this call otherwise autosubscriptions won't be called
            this.trigger();
        }

        // Reserved for the FullScreenSpinnerHOC
        @autoSubscribe
        public isVisible() {
            return this.visible;
        }
    }

    export = new SpinnerStateStore();

HOC 必须扩展 ComponentBase 而不是 RX.Component。问题中的代码变为:

import RX = require("reactxp");
import ComponentBase from "resub/dist/ComponentBase";
import WithFullScreenSpinnerStateStore = require("./WithFullScreenSpinnerStore");

const styles = {
    semiTransparentBlackBbackground: RX.Styles.createViewStyle(
        { backgroundColor: "rgba(0, 0, 0, 0.7)", justifyContent: "center" },
    ),
};

// tslint:disable-next-line:no-empty-interface
export interface IWithFullScreenSpinnerProps {
}

export interface IWithFullScreenSpinnerState {
    pendingIO: boolean;
}

// Higher-Order component that will allow any component to display a fullscreen activity indicator
const withFullScreenSpinner = <P extends IWithFullScreenSpinnerProps, S extends IWithFullScreenSpinnerState>(
    WrappedComponent: new (props: P) => RX.Component<P, S>,
) =>
    // tslint:disable-next-line:max-line-length
    class WithFullScreenSpinner extends ComponentBase<P & IWithFullScreenSpinnerProps, S & IWithFullScreenSpinnerState> {
        constructor(props) {
            super(props);

            this.state = {
                pendingIO: false,
            } as S & IWithFullScreenSpinnerState;
        }

        // tslint:disable-next-line:max-line-length
        protected _buildState(props: P & IWithFullScreenSpinnerProps, initialBuild: boolean): S & IWithFullScreenSpinnerState {
            return {
                pendingIO: WithFullScreenSpinnerStateStore.isVisible(),
            } as S & IWithFullScreenSpinnerState;
        }

        // tslint:disable-next-line:member-ordering
        public render() {
            return (
                <RX.View style={{ flex: 1 }}>
                    <WrappedComponent {...this.props}>
                    </WrappedComponent>
                    {this.state.pendingIO &&
                        <RX.View
                            style={styles.semiTransparentBlackBbackground}
                        >
                            <RX.ActivityIndicator
                                size="large"
                                color="black"
                            />
                        </RX.View>}
                </RX.View>
            );
        }
    };

export default withFullScreenSpinner;

这是有效的,因为每次你调用SpinnerStateStore.toggleVisibility()WithFullScreenSpinner._buildState()都会被调用——作为this.trigger(); 的结果。

于 2018-04-09T08:38:02.410 回答