React函数组件与React类有何不同?有一段时间,规范的答案是类可以访问更多功能(如状态)。但是由于Hook可用,所以这个答案不是唯一的。也许你听说过其中一个表现更好。哪一个?这些基准中的许多都存在缺陷,因此我会谨慎地从中得出结论。性能主要取决于你的代码做了什么,而不是你选择的是函数还是类。在我们的观察中,虽然优化策略有点不同,但性能差异可以忽略不计。无论哪种情况,我们都不建议重写现有组件,除非您有其他原因并且不介意成为早期采用者。钩子还是新的。那么功能函数和类有根本的不同吗?函数组件捕获呈现的值。我们看看这句话是什么意思?注意:这篇文章不是对一个类或函数的价值的判断。我只描述了这两种编程模型在React中的区别。有关更广泛地采用该功能的问题,请参阅Hooks常见问题解答。考虑这个组件:functionProfilePage(props){constshowMessage=()=>{alert('Followed'+props.user);};consthandleClick=()=>{setTimeout(showMessage,3000);};return(Follow);}它显示一个按钮,使用setTimeout模拟网络请求,然后显示确认警报。例如,如果props.user是'Dan',它会在三秒后显示'FollowedDan'。很简单。(请注意,在上面的示例中,使用箭头还是函数声明并不重要。函数handleClick()将以完全相同的方式工作。)我们如何把它写成一个类?感觉应该是这样的:classProfilePageextendsReact.Component{showMessage=()=>{alert('Followed'+this.props.user);};handleClick=()=>{setTimeout(this.showMessage,3000);};render(){returnFollow;这两个代码片段通常被认为是等价的。人们经常在这些模式之间自由重构,而没有注意到它们的含义:然而,这两个代码片段略有不同。好好看看他们。你看得到差别吗?就个人而言,我花了一段时间才看到这个。如果您想自己弄清楚,这里有一个现场演示。本文的其余部分解释了差异及其重要性。在继续之前,我想强调一下,我所描述的差异与ReactHooks本身无关。上面的例子甚至没有使用Hooks!这就是React中函数和类的区别。如果您打算在React应用程序中更频繁地使用函数,您可能想了解一下。我们将通过React应用程序中的常见错误来说明区别。打开此示例并尝试使用两个按钮执行以下操作序列:单击其中一个“关注”按钮。在3秒前更改所选配置文件。阅读警报文本。您会注意到一个特殊的区别:使用上面的ProfilePage功能,单击Dan的个人资料上的关注,然后导航到Sophie's仍然会提示“已关注Dan”。使用上面的ProfilePage类,它会警告“已关注Sophie”:第一个行为是本例中的正确行为。如果我关注一个人然后导航到另一个人的个人资料,我的组件不应该让使用它的人感到困惑。这个类的实现显然是错误的。那么为什么我们的类示例会以这种方式运行呢?让我们仔细看看我们类中的showMessage方法:};此类方法从this.props.user读取传递。Props在React中是不可变的,所以它们永远不会改变。然而,这一直是,也将永远是可变的。React本身会随着时间发生变化,因此您可以在渲染和生命周期方法中读取新版本。因此,如果我们的组件在请求进行时呈现,则this.props将会改变。showMessage方法从“太新”的道具中读取用户。这暴露了一个关于用户界面本质的有趣观察。如果我们说UI在概念上是当前应用程序状态的函数,那么事件处理程序就是呈现结果的一部分——就像视觉输出一样。我们的事件处理程序“属于”具有特定道具和状态的特定渲染。假设功能组件不存在。我们如何解决这个问题?一种方法是在事件期间尽早读取this.props,然后将它们显式传递给超时完成处理程序:;};handleClick=()=>{const{user}=this.props;setTimeout(()=>this.showMessage(user),3000);};render(){return关注;这行得通。但是,随着时间的推移,这种方法会使代码变得更加冗长和容易出错。如果我们需要不止一个道具怎么办?如果我们仍然需要访问该州怎么办?如果showMessage调用另一个方法,并且该方法读取this.props.something或this.state.something,我们将再次遇到完全相同的问题。所以我们必须通过showMessage调用的每个方法传递this.props和this.state作为参数。此外,在handleClick中内联警报代码并不能解决更大的问题。我们希望以一种允许将其拆分为更多方法的方式构建代码,同时还读取与该调用关联的渲染对应的道具和状态。这个问题甚至不是React独有的——你可以在任何像这样将数据放入可变对象的UI库中重现它。也许,我们可以在构造函数中绑定方法?类ProfilePage扩展React.Component{constructor(props){super(props);this.showMessage=this.showMessage.bind(this);this.handleClick=this.handleClick.bind(this);}showMessage(){alert('已关注'+this.props.user);}handleClick(){setTimeout(this.showMessage,3000);}render(){returnFollow;}}不,这不能解决任何问题。请记住,问题是我们从这里读取。来不及支持-不是我们使用的语法!但是,如果我们完全依赖JavaScript闭包,问题就会消失。通常会避免闭包,因为很难想象一个值会随时间发生变化。但是在React中,props和state是不可变的!(或者至少,这是一个强烈的建议。)这消除了一个主要的封闭区域。这意味着,如果您从特定渲染中关闭props或state,它们的值将保持完全相同:classProfilePageextendsReact.Component{render(){//捕获props!constprops=this.props;//注意:我们在*insiderender*。//这些不是类方法。constshowMessage=()=>{alert('已关注'+props.user);};consthandleClick=()=>{setTimeout(showMessage,3000);};返回Follow;您在渲染时“捕获”道具:这样,其中的任何代码(包括showMessage)都可以保证看到道具。React不再“动了我们的奶酪”。然后我们可以在里面添加任意数量的辅助函数,它们都将使用捕获的道具和状态。上面的例子是正确的,但看起来很奇怪。如果您在render中定义一个函数而不是使用类方法,那么拥有一个类意味着什么?我们实际上可以通过删除围绕它的类“shell”来简化代码:};consthandleClick=()=>{setTimeout(showMessage,3000);};return(Follow);}就像上面一样,道具仍然被捕获-React将它们作为参数传递。与此不同的是,道具对象本身永远不会被React改变。如果你在函数定义中构建props就更明显了:};consthandleClick=()=>{setTimeout(showMessage,3000);};return(Follow);}当父组件用不同的props渲染ProfilePage时,React会再次调用ProfilePage函数。但是我们点击的事件处理程序“属于”之前的渲染,具有自己的用户值和读取它的showMessage回调。他们都完好无损。现在我们了解了React中函数和类之间的巨大区别:函数组件捕获呈现的值。对于Hooks,同样的原则适用于状态。考虑这个例子:functionMessageThread(){const[message,setMessage]=useState('');constshowMessage=()=>{alert('你说:'+message);};常量handleSendClick=()=>{setTimeout(showMessage,3000);};常量handleMessageChange=(e)=>{setMessage(e.target.value);};return(<>Send>);}虽然这不是一个非常好的消息应用程序UI,但它说明了同一点:如果我发送特定消息,组件不应该对实际发送的消息感到困惑。此功能组件的消息捕获“属于”渲染器的状态,该渲染器返回浏览器调用的点击处理程序。所以当我点击发送时,消息被设置为输入中的内容。所以默认情况下,我们知道React捕获状态中的props和函数。但是,如果我们想读取不属于此特定渲染的最新道具或状态怎么办?如果我们想“从未来读取它们”怎么办?在类中,您可以通过阅读this.props或this.state来做到这一点,因为它本身是可变的。React改变了这一点。在功能组件中,您还可以拥有由所有组件渲染共享的可变值。它被称为“ref”:functionMyComponent(){constref=useRef(null);//你可以读取或写入`ref.current`。//...}但是,您必须自己管理它。refs扮演与实例字段相同的角色。它是进入可变命令世界的逃生舱。您可能熟悉“DOMrefs”,但这个概念更为笼统。它只是一个你放东西的盒子。即使在视觉上,这个东西看起来就像是某种东西的镜子。它们代表同一个概念。默认情况下,React不会在功能组件中创建对最新道具或状态的引用。在许多情况下,您不需要它们并且分配它们将是浪费工作。但是,如果需要,您可以手动跟踪该值:functionMessageThread(){const[message,setMessage]=useState('');constlatestMessage=useRef('');constshowMessage=()=>{alert('你说:'+latestMessage.current);};常量handleSendClick=()=>{setTimeout(showMessage,3000);};常量handleMessageChange=(e)=>{setMessage(e.target.value);latestMessage.current=e.target.value;};如果我们在showMessage中阅读消息,我们将在按下“发送”按钮时看到消息。但是当我们读取latestMessage.current时,我们得到了最新的值——即使我们在按下发送按钮后继续输入。您可以比较两个演示(https://codesandbox.io/s/93m5...https://codesandbox.io/s/ox200vw8k9)并查看差异。refs是一种“选择退出”渲染一致性的方法,在某些情况下这很方便。通常,您应该避免在渲染期间读取或设置引用,因为它们是可变的。我们希望保持渲染的可预测性。但是如果我们想要获取特定prop或state的最新值,手动更新ref可能会很烦人。我们可以通过使用效果来自动执行此操作:functionMessageThread(){const[message,setMessage]=useState('');//跟踪最新值。constlatestMessage=useRef('');useEffect(()=>{latestMessage.current=message;});constshowMessage=()=>{alert('你说:'+latestMessage.current);};demo结论在这篇文章中,我们研究了类中常见的损坏模式,以及闭包如何帮助我们修复它。但是,您可能已经注意到,当您尝试通过指定依赖项数组来优化Hooks时,您可能会遇到过期闭包的错误。这是否意味着关闭是问题所在?我不这么认为。正如我们在上面看到的,闭包实际上帮助我们解决了难以察觉的细微问题。此外,它们还可以更轻松地编写在并发模式下正常工作的代码。这没关系,因为组件内部的逻辑关闭了正确的道具和渲染状态。到目前为止,在我见过的所有案例中,“陈旧闭包”问题都是由于“功能不会改变”或“道具始终相同”的错误假设而产生的。事实并非如此,我希望这篇文章有助于澄清。