为何要在componentDidMount里面发送请求?

5,603 阅读4分钟

转自为何要在componentDidMount里面发送请求?

在我们编写React代码的时候,总会遇到这么一个问题:请求接口并展示我们获取到的数据。这听起来很简单,但是你有没有想过一个问题:在何时进行进行网络请求才是最好的?我相信你们都会说,我当然知道,在 componentDidMount 中啊,因为这是React官方推荐的,不服上个图

pic1

这一下你应该服了吧?服是服了,但为什么是 componentDidMountconstructorcomponentWillMount不可以吗?

首先我们来百度一下,这是一个最高赞的答案

pic2

总结一下:

  1. componentDidmount 是在组件完全挂载后才会执行,在此方法中调用setState 会触发重新渲染,最重要的是,这是官方推荐的!

  2. constructor 调用是在一开始,组件未挂载,所以不能用。

  3. componentWillMount 调用在 constructor 后,在这里的代码调用 setState 不会出发重新渲染,所以不用。

  4. 还有一个没有出现在这里但听得最多的说法是:在 componentWillMount 里进行网络请求会阻碍组件的渲染。

  5. 反正就是要在 componentDidmount 里用!

说的好像挺有道理的,但是也感觉怪怪的,看的再多不如自己动手测试一下。首先测试一下 constructor

constructor

class Parent extends React.Component {
  constructor(props) {
    super(props);
<span class="hljs-keyword">this</span>.state = {
  <span class="hljs-attr">text</span>: <span class="hljs-string">'plain text'</span>
};

fetch(<span class="hljs-string">'https://s.codepen.io'</span>)
  .then(<span class="hljs-function"><span class="hljs-params">res</span> =&gt;</span> <span class="hljs-keyword">this</span>.setState({<span class="hljs-attr">text</span>: <span class="hljs-string">'success'</span>}))
  .catch(<span class="hljs-function"><span class="hljs-params">err</span> =&gt;</span> <span class="hljs-keyword">this</span>.setState({<span class="hljs-attr">text</span>: <span class="hljs-string">'error'</span>}))

}

render() { return ( <div> <h1>{this.state.text}</h1> </div> ); } }

ReactDOM.render( <Parent/>, document.getElementById('root') ); 复制代码

这里看演示,state从一开始的plain text变成了error(因为跨域问题,请求无法成功,但是没有关系)

pic3

所以上面说的constructor 调用时组件未挂载,所以不能用的说法是错误的,组件未挂载也可以发送请求,这里所影响的时间只有执行发送请求的时间,然后组件接着渲染,等异步数据返回后,再执行 setState,或许你会说,如果请求时间很短,在组件挂载之前就返回了怎么办,此时的 setState 还会起作用吗?别着急,这个问题后面会提到。

componentWillMount

将请求移到 componentWillMount


class Parent extends React.Component {
  constructor(props) {
    super(props);
<span class="hljs-keyword">this</span>.state = {
  <span class="hljs-attr">text</span>: <span class="hljs-string">'plain text'</span>
};

}

componentWillMount() { fetch('s.codepen.io') .then(res => this.setState({text: 'success'})) .catch(err => this.setState({text: 'error'})) }

render() { return ( <div> <h1>{this.state.text}</h1> </div> ); } }

ReactDOM.render( <Parent/>, document.getElementById('root') );

复制代码

这里,可以看到,state也是从plain text 变成了error,嫌太快看不清楚的可以用setTimeout模拟一下。这就很奇怪了,不是说willMount里面setState不会重新渲染吗?不是说网络请求会阻塞组件的渲染吗?然而都没有,其实原理跟constructor是一样的,所影响的时间只有执行发送请求的时间,并不会阻塞组件的渲染,但不推荐使用 componentWillMount 是有其他的原因:

  1. 很重要的一点,React16.3后将会废弃掉componentWillMount、componentWillReceiveProps 以及 componentWillUpdate 三个周期函数,直到React 17前还可以使用,不过会有一个警告。

  2. 跟服务端渲染有关系(同构),如果在 componentWillMount 里获取数据,fetch data会执行两次,一次在服务端一次在客户端,使用 componentWillMount 则没有这个问题。

至于前面说到的数据在组件挂载前返回导致不生效的,这种情况并不会发生, 因为 setState 是将更新的状态放进了组件的__pendingStateQueue队列中,react并不会立即响应更新,会等到组件挂载完成后,再统一更新脏组件,见下图

pic4

因此,从另外的角度看,放在constructor或者componentWillMount里面反而会更加有效率。

总结

  1. 数据获取可以放在 constructor 或者 componentDidmount 中,不建议放在 componentWillMount。 但是为了更好的代码规范和可读性,建议统一放在 componentDidmount

  2. 对于首次render没有数据,可能导致出错的。可以设置一个initial state,或者增加一个loading状态,加载数据时展示一个spinner或者骨架图都是比较常用的方案。

参考链接

  1. Where to Fetch Data: componentWillMount vs componentDidMount

  2. React数据获取为什么一定要在componentDidMount里面调用?