React 高阶组件

2017年9月4日

本文写于 2017 年,示例基于 class 组件与当时的 React API;Hooks 普及后更常见的是自定义 Hook,但 HOC 思路仍有参考价值。

React 高阶组件简单来说,就是一个 function 接受一个 component 作为参数,最后返回一个新的组件,高阶组件的概念来自于 JavaScript 中的高阶函数。

高阶函数

因为函数在 JavaScript 中为一等公民(first-class functions),所以函数能作为参数,或者返回值。一个函数如果接受函数作为参数,或者返回值为函数,则称为高阶函数,常见的 JavaScript 高阶函数有 Array.prototype.filterArray.prototype.mapArray.prototype.reduce 等。

123[1, 2, 3, 4, 5].reduce(function (prev, curr, index, wholeArr) {  return prev + curr;}, 0);

Array.prototype.reduce 中,第一个参数为一个函数,这个函数接受四个参数,第一个为上次运算的结果,第二个为当前循环的元素,第三个为当前循环的 index,第四个则为整个数组。

1234567891011function record() {  const startTime = new Date().getTime();  return function () {    const now = new Date().getTime();    return now - startTime;  };}const startRecord = record();setTimeout(() => console.log(startRecord()), 50); // 50setTimeout(() => console.log(startRecord()), 100); // 100

函数 record 中返回了另外一个匿名函数,record 函数执行以后,再次执行 startRecord 都会返回与一次函数执行的时间间隔。

高阶组件

高阶组件类似于高阶函数,只是函数改为了 React 组件(实际上 React 组件也是一个函数)。例如我们要实现一个普通的倒计时组件

12345678910111213141516171819202122232425262728293031323334353637class Countdown extends React.Component {  constructor(props) {    super(props);    this.state = {      remaining: props.remaining,    };    this.countdown = undefined;  }  componentDidMount() {    this.countdown = setInterval(this.tick.bind(this), 1000);  }  tick() {    if (this.state.remaining <= 0) {      clearInterval(this.countdown);      this.countdown = undefined;      return undefined;    }    this.setState({ remaining: this.state.remaining - 1 });  }  render() {    let totalSeconds = this.state.remaining;    const hours = Math.floor(totalSeconds / 3600);    totalSeconds %= 3600;    const minutes = Math.floor(totalSeconds / 60);    const seconds = totalSeconds % 60;    return (      <time>        <span>{hours}</span>        <span>{minutes}</span>        <span>{seconds}</span>      </time>    );  }}

上面的组件根据 remaining 属性,计算出倒计时的时、分、秒,但如果我们需要一个更抽象的组件,并不需要输出固定的 DOM 结构,这时候我们就可以借助高阶组件。

1234567891011121314151617181920212223242526272829303132333435function withCountdown(WrapperComponent) {  return class extends React.Component {    constructor(props) {      super(props);      this.state = {        remaining: props.remaining,      };      this.countdown = undefined;    }    componentDidMount() {      this.countdown = setInterval(this.tick.bind(this), 1000);    }    tick() {      if (this.state.remaining <= 0) {        clearInterval(this.countdown);        this.countdown = undefined;        return undefined;      }      this.setState({ remaining: this.state.remaining - 1 });    }    render() {      let totalSeconds = this.state.remaining;      const hours = Math.floor(totalSeconds / 3600);      totalSeconds %= 3600;      const minutes = Math.floor(totalSeconds / 60);      const seconds = totalSeconds % 60;      return (        <WrapperComponent hours={hours} minutes={minutes} seconds={seconds} />      );    }  };}

调用方式

1234567891011function Timer({ hours, minutes, seconds }) {  return (    <time>      <span>{hours}</span>      <span>{minutes}</span>      <span>{seconds}</span>    </time>  );}const Countdown = withCountdown(Timer);

评论

0 条讨论

匿名评论需填写昵称、邮箱,并通过人机验证。邮箱不会公开展示,提交后需审核通过才会显示。

还没有评论,来抢沙发吧。