使用React Hooks代替class Component的六个原因

这是一篇译文,如果你想直接看原文,请点击 -> 原文链接


React Hooks已经发布一段时间了,有些开发者还没开始使用它们,我认为主要有两个原因:

  1. 很多React开发者正在开发一些大型应用,如果进行迁移需要话费大量的时间;
  2. 大多数人已经熟悉了Class Component 的写法,继续使用是一个很简单的方式。

在这篇文章里,我将列举六个使用React Hooks的好处

1. 当一个函数组件变大,你不需要对他进行Class Component 的重构


一般来说,我们在拆分页面时,会抽离一些组件,其中部分只依赖于props,也就是我们常说的木偶组件,随着功能的增多,这个木偶组建可能需引入state,这时候,我们就不得不把它重构为一个Class Component。

而对于Hooks而言,功能组件也有能力接入state,类似的重构就会变得很小。比如以下这个用来展示数字的木偶组件:

export function(props) {
  return (
    <div>Count: {props.count}</div>
  )
}


此时,新增了一个需求,需要每次鼠标点击后对count进行+1操作,并且这个count只在当前组件中被改变,不影响其他组件,这时,我们就需要将state引入,以下是基于Class Component的实现:

class Counter extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      count: 0
    }
  }
  componentDidMount() {
    this.setState({ count: this.props.count })
  }

  render() {
    return (
      <div>Count: {this.state.count}</div>
    )
  }
}


如果我们用Hooks来做同样的事情:

const Counter = (props) => {
  const [count, setCount] = useState(0);
  useEffect(() => {
    setCount(props.count)
  }, [])
  return (
    <div>Count: {count}</div>
  )
}

2. 你不需要再担心this

Classes confuse both people and machines Classes 让人和机器都很困扰


上面这句话来自React 官方文档,其中一个困扰来自this关键字,如果你熟悉Javascript,你就知道在Javascript中,this的作用和其他语言this的作用不同,当你开始使用Hooks,则不需要再为this而感到困扰,这对于初学者和有经验的开发者都很友好。

image.png

3. 不需要再使用bind


继续为刚才的组件添加上点击增加计数的功能:

class Counter extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      count: 0
    }
    this.handleClickEvent = this.handleClickEvent.bind(this)
  }
  
  componentDidMount() {
    this.setState({ count: this.props.count })
  }

  handleClickEvent = () => {
    this.setState({count: this.props.count + 1})
  }

  render() {
    return (
      <div onClick={this.handleClickEvent}>Count: {this.state.count}</div>
    )
  }
}


这需要首先添加一个方法来增加计数,为了能够正常的使用这个方法,我们需要在constructor中进行this的绑定:

this.handleClickEvnet = this.handleClickEvent.bind(this)


因为函数的执行上下文在点击事件触发会变化,导致方法中的this不是当前class,所以我们必须进行this的绑定,对于初学者而言,这可能有些难以理解。

当然,除了使用this进行绑定之外,还有一些方式也可以做到类似的效果,比如箭头函数:

handleClickEvent = () => {
  this.setState({ count: this.state.count + 1 })
}


我们再来看一下Hooks怎么实现这个功能吧:

count Counter = (props) => {
  const [count, setCount] = useState(0);
  useEffect(() => {
    setCount(props.count)
  }, [])
  function handleClickEvent() {
    setCount(count + 1)
  }
  return (
    <div onClick={handleClickEvent}>Count: {count}</div>
  )
}


如你所见,在Hooks中,我们只需要添加一个方法,然后将方法绑定到对应节点,完全不需要用到this

4. 让UI和逻辑更容易地解耦,复用性更高


使用Hooks,让UI和逻辑能够更容易分离,不再需要HOC和render props,Hooks可以用更少的样板代码,更优雅的UI和逻辑结合。

当你在使用BitGithub这样的代码分享平台分享你的组件时,这种“优雅的分离”显得格外重要,它可以让组件更容易被理解,维护和在不同应用之间复用。

1_T6i0a9d9RykUYZXNh2N-DQ.gif

5. 将相关逻辑保留在相同位置

Complex components become hard to understand 复杂组件变得难以理解


在使用Class Component时,有不同的生命周期,比如componentDidMountcomponentDidUpdate等,我们来思考一个场景,我们在componentDidMount中订阅了A和B两个服务,并且在componentWillUnMount中取消订阅,同时,在这两个生命周期中还有很多其他逻辑,这使得我们在回头阅读代码时很难将这分布在两个生命周期中的订阅与取消订阅关联起来。

为了证明这一点,我们使用Rxjs创建了一个计数的应用,我们不再需要handleClickEvent这个方法了

import { Subject } from 'rxjs'
export function getCounts() {
  const subject = new Subject()
  let count = 0;
  const interval = setInterval(() => {
    if (count > 10 || subject.isStopped) {
      clearInterval(interval)
      subject.complete()
    }
    subject.next(count++)
  }, 1000)
  return subject()
}
import { getCounts } from './reactive-service'

export function Counter(props) {
  const [count, setCount] = useState();

  useEffect(() => {
    const counterServiceSubject = getCounts();

    counterServiceSubject.subscribe(c => {
      setCount(c)
    })

    return () => {
      counterServiceSubject.unsubscribe()
    }
  }, [])

  return (
    <div>Count: {count}</div>
  )
}


我们可以在同一个useEffect中包含subscribe和其对应的unsubscribe逻辑,同样,如果你还需要其他不相关的逻辑,你可以放在另一个useEffect中,我们可以使用不同的useEffect来分离不同的部分。

// ...
useEffect(() => {
  others.subscribe(() => {
    // do sth.
  })
  return () => others.unsubscribe()
}, [])
// ...

6. 在不同组件之间共用状态逻辑


在class Component中,即便两个组件拥有相同的功能,但两个组件可能分别需要在不同数据源获取数据,用不同的规则进行排序和展示数据,所以两个组件之间很难共用状态逻辑。

虽然我们可以用HOC,render props之类的方式解决,但是这让我们必须调整现有组件,最终让一切的难度加大。

React Hooks提供了什么


通过自定义Hooks,你可以将这些逻辑提取出来,抽离成通用逻辑,并还可以分别对他们进行测试。

比如我们可以将上面例子中的count抽离成一个叫useCount的Hooks:

export function useCount(serviceSubject) {
  const [count, setCount] = useState();

  useEffect(() => {
    serviceSubject.subscribe(cnt => {
      setCount(cnt)
    })
    return () => serviceSubject.unsubscribe()
  }, [serviceSubject])

  return [count, setCount];
}


然后就可以直接进行使用:

import { useCount } from "./use-count";

export function ShowCount(props) {
  const [count, setCount] = useCount(props.serviceSubject);

  useEffect(() => {
    setCount(props.count);
  }, [setCount]);

  return (
    <div>
      <h1> Count : {count} </h1>
    </div>
  );
}

注意,我们在父组件中而不是在ShowCount组件中调用getCounts。否则,serviceSubject每次运行ShowCount时都会有一个新值,我们就不会得到我们期望的结果。

以上就是我切换到React Hooks的原因,如果您查看官方文档,就会发现React钩子有许多有趣的功能。如果React Hooks给你带来了其他好处,请在评论区告诉我~