React Hooks (一) Why

114 阅读5分钟

原文:ui.dev/why-react-h…

Why

在介绍React Hooks之前,我们需要了解两个问题:

  1. 为什么会出现React Hooks

  2. 它解决了什么问题

我们可以先了解一下以前的React是怎么写的

React.createClass

createClass是一种简单有效的创建组件的方式,最早使用createClass的原因是当时,JavaScript还没有类体系。

React.Component

React v0.13.0 引入了React.Component,它允许我们用JavaScript类来创建组件。

那么,使用中有哪些问题呢?

constructor

当我们使用类创建组件,我们需要constructor方法来初始化组件state,然而,由于我们继承了React.Component,我们需要在每个constructor里都调用super。

constructor (props) {
    super(props) // 🤮
    ...
  }

Autobinding

当我们使用createClass,React会自动帮我们bind所有方法到组件实例上。而当我们使用React.Component,我们需要在constructor手动bind,否则就会曝出“Cannot read property setState of undefined”错误。

  constructor (props) {
    ...
    this.updateRepos = this.updateRepos.bind(this) // 😭
  }

这时我们可能会想:首先,这些问题都很简单,保证调用super(props) 和手动bind虽然繁琐,但是整体没有大问题。第二,这些甚至算不上React的问题,因为JavaScript类就是这么设计的。但是作为程序员,即使最简单的问题,每天重复20+次,也很讨厌。

Class Fields

Class Fields允许我们不用constructor就可以直接给组件类添加实例属性并赋值。这样我们就可以直接初始化state,并且用箭头函数解决bind问题。

class ReposGrid extends React.Component {
  state = {
    repos: [],
    loading: true
  }
  componentDidMount () {
    this.updateRepos(this.props.id)
  }
  componentDidUpdate (prevProps) {
    if (prevProps.id !== this.props.id) {
      this.updateRepos(this.props.id)
    }
  }
  updateRepos = (id) => {
    this.setState({ loading: true })
    ...
  }
  ...
}

这样就可以了吗?还没。

React的核心思想是让我们把复杂度降解到各个组件,再把组件组合起来,以此来管理App的复杂度。这样的组件模型会让React很优雅。然而,现在的问题不是出在组件模型上,而是出在了模型怎么实现上。

Duplicate Logic

由于历史原因,我们如何构建组件直接和组件生命周期相关。这种生命周期的划分,强制我们把逻辑散布在组件各处。下面的例子中,我们可以清楚的看到,我们需要三个方法(componentDidMount, componentDidUpdate, and updateRepos) 来完成同一件事:保证 repos 和 props.id 同步。

componentDidMount () {
    this.updateRepos(this.props.id)
  }
  componentDidUpdate (prevProps) {
    if (prevProps.id !== this.props.id) {
      this.updateRepos(this.props.id)
    }
  }
  updateRepos = (id) => {
    this.setState({ loading: true })

    fetchRepos(id)
      .then((repos) => this.setState({
        repos,
        loading: false
      }))
  }

为了修复这个问题,我们需要新的范式来处理React组件的副作用(side effects)。

Sharing Non-visual Logic

当我们在思考React的组织形式时,我们大概率是以 UI 的组织视角来思考。这很正常,因为 React 对 UI 很在行。

view = fn(state)

现实中,构建一个app仅仅做UI层是不够的。需要复用非可视化的逻辑是一个很常见的需求。然而,由于React把UI耦合在了component上,所以复用非可视化变得很难。从历史上看,React都没有很好的解决这个问题。 结合例子来看,如果我们需要创建另一个也需要repos state的组件。现在,逻辑处理和状态都在ReposGrid组件里。我们怎么复用?最简单的方法是copy所有网络请求和处理数据的逻辑,然后粘贴到新组件。当然这只能是临时方案...更好一点的方案应该是创建一个HOC(Higher-Order Component)来封装所有公共逻辑,并且把 loading 和 repos 作为props传给需要的组件。

function withRepos (Component) {
  return class WithRepos extends React.Component {
      state = {
        repos: [],
        loading: true
      }
      componentDidMount () {
        this.updateRepos(this.props.id)
      }
      componentDidUpdate (prevProps) {
        if (prevProps.id !== this.props.id) {
          this.updateRepos(this.props.id)
        }
      }
      updateRepos = (id) => {
        this.setState({ loading: true })
  
        fetchRepos(id)
          .then((repos) => this.setState({
            repos,
            loading: false
          }))
      }
      render () {
        return (
          <Component
            {...this.props}
            {...this.state}
          />
        )
      }
  }
}

这样,无论任何组件需要repos(or loading),我们都能用 withRepos HOC来包装。

// ReposGrid.js
function ReposGrid ({ loading, repos }) {
  ...
}

export default withRepos(ReposGrid)
// Profile.js
function Profile ({ loading, repos }) {
  ...
}

export default withRepos(Profile)

这个方案可行,而且是推荐的共享非可视逻辑的方案。但是还是有些缺陷。

首先,如果你不熟悉HOC,你会觉得有点跟不上逻辑。以 withRepos HOC为例,这是一个函数,接收一个负责最终渲染的component作为入参,但是返回一个包含我们逻辑的新组件。这个流程很复杂,不易理解。

其次,如果我们需要更多HOC,可以想象,很快我们的代码就不好掌控了。

export default withHover(
  withTheme(
    withAuth(
      withRepos(Profile)
    )
  )
)

比上面更惨的是最终怎么render。HOC会强迫我们重新构造包装我们的组件。最终会导致“包装地狱”,难以维护。

<WithHover>
  <WithTheme hovering={false}>
    <WithAuth hovering={false} theme='dark'>
      <WithRepos hovering={false} theme='dark' authed={true}>
        <Profile 
          id='JavaScript'
          loading={true} 
          repos={[]}
          authed={true}
          theme='dark'
          hovering={false}
        />
      </WithRepos>
    </WithAuth>
  <WithTheme>
</WithHover>

Current State

现状是:

  • React很流行
  • 我们用类来写React组件是因为这在当时最合适
  • 调用super(props)很烦
  • 没人知道this怎么绑定的
  • 别急,我们知道你知道this怎么绑定的,但是对其他人是不必要的负担
  • 用生命周期来组织组件会让我们把相关的逻辑打散到组件各处
  • React没有很好的原始方案来共享非可视逻辑

现在,我们需要一个新的组件方案来解决上述问题,并且保持simple, composable, flexible, and extendable。 这就是React Hooks。