组件与服务器通信

React 关注的是 UI 的分离、视图的组件化,对于组件如何与服务器端 API 通信,React 官方并没有给出太多指导。但是,几乎所有应用都避免不了和服务器端 API 通信。这就给很多 React 的使用者带来了困惑,React 中的组件到底应该如何优雅地和服务器通信呢?本节将结合实践对这个问题的解决方法给出建议。

首先需要明确一点,本节讨论的组件与服务器通信特指组件从服务器上获取数据,不包含组件向服务器提交数据的情况。组件向服务器提交数据一定是由组件 UI 的某一事件触发的,比如提交了一个表单、点击了一个元素等,所以只要在监听相应事件的回调函数中执行向服务器提交数据的逻辑即可,一般不会有疑问。但组件从服务器上获取数据,情况就要复杂得多。

组件挂载阶段通信

React 组件的正常运转本质上是组件不同生命周期方法的有序执行,因此组件与服务器的通信也必定依赖组件的生命周期方法。我们先来看一下组件在挂载阶段如何与服务器通信。

定义一个 UserListContainer 组件,需要从服务器获取用户列表:

class UserListContainer extends React.Component {
    /** 省略无关代码 **/

    componentDidMount() {
        var that = this;
        fetch('/path/to/user-api').then(function (response) {
            response.json().then(function (data) {
                that.setState({users: data})
            });
        });
    }
}

UserListContainer 是在 componentDidMount 中与服务器进行通信的,这时候组件已经挂载,真实 DOM 也已经渲染完成,是调用服务器 API 最安全的地方,也是 React 官方推荐的进行服务器通信的地方。

除了 componentDidMount 外,在 componentWillMount 中进行服务器通信也是比较常见的一种方式。代码如下:

class UserListContainer extends React.Component {
    /** 省略无关代码 **/

    componentWillMount() {
        var that = this;
        fetch('/path/to/user-api').then(function (response) {
            response.json().then(function (data) {
                that.setState({users: data})
            });
         });
    }
}

componentWillMount 会在组件被挂载前调用,因此从时间上来讲,在 componentWillMount 中执行服务器通信要早于在 componentDidMount 中执行,执行得越早意味着服务器数据越能更快地返回组件。这也是很多人青睐在 componentWillMount 中执行服务器通信的重要原因。但实际上,componentWillMount 与 componentDidMount 执行的时间差微乎其微,完全可以忽略不计。

componentDidMount 是执行组件与服务器通信的最佳地方,原因主要有两个:

  1. 在 componentDidMount 中执行服务器通信可以保证获取到数据时,组件已经处于挂载状态,这时即使要直接操作 DOM 也是安全的,而 componentWillMount 无法保证这一点。

  2. 当组件在服务器端渲染时(本书不涉及服务器渲染内容),componentWillMount 会被调用两次,一次是在服务器端,另一次是在浏览器端,而 componentDidMount 能保证在任何情况下只会被调用一次,从而不会发送多余的数据请求。

有些开发人员会在组件的构造函数中执行服务器通信,一般情况下,这种方式也可以正常工作。但是,构造函数的意义是执行组件的初始化工作,如设置组件的初始状态,并不适合做数据请求这类有 “副作用” 的工作。因此,不推荐在构造函数中执行服务器通信。

组件更新阶段通信

组件在更新阶段常常需要再次与服务器通信,获取服务器上的最新数据。例如,组件需要以 props 中的某个属性作为与服务器通信时的请求参数,当这个属性值发生更新时,组件自然需要重新与服务器通信。回想2.3节中对组件生命周期的介绍,不难发现 componentWillReceiveProps 非常适合做这个工作。假设 UserListContainer 在获取用户列表时还需要一个参数 category,用来根据用户的职业做筛选,category 这个参数是从 props 中获取的,实现代码如下:

class UserListContainer extends React.Component {

    /** 省略无关代码 **/

    componentWillReceiveProps(nextProps) {
        if (nextProps.category !== this.props.category) {
            fetch('/path/to/user-api?category=' + nextProps.category).then(function (response) {
                response.json().then(function (data) {
                    that.setState({users: data})
                });
            });
        }
    }
}

这里还有一个地方要注意,在执行 fetch 请求时,要先对新老 props 中的 category 做比较,只有不一致才说明 category 有了更新,才需要重新进行服务器通信。componentWillReceiveProps 的执行并不能保证 props 一定发生了修改。