摘要:函数组件与类有何不同有一段时间,规范的答案是类可以访问更多功能如状态。那么功能性函数和类是否又根本的区别函数组件捕获的值。假设功能组件不存在。在我到目前为止看到的所有情况中,由于错误地假设功能不会改变或总是相同,所以会出现陈旧的封闭问题。
React函数组件与React类有何不同?
有一段时间,规范的答案是类可以访问更多功能(如状态)。但是自从有了Hook后,这个答案变得不唯一了。
也许你听说其中一个表现更好。哪一个?许多此类基准都存在缺陷,因此我会小心地从中得出结论。性能主要取决于代码的作用,而不是您选择的是函数还是类。在我们的观察中,虽然优化策略有点不同,但性能差异可以忽略不计。
在任何一种情况下,除非您有其他原因并且不介意成为早期采用者,否则我们不建议您重写现有组件。Hooks仍然是新的。
那么功能性函数和类是否又根本的区别?
函数组件捕获rendered的值。让我们看看这句话是什么意思?
注意:这篇文章不是对类或函数的值判断。我只描述了React中这两种编程模型之间的区别。有关更广泛地采用功能的问题,请参阅Hooks常见问题解答。
思考一下这个组件:
function ProfilePage(props) { const showMessage = () => { alert("Followed " + props.user); }; const handleClick = () => { setTimeout(showMessage, 3000); }; return ( ); }
它显示一个按钮,使用setTimeout模拟网络请求,然后显示确认警报。例如,如果props.user是"Dan",它将在三秒后显示"Followed Dan"。很简单。(注意在上面的例子中我是否使用箭头或函数声明并不重要。函数handleClick()将以完全相同的方式工作。)
我们如何把它写成一个类?感觉应该是如下所示:
class ProfilePage extends React.Component { showMessage = () => { alert("Followed " + this.props.user); }; handleClick = () => { setTimeout(this.showMessage, 3000); }; render() { return ; } }
通常认为这两个代码片段是等效的。人们经常在这些模式之间自由地重构,而不会注意到它们的含义:
但是,这两个代码片段略有不同。好好看看他们。你看到了差异吗?就个人而言,我花了一段时间才看到这一点。
如果你想自己搞清楚,这里是一个现场演示。本文的其余部分解释了差异及其重要性。
在我们继续之前,我想强调一点,我所描述的差异与React Hooks本身无关。以上示例甚至不使用Hooks!这就是React中函数和类之间的区别。如果您计划在React应用程序中更频繁地使用函数,则可能需要了解它。
我们将通过React应用程序中常见的错误说明其差异。
打开此示例
使用两个按钮尝试以下操作序列:
单击其中一个“关注”按钮。
在3秒之前更改所选的配置文件。
阅读警报文字。
你会注意到一个特殊的区别:
使用上面的ProfilePage函数,单击Follow on Dan的个人资料,然后导航到Sophie"s仍然会提醒"Followed Dan"。
使用上面的ProfilePage类,它会警告"Followed Sophie":
在此示例中,第一个行为是正确的行为。如果我跟随一个人然后导航到另一个人的个人资料,我的组件不应该让使用的人感到困惑。这个类实现显然是错误的。
那么为什么我们的类示例会以这种方式运行?
让我们仔细看看我们类中的showMessage方法:
class ProfilePage extends React.Component { showMessage = () => { alert("Followed " + this.props.user); };
此类方法从this.props.user读取。Props在React中是不可变的,因此它们永远不会改变。然而,这一直是,并且一直是可变的。
React本身会随着时间的推移而变异,以便您可以在渲染和生命周期方法中阅读新版本。
因此,如果我们的组件在请求处于运行状态时render,则this.props将会更改。showMessage方法从“too new”的props中读取用户。
这暴露了一个关于用户界面性质的有趣观察。如果我们说UI在概念上是当前应用程序状态的函数,则事件处理程序是渲染结果的一部分 - 就像视觉输出一样。我们的事件处理程序“属于”具有特定props和state的特定渲染。
假设功能组件不存在。我们如何解决这个问题?
一种方法是在事件期间尽早读取this.props,然后将它们显式传递到超时完成处理程序:
class ProfilePage extends React.Component { showMessage = (user) => { alert("Followed " + user); }; 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中内联警报代码并不能解决更大的问题。我们希望以允许将其拆分为更多方法的方式构造代码,同时还要读取与该调用相关的渲染所对应的props和state。这个问题甚至不是React独有的 - 您可以在任何将数据放入像这样的可变对象的UI库中重现它。
也许,我们可以绑定构造函数中的方法?
class ProfilePage extends React.Component { constructor(props) { super(props); this.showMessage = this.showMessage.bind(this); this.handleClick = this.handleClick.bind(this); } showMessage() { alert("Followed " + this.props.user); } handleClick() { setTimeout(this.showMessage, 3000); } render() { return ; } }
不,这不能解决任何问题。请记住,问题是我们从这里读取。支持太晚了 - 不是我们正在使用的语法!但是,如果我们完全依赖JavaScript闭包,问题就会消失。
通常会避免闭包,因为很难想象随着时间的推移可能会发生变异的价值。但在React中,props和state是不可改变的!(或者至少,这是一个强烈的推荐。)这消除了一个主要的封闭区域。
这意味着如果你从特定渲染中关闭props或state,它们的值保持完全相同:
class ProfilePage extends React.Component { render() { // Capture the props! const props = this.props; // Note: we are *inside render*. // These aren"t class methods. const showMessage = () => { alert("Followed " + props.user); }; const handleClick = () => { setTimeout(showMessage, 3000); }; return ; } }
你在渲染时“捕获”了props:
这样,它内部的任何代码(包括showMessage)都可以保证看到这个特定渲染的道具。React不再“move our cheese”了。
然后我们可以在里面添加任意数量的辅助函数,它们都会使用捕获的props和state。
上面的例子是正确的,但看起来很奇怪。如果在render中定义函数而不是使用类方法,那么有一个类是什么意思?
实际上,我们可以通过删除它周围的类“shell”来简化代码:
function ProfilePage(props) { const showMessage = () => { alert("Followed " + props.user); }; const handleClick = () => { setTimeout(showMessage, 3000); }; return ( ); }
就像上面一样,props仍然被捕获 - React将它们作为参数传递。与此不同,props对象本身永远不会被React变异。如果你在函数定义中构造props,那就更明显了:
function ProfilePage({ user }) { const showMessage = () => { alert("Followed " + user); }; const handleClick = () => { setTimeout(showMessage, 3000); }; return ( ); }
当父组件使用不同的props呈现ProfilePage时,React将再次调用ProfilePage函数。但是我们已经点击的事件处理程序“属于”具有自己的用户值的前一个渲染和读取它的showMessage回调。他们都完好无损。
现在我们了解React中函数和类之间的巨大差异:
函数组件捕获呈现的值。使用Hooks,同样的原则也适用于州。考虑这个例子:
function MessageThread() { const [message, setMessage] = useState(""); const showMessage = () => { alert("You said: " + message); }; const handleSendClick = () => { setTimeout(showMessage, 3000); }; const handleMessageChange = (e) => { setMessage(e.target.value); }; return ( <> > ); }
虽然这不是一个非常好的消息应用UI,但它说明了同样的观点:如果我发送特定消息,组件不应该对实际发送的消息感到困惑。此函数组件的消息捕获“属于”渲染器的状态,该渲染器返回浏览器调用的单击处理程序。因此,当我单击“发送”时,消息将设置为输入中的内容。
因此,默认情况下,我们知道React捕获道具和状态中的函数。但是,如果我们想要阅读不属于这个特定渲染的最新道具或州,该怎么办?如果我们想 “read them from the future”怎么办?
在类中,你可以通过阅读this.props或this.state来实现它,因为它本身是可变的。React改变了它。在函数组件中,您还可以具有由所有组件呈现共享的可变值。它被称为“ref”:
function MyComponent() { const ref = useRef(null); // You can read or write `ref.current`. // ... }
但是,您必须自己管理它。
ref与实例字段扮演相同的角色。它是进入可变命令世界的逃脱舱。您可能熟悉“DOM refs”,但概念更为通用。它只是一个盒子,你可以把东西放进去。即使在视觉上,这个东西看起来像是某种东西的镜子。它们代表了相同的概念。默认情况下,React不会为函数组件中的最新props或状态创建引用。在许多情况下,您不需要它们,分配它们将是浪费的工作。但是,如果您愿意,可以手动跟踪值:
function MessageThread() { const [message, setMessage] = useState(""); const latestMessage = useRef(""); const showMessage = () => { alert("You said: " + latestMessage.current); }; const handleSendClick = () => { setTimeout(showMessage, 3000); }; const handleMessageChange = (e) => { setMessage(e.target.value); latestMessage.current = e.target.value; };
如果我们在showMessage中读取消息,我们会在按下“发送”按钮时看到消息。但是当我们读取latestMessage.current时,我们得到最新的值 - 即使我们在按下发送按钮后继续输入。你可以比较两个演示(https://codesandbox.io/s/93m5... https://codesandbox.io/s/ox200vw8k9),看看差异。ref是一种“选择退出”渲染一致性的方法,在某些情况下可以很方便。通常,您应该避免在渲染期间读取或设置引用,因为它们是可变的。我们希望保持渲染的可预测性。但是,如果我们想获得特定道具或状态的最新值,那么手动更新ref会很烦人。我们可以通过使用效果自动化它:
function MessageThread() { const [message, setMessage] = useState(""); // Keep track of the latest value. const latestMessage = useRef(""); useEffect(() => { latestMessage.current = message; }); const showMessage = () => { alert("You said: " + latestMessage.current); };
demo
结论在这篇文章中,我们研究了类中常见的破碎模式,以及闭包如何帮助我们修复它。但是,您可能已经注意到,当您尝试通过指定依赖关系数组来优化Hook时,您可能会遇到带有过时闭包的错误。是否意味着闭包是问题?我不这么认为。
正如我们上面所看到的,闭包实际上帮助我们解决了很难注意到的细微问题。同样,它们使编写在并发模式下正常工作的代码变得更加容易。这是可以的,因为组件内部的逻辑关闭了正确的props和渲染state。
在我到目前为止看到的所有情况中,由于错误地假设“功能不会改变”或“props总是相同”,所以会出现“陈旧的封闭”问题。事实并非如此,因为我希望这篇文章有助于澄清。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/102377.html
摘要:函数组件与类有何不同有一段时间,规范的答案是类可以访问更多功能如状态。那么功能性函数和类是否又根本的区别函数组件捕获的值。假设功能组件不存在。在我到目前为止看到的所有情况中,由于错误地假设功能不会改变或总是相同,所以会出现陈旧的封闭问题。 React函数组件与React类有何不同? 有一段时间,规范的答案是类可以访问更多功能(如状态)。但是自从有了Hook后,这个答案变得不唯一了。 也...
摘要:函数组件与类有何不同有一段时间,规范的答案是类可以访问更多功能如状态。那么功能性函数和类是否又根本的区别函数组件捕获的值。假设功能组件不存在。在我到目前为止看到的所有情况中,由于错误地假设功能不会改变或总是相同,所以会出现陈旧的封闭问题。 React函数组件与React类有何不同? 有一段时间,规范的答案是类可以访问更多功能(如状态)。但是自从有了Hook后,这个答案变得不唯一了。 也...
摘要:需要注意的是,同样的行为也适用于。这意味着我们必须重新绑定每个事件。组件的由调用它的父组件提供,这意味着所有事件都应该与父组件相关联。 原文链接:Vue.js — Considerations and Tricks showImg(https://segmentfault.com/img/bVbqHOd?w=1600&h=1599); Vue.js 是一个很棒的框架。然而,当你开始构建...
摘要:相反的,提供了全局作用域和局部作用域。组成界面的分子的样式可以通过元素选择器定位。元素选择器的优先级很低,因此他们不会覆盖任何基于类选择器的样式。使用元素选择器有以下优点避免了的冗长没有冗余的类。 最近学习到CSS的继承属性,正好看到这篇文章,便将它翻译出来。作者的思想,在平时的项目中或多或少都有用过,但是从来没有仔细去思考如何利用这些特性让代码更加优雅。 我热爱模块化设计。长期以来,...
摘要:经常处于随机级联样式表项目维护者的心血来潮之中,并且试图让项目编译过程中涉及的其他库,框架或预处理器经常成为一场噩梦。上下文中的预处理器基本上是构建过程中的一个步骤。 showImg(https://segmentfault.com/img/remote/1460000018822671?w=1920&h=960); 来源 | 愿码(ChainDesk.CN)内容编辑 愿码Sloga...
阅读 1522·2021-11-19 09:55
阅读 2746·2021-09-06 15:02
阅读 3474·2019-08-30 15:53
阅读 1001·2019-08-29 16:36
阅读 1180·2019-08-29 16:29
阅读 2254·2019-08-29 15:21
阅读 589·2019-08-29 13:45
阅读 2659·2019-08-26 17:15