簡體   English   中英

反應多個高階組件

[英]React Multiple Higher-Order Components

我剛剛發現在我的 React 項目中使用 HOC 的驚人好處。

我的問題是在一個組件上調用多個 HOC 函數會影響性能嗎?

示例

export default withState(withLabel(withTheme(MyComponent)))

這當然只會render一個組件,但是查看我的 React 開發工具,我可以看到輸出的 HOC 組件深度為三層。 這是需要警惕的,還是有更好的方法來在一個組件上調用多個 HOC?

你的語法相當於做:

<StateProvider>
  <LabelProvider>
    <ThemeProvider>
      <MyComponent />
    </ThemeProvider>
  </LabelProvider>
</StateProvider>

性能影響將來自這些 HOC 的實現方式。 您可能必須查看它們中的每一個。

示例:

  • Theme Provider HOC 通常在 React 上下文中存儲一堆顏色和變量。 因此,在您的應用程序的最根部僅使用一個就足夠了。
  • 可以想象,您的 LabelProvider 只是在您的組件之前添加了一個額外的跨度,在這種情況下,無需擔心
  • 像 redux 這樣的 StateProviders 通常在它們下面的組件中注入 props,所以你別無選擇,只能在需要狀態對象時使用它們。

總之,沒有硬性規定。 您的主要重點應該是了解這些 HOC 的作用,並嘗試限制對您的應用程序進行不必要的重新渲染。

這個答案並不完全是關於性能的 ,但是當我必須一次使用一些高階組件來包裝相同的組件時,我做的一件事就是創建一個主要的高階組件,它將調用我需要的所有高階組件。我可以用這個換一下。

創建一個主要的高階組件使它們更容易重用,並且包裝的組件更加整潔和易於閱讀。

看看這個例子:

調用所有常見的“主”高階組件:

import React from 'react';
import { connect } from 'react-redux';
import { withRouter } from 'react-router';
import { withStyles, createStyles, Theme, WithStyles } from '@material-ui/core/styles';
import withSnack, { Snack } from '../CommonComponents/SnackBar/WithSnack';
import withCancelable, { Cancelable } from '../CommonComponents/WithCancelable';

interface Props {
  snack: Snack;
  cancelable: Cancelable;
  history: any;
};

type WithCommon =
  (TargetComponent: React.ComponentClass, componentsToInclude?: string[], styles?: any, mapStateToProps?: any, mapDispatchToProps?: any) => React.ComponentClass<Props, any>;

const withCommon = (
  TargetComponent: any,
  componentsToInclude: string[] | undefined = undefined,
  styles: any | undefined = undefined,
  mapStateToProps: any | undefined = undefined,
  mapDispatchToProps: any | undefined = undefined
): WithCommon => {

  const withCommonComponent = (props: Props) => (<TargetComponent {...props} />)
    let resultComponent: any = withCommonComponent;

    if (mapStateToProps || mapDispatchToProps) {
      resultComponent = connect(
        mapStateToProps,
        mapDispatchToProps
      )(withCommonComponent);
    }

    if (styles) {
      resultComponent = withStyles(styles)(resultComponent);
    }

    if (componentsToInclude) { // "extra" components to wrap the target
      if (componentsToInclude.length) {
        if (componentsToInclude.some(c => c === 'router')) {
          resultComponent = withRouter(resultComponent);
        }
        if (componentsToInclude.some(c => c === 'snack')) {
          resultComponent = withSnack(resultComponent);
        }
        if (componentsToInclude.some(c => c === 'cancelable')) {
          resultComponent = withCancelable(resultComponent);
        }
      } else { // if array is empty then include them all (avoids typing the same ones every time you need to have them all)
        resultComponent = withCancelable(withSnack(withRouter(resultComponent)));
      }
    }

    return resultComponent;
};

export interface Common extends WithStyles {
  snack: Snack,
  cancelable: Cancelable,
  history: any,
};

export default withCommon;

然后由上面的主要組件包裹的組件:

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Avatar from '@material-ui/core/Avatar';
import LockOutlinedIcon from '@material-ui/icons/LockOutlined';
import Paper from '@material-ui/core/Paper';
import Typography from '@material-ui/core/Typography';
import Dialog from '@material-ui/core/Dialog';
import { createStyles, Theme } from '@material-ui/core/styles';
import TextFieldOutlined from '../CommonComponents/Form/TextFieldOutlined';
import { hideSignin } from '../Redux/ActionCreators'
import Auth from '../Auth/Auth';
import Form from '../CommonComponents/Form/Form';
import Loading from '../CommonComponents/Loading';
import withCommon, { Common } from '../CommonComponents/WithCommon';

const styles = (theme: Theme) =>
  createStyles({...});

interface Props extends Common {
  show: boolean;
  hideSignin(): void;
};

interface State {...}

class SignIn extends Component<Props, State> {
  constructor(props: Props) {
    super(props);

    this.handleEmailchange = this.handleEmailchange.bind(this);
    this.handlePasswordchange = this.handlePasswordchange.bind(this);
    this.signin = this.signin.bind(this);
  }

  signin(e: any): void {
    if (!e.currentTarget.form.reportValidity()) {
      return;
    }

    e.preventDefault();

    this.setState({ loading: true });

    Auth.signin(
      this.state.email,
      this.state.password,
      (promise: Promise<any>) => this.props.cancelable.setCancelable('signin', promise),
    )
    .then(() => {
      this.props.history.push('/');
    })
    .catch((err: any) => {
      this.props.snack.error(err.message);
    })
    .finally(() => {
      this.setState({ loading: false });
    });
  }

  render() {
    const { classes } = this.props;

    return (
      <Dialog
        open={this.props.show}
        onClose={() => this.props.hideSignin()}
      >
        <Paper className={classes.paper}>
          <Loading loading={this.state.loading} />
          <Avatar className={classes.avatar}>
            <LockOutlinedIcon />
          </Avatar>
          <Typography component="h1" variant="h5" className={classes.label}>
            Sign in
          </Typography>
          <Form submitButtonText="Sign in" onSubmit={this.signin}>
            <TextFieldOutlined required label="Email Address" type="email" onChange={this.handleEmailchange} focused />
            <TextFieldOutlined required label="Password" type="password" onChange={this.handlePasswordchange} />
          </Form>
        </Paper>
      </Dialog>
    );
  }
}

(SignIn as React.ComponentClass<Props, State>).propTypes = {
  classes: PropTypes.object.isRequired,
} as any;

const mapStateToProps = (state: any) => {
  return {
    show: state.signin.show,
  };
}

const mapDispatchToProps = (dispatch: any) => {
  return {
    hideSignin: () => dispatch(hideSignin()),
  }
}

// Empty array will make it include all the "extra" higher order components
export default withCommon(SignIn, [], styles, mapStateToProps, mapDispatchToProps) as any;

// Or you could specify which extra components you want to use:
// export default withCommon(SignIn, ['router', 'cancelable'], styles, mapStateToProps, mapDispatchToProps) as any;

// Or some nulls if you just want to connect the store:
// export default withCommon(SignIn, null, null, mapStateToProps, mapDispatchToProps) as any;

我不會用那個。 當您查看MyComponent組件時,理解 props 的來源很復雜。 使用這種模式還有更多的缺點。 無論如何,如果您決定使用HOC以正確的方式使用它,例如

const withDetails = Component => {
  const C = props => {
    // do something
  }

  // assign display & wrapped names - easier to debug
  C.displayName = `withRouter(${Component.displayName))`
  C.WrappedComponent = Component;

  return C;
}

建議查看render props反應模式,而不是使用HOC si。 Use a Render Prop!進行了很好的解釋Use a Render Prop! Michael Jackson(反應路由器創建者)的文章。

希望這是有道理的。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM