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,thisis, 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() { |