简体   繁体   English

反应多个高阶组件

[英]React Multiple Higher-Order Components

I'm just discovering the amazing benefits of using HOC in my react projects.我刚刚发现在我的 React 项目中使用 HOC 的惊人好处。

My question is there any performance hit for calling multiple HOC functions on a component?我的问题是在一个组件上调用多个 HOC 函数会影响性能吗?

Example示例

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

This will of course only render one component, however looking at my react dev tools i can see the outputted HOC components three levels deep.这当然只会render一个组件,但是查看我的 React 开发工具,我可以看到输出的 HOC 组件深度为三层。 Is this something to be wary of or is there a better approach to calling multiple HOC on a component?这是需要警惕的,还是有更好的方法来在一个组件上调用多个 HOC?

Your syntax is equivalent to doing:你的语法相当于做:

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

The performance hit will come from how these HOC are implemented.性能影响将来自这些 HOC 的实现方式。 You would probably have to look at each of them.您可能必须查看它们中的每一个。

Example:示例:

  • Theme Provider HOCs usually store a bunch of colors and variables in the React context. Theme Provider HOC 通常在 React 上下文中存储一堆颜色和变量。 So using only one at the very root of your App is enough.因此,在您的应用程序的最根部仅使用一个就足够了。
  • One could imagine that your LabelProvider simply adds an extra span before your component, in which case there is little to worry about可以想象,您的 LabelProvider 只是在您的组件之前添加了一个额外的跨度,在这种情况下,无需担心
  • StateProviders like redux usually inject props in the component just below them so you don't really have a choice but to use them whenever you need state objects.像 redux 这样的 StateProviders 通常在它们下面的组件中注入 props,所以你别无选择,只能在需要状态对象时使用它们。

In conclusion, there are no hard rules.总之,没有硬性规定。 Your main focus should be on understanding what these HOC do and to try to limit unnecessary re-renders of your app.您的主要重点应该是了解这些 HOC 的作用,并尝试限制对您的应用程序进行不必要的重新渲染。

This answer is not exactly about performance , but one thing I do when I have to use a few higher order components at once to wrap the same component is to create one main higher order component that will call all the higher order components that I need so I can just wrap in this one instead. 这个答案并不完全是关于性能的 ,但是当我必须一次使用一些高阶组件来包装相同的组件时,我做的一件事就是创建一个主要的高阶组件,它将调用我需要的所有高阶组件。我可以用这个换一下。

Creating one main higher order component makes them easier to reuse and the wrapped components more neat and easy to read. 创建一个主要的高阶组件使它们更容易重用,并且包装的组件更加整洁和易于阅读。

Look at this example: 看看这个例子:

"Main" higher order component calling all the common ones: 调用所有常见的“主”高阶组件:

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;

And then the component that is wrapped by the main one above: 然后由上面的主要组件包裹的组件:

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;

I wouldn't use that.我不会用那个。 It's complicated to understand where the props come from, when you are looking at your MyComponent component.当您查看MyComponent组件时,理解 props 的来源很复杂。 There are much more downsides using this pattern.使用这种模式还有更多的缺点。 Anyway if you decided to use HOC s use it in a right way eg无论如何,如果您决定使用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;
}

Instead of using HOC si suggest looking at render props react pattern.建议查看render props反应模式,而不是使用HOC si。 It's well explained in a Use a Render Prop!Use a Render Prop!进行了很好的解释Use a Render Prop! article by Michael Jackson (react-router creator). Michael Jackson(反应路由器创建者)的文章。

Hope it makes sense.希望这是有道理的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM