Web开发编程网
分享Web开发相关技术

一文详解 React 组件类型

本文的目标是让开发者清晰地了解 React 组件类型,哪些在现代 React 应用中依然在使用,以及为何一些类型现在不再使用了。

尽管React从2013年发布到现在并没有引入太多重大改变,但不同类型的React组件也出现了不少。一些组件类型和组件设计模式今天依然在使用,它们已成了构建React应用程序的标准,而另一些类型的组件只会在旧的应用和新手教学中出现。

在这篇文章中,我想通过层次化的方式向初学React的人介绍一下不同的React组件和React设计模式。读完本文后,你应当能够从旧的应用和入门文章中分辨出不同类型的React组件,并能够自信地编写自己的现代React组件。

React createClass组件

我们要从React的createClass组件说起。createClass方法为开发者提供了一个工厂方法,无需编写JavaScript类就可以创建React类组件。在JavaScript ES6出现之前,这种方法是创建React组件的标准方法,因为在JavaScript ES5时代还无法使用类语法:

var App = React.createClass({
  getInitialStatefunction() {
    return {
      value'',
    };
  },

  onChangefunction(event) {
    this.setState({ value: event.target.value });
  },

  renderfunction() {
    return (
      <div>
        <h1>Hello React "createClass" Component!</h1>

        <input
          value={this.state.value}
          type="text"
          onChange={this.onChange}
        />

        <p>{this.state.value}</p>
      </div>
    );
  },
});

createClass()工厂方法接受一个对象,该对象定义了React组件中的方法。其中,getInitialState()方法用来设置React组件的初始状态,还有必须的render()方法用来显示JSX形式的组件。给对象传递更多函数即可添加更多的“方法”(如onChange())。

还可以使用生命周期方法来管理副作用。例如,为了随时将输入框中的值保存到浏览器的local storage中,可以利用componentDidUpdate()这个生命周期方法,只需要将该函数传递给工厂函数的对象即可。而且,local storage中的值也可以在组件接收初始状态的时候读出来:

var App = React.createClass({
  getInitialStatefunction() {
    return {
      value: localStorage.getItem('myValueInLocalStorage') || '',
    };
  },

  componentDidUpdatefunction() {
    localStorage.setItem('myValueInLocalStorage'this.state.value);
  },

  onChangefunction(event) {
    this.setState({ value: event.target.value });
  },

  renderfunction() {
    return (
      <div>
        <h1>Hello React "createClass" Component!</h1>

        <input
          value={this.state.value}
          type="text"
          onChange={this.onChange}
        />

        <p>{this.state.value}</p>
      </div>
    );
  },
});

每次重新加载或刷新浏览器时,之前在输入框中输入过的、保存在local storage中的初始状态就会在组件初次mount的时候显示出来。

注意:React的createClass方法现在已经不在React的核心包中了。如果你想尝试下,就必须要安装另一个包:npm install create-react-class。

React Mixin

React Mixin是在React提出可重用组件逻辑的高级设计方式时加入的。利用Mixin可以将React组件中的逻辑提取出来作为独立的对象使用。在使用Mixin对象时,Mixin中的所有功能都会被引入到组件中:

var localStorageMixin = {
  getInitialStatefunction() {
    return {
      value: localStorage.getItem('myValueInLocalStorage') || '',
    };
  },

  setLocalStoragefunction(value) {
    localStorage.setItem('myValueInLocalStorage', value);
  },
};

var App = React.createClass({
  mixins: [localStorageMixin],

  componentDidUpdatefunction() {
    this.setLocalStorage(this.state.value);
  },

  onChangefunction(event) {
    this.setState({ value: event.target.value });
  },

  renderfunction() {
    return (
      <div>
        <h1>Hello React "createClass" Component with Mixin!</h1>

        <input
          value={this.state.value}
          type="text"
          onChange={this.onChange}
        />

        <p>{this.state.value}</p>
      </div>
    );
  },
});

在这个例子中,Mixin提供了组件的初始状态,而该初始状态是从local storage中读取的,并且还利用setLocalStorage()扩展原来的组件,该函数之后会在组件中被调用。为了让Mixin更灵活,我们可以使用一个返回函数的对象:

function getLocalStorageMixin(localStorageKey) {
  return {
    getInitialStatefunction() {
      return { value: localStorage.getItem(localStorageKey) || '' };
    },

    setLocalStoragefunction(value) {
      localStorage.setItem(localStorageKey, value);
    },
  };
}

var App = React.createClass({
  mixins: [getLocalStorageMixin('myValueInLocalStorage')],

  ...
});

不过,现代React应用程序已经不再使用Mixin了,因为它们会带来一些负面作用。关于Mixin的细节和消亡过程可以阅读这里(https://reactjs.org/blog/2016/07/13/mixins-considered-harmful.html)

React类组件

React类组件是在JavaScript ES6时引入的,因为直到ES6才支持JS类。有时候它们也被称为React ES6类组件。至少有了JavaScript ES6之后,就不需要使用React的createClass方法了。JS自己终于支持类了:

class App extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      value'',
    };

    this.onChange = this.onChange.bind(this);
  }

  onChange(event) {
    this.setState({ value: event.target.value });
  }

  render() {
    return (
      <div>
        <h1>Hello React ES6 Class Component!</h1>

        <input
          value={this.state.value}
          type="text"
          onChange={this.onChange}
        />

        <p>{this.state.value}</p>
      </div>
    );
  }
}

使用JavaScript类编写的React Component有个类似于类构造器的方法,主要用于让React设置初始状态,或者绑定方法。还有必须的render方法用于返回JSX的输出。React组件的所有内部逻辑都通过类组件定义中的面向对象继承,从extends React.Component获得。但是,除了这种用法之外,我并不推荐进一步使用类继承,相反,应当主要使用类组合(composition)。

注意:利用JavaScript类定义React组件时还可以使用了另一种语法,通过JavaScript ES6的箭头函数来自动绑定React组件中的方法:

class App extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      value'',
    };
  }

  onChange = event => {
    this.setState({ value: event.target.value });
  };

  render() {
    return (
      <div>
        <h1>Hello React ES6 Class Component!</h1>

        <input
          value={this.state.value}
          type="text"
          onChange={this.onChange}
        />

        <p>{this.state.value}</p>
      </div>
    );
  }
}

React类组件提供几个生命周期方法,用于mount、update和unmount等。比如前面的local storage的例子,可以在生命周期方法中以副作用的方式来执行这些操作——即,将输入框中的最新值保存到local storage中,而在构造函数中可以根据local storage的值来设置初始状态:

class App extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      value: localStorage.getItem('myValueInLocalStorage') || '',
    };
  }

  componentDidUpdate() {
    localStorage.setItem('myValueInLocalStorage'this.state.value);
  }

  onChange = event => {
    this.setState({ value: event.target.value });
  };

  render() {
    return (
      <div>
        <h1>Hello React ES6 Class Component!</h1>

        <input
          value={this.state.value}
          type="text"
          onChange={this.onChange}
        />

        <p>{this.state.value}</p>
      </div>
    );
  }
}

利用this.state、this.setState()和生命周期方法,React类组件中的状态管理和副作用可以写在一起。React类组件到现在依然在广泛使用,尽管后文即将介绍的React函数组件在现代React应用程序中得到了更广泛的应用,因为函数组件已经不逊于类组件了。

React高阶组件

React高阶组件(Higher-Order Components,简称 HOC)是一种React的高级设计模式,是替代Mixin的另一种在组件间复用逻辑的方法。如果你没听说过HOC,可以读一读我的另一篇入门文章:高阶组件(https://www.robinwieruch.de/gentle-introduction-higher-order-components/)。简单来说,高阶组件就是接收一个组件作为输入,然后输出另一个组件(并扩展其功能)的组件。我们利用前面的例子来看看,怎样才能将功能提取到可复用的高阶组件中。

const withLocalStorage = localStorageKey => Component =>
  class WithLocalStorage extends React.Component {
    constructor(props) {
      super(props);

      this.state = {
        [localStorageKey]: localStorage.getItem(localStorageKey),
      };
    }

    setLocalStorage = value => {
      localStorage.setItem(localStorageKey, value);
    };

    render() {
      return (
        <Component
          {...this.state}
          {...this.props}
          setLocalStorage={this.setLocalStorage}
        />
      );
    }
  };

class App extends React.Component {
  constructor(props) {
    super(props);

    this.state = { value: this.props['myValueInLocalStorage'] || '' };
  }

  componentDidUpdate() {
    this.props.setLocalStorage(this.state.value);
  }

  onChange = event => {
    this.setState({ value: event.target.value });
  };

  render() {
    return (
      <div>
        <h1>
          Hello React ES6 Class Component with Higher-Order Component!
        </h1>

        <input
          value={this.state.value}
          type="text"
          onChange={this.onChange}
        />

        <p>{this.state.value}</p>
      </div>
    );
  }
}

const AppWithLocalStorage = withLocalStorage('myValueInLocalStorage')(App);

另一个高级React设计模式就是React Render Prop组件,通常代替React高阶组件使用。我不在给出这种抽象的例子,更多内容请查看网上的一些教程。

React高阶组件和React Render Prop组件在今天都在被广泛使用,尽管React函数组件和React钩子(后文会介绍)也许对于React组件的抽象更好。不过,高阶组件和Render Prop也可以用在函数组件上。

React函数组件

React函数组件等价于React类组件,但它表现为一个函数,而不是一个类。过去函数组件没有状态也无法使用副作用,因此它们被称为“无状态函数组件”,但自从React钩子出现后,函数组件就复活了。

React钩子给函数组件带来了状态和副作用。React不仅带有各种内置的钩子,还允许创建自定义的钩子。我们来看看前面的类组件的例子怎样改写成函数组件:

const App = () => {
  const [value, setValue] = React.useState('');

  const onChange = event => setValue(event.target.value);

  return (
    <div>
      <h1>Hello React Function Component!</h1>

      <input value={value} type="text" onChange={onChange} />

      <p>{value}</p>
    </div>
  );
};

这段代码仅在输入框上演示了函数组件。由于要捕获输入框的值,就需要使用组件状态,因此这里用到了内置的React.useState钩子。

React钩子还可以在函数组件中实现副作用。一般来说,内置的useEffect钩子可以用来在任何props或state发生变化时执行一个函数:

const App = () => {
  const [value, setValue] = React.useState(
    localStorage.getItem('myValueInLocalStorage') || '',
  );

  React.useEffect(() => {
    localStorage.setItem('myValueInLocalStorage', value);
  }, [value]);

  const onChange = event => setValue(event.target.value);

  return (
    <div>
      <h1>Hello React Function Component!</h1>

      <input value={value} type="text" onChange={onChange} />

      <p>{value}</p>
    </div>
  );
};

这段代码演示了useEffect钩子的用法,每次状态中的输入框的值改变时,该钩子就会被执行。当提供给useEffect钩子的函数被执行时,它会利用最新的值更新local storage中的值。此外,函数组件的初始状态也可以使用useState钩子从local storage中读取。

最后一点,我们可以将讲个钩子提取出来,封装成一个自定义钩子,这样可以保证组件状态永远和local storage同步。它在最后会返回一个值和setter函数,供函数组件使用:

const useStateWithLocalStorage = localStorageKey => {
  const [value, setValue] = React.useState(
    localStorage.getItem(localStorageKey) || '',
  );

  React.useEffect(() => {
    localStorage.setItem(localStorageKey, value);
  }, [value]);

  return [value, setValue];
};

const App = () => {
  const [value, setValue] = useStateWithLocalStorage(
    'myValueInLocalStorage',
  );

  const onChange = event => setValue(event.target.value);

  return (
    <div>
      <h1>Hello React Function Component!</h1>

      <input value={value} type="text" onChange={onChange} />

      <p>{value}</p>
    </div>
  );
};

由于这段代码是从函数组件中提取出来的,它可以用于任何其他组件,以实现业务逻辑的复用。它与Mixin、高阶组件和Render Prop组件一样都是高级设计模式。但是需要指出的是,React的函数组件也可以用高阶组件和Render Prop组件来增强。

React函数组件、钩子和类组件是目前编写现代React应用程序的标准。但是,我坚信以后函数组件和钩子将取代类组件。届时,类组件也许只会出现在旧的应用程序和教程中,就像今天的 createClass组件和Mixin一样。高阶组件和Render Prop组件也同理,它们也会被钩子取代。

写在最后

所有React的组件在Pros的用法方面的理念都是一样的,都是将信息沿着组件树向下传递。但是,类组件和函数组件对于状态和副作用的用法是不同的,还有生命周期方法和钩子。

这篇文章介绍了所有不同种类的React组件及其用法,以及它们在历史中的位置。最后总结一下,现在使用类组件、函数组件和钩子、高阶组件和Render Prop组件等高级概念是完全没问题的。但是也应当了解到,旧的React应用程序和教程也会使用一些只有以前才使用的旧组件和设计模式。

原文:https://www.robinwieruch.de/react-component-types/

未经允许不得转载:WEB开发编程网 » 一文详解 React 组件类型
微信扫码关注微信公众号

WEB开发编程网

谢谢支持,我们一直在努力

安全提示:您正在对WEB开发编程网进行赞赏操作,一但支付,不可返还。