overreated是React作者dan_abramov的博客,有很多详解React的干货。
How are function components different from classes是一篇详述function components和classes区别的文章。
在有Hooks之前,大家对function components与classes的区别的认知是,classes拥有更多的功能,比如可以有state
等。但是在有了hooks之后,这似乎不再是一个问题了,那么它们之间的区别是什么呢?
这里作者举了一个简单的例子来说明它们之间的不同:
1 | // function |
上面两种写法是我们常用的function和class的写法,乍看之下,它们完成的任务是一样的,但是这里作者玩了一个小小的trick,它给页面提供了不同的profile,让user
可以被切换(见live demo),于是我们看到,function实现的页面里,我们将user
从dan
切换到sophoie
之后,3秒后提示框里显示的仍是dan
,但是对于class来说就不是这样了,3秒之后显示的用户变成了sophine
,这是为什么呢?
This class method reads from
this.props.user
. Props are immutable in React so they can never change. However,this
is, and has always been, mutable.
事实上,class在设计的时候就是希望this
能够获取到最新的数据,这样在生命周期的方法里以及在render
里都能够显示最新的数据,但这样也会有一个问题,就是虽然props
是不会变的,但是this
是会变的,这样就导致showMessage
方法里读取到的user
数据可能“太新”了,从而导致它并不是当前render
对应的数据。简单来说,就是我们触发事件的时候,user
还是第一次传入的props
的值,可是在alert执行的时候,user
值已经是第二次传入的props
的值了。
假设现在没有function component,只能通过class来写组件,那么我们要如何解决上面的问题呢?
有一个方法是我们可以在事件之前先读取需要的数据,这样即使this
在事件之后改变,也不会影响到数据的呈现。
1 | class ProfilePage extends React.Component { |
这样的确可以解决上面提到的问题,但是很显然,这个方法也会导致代码变冗余,尤其是随着时间的增长,这样的写法会更容易出错,因为我们需要的每个props
或state
中的值都需要事先从对象中取出来,有没有更好的解决方案呢?
另一个解决方法是通过闭包来实现数据的不变。也就是说,当你将props
和state
数据都放入render中,使其成为闭包,可以保证数据不会发生变化。
1 | class ProfilePage extends React.Component { |
You’ve “captured” props at the time of render”
上面的方法的确可以保证数据不会发生变化,且我们可以加入更多的方法在render
中,但是这种写法非常奇怪,如果我们需要在render
方法里来定义函数,那么为什么要使用class呢?这样完全可以通过function来实现了:
1 | function ProfilePage(props) { |
不同于this
, props
是不会发生变化的。当ProfilePage
的父组件需要传给ProfilePage
不同的props
时,它会重新调用这个function来重新渲染。而我们点击按钮时的props
是前一次渲染中的props
,所以showMessage
还是会读取到前一次渲染中的user
,这样就不会出现点击的callback数据和实际页面对不上的情况了。
这样我们知道了function捕获的是每一次渲染的值,hooks在实现上也是这样。
1 | function MessageThread() { |
那么附加一个问题,如果我们就是想要读取到“最新的数据”,即不是当前渲染的数据,而是“未来的数据”呢?
一个方法就是我们通过classes来读取this.props
和this.state
来实现。还有另一个思路是我们可以通过ref
来实现。
1 | function MessageThread() { |
通过重新给ref赋值,我们能够拿到最新的props
值。当然这样手动修改ref值是一件麻烦的事情,有一个更方便的方法是通过useEffect
来自动修改:
1 | function MessageThread() { |