当前位置: 首页 > Web前端 > vue.js

ReactHooks的实现、由来及解决的问题

时间:2023-03-31 20:28:31 vue.js

React函数式组件与React类组件相比有何不同?一般的回答是:类组件比函数式组件有更多的特性,比如state,如果有Hooks呢?功能组件比类组件表现更好,但在现代浏览器中,闭包和类的原始性能只会在极端情况下产生显着差异。性能主要取决于代码的作用,而不是通过选择功能或类组件。尽管优化策略不同,但性能差异可以忽略不计。参考官网??:(https://zh-hans.reactjs.org/d...参考作者的github:(https://github.com/ryardley/h...而下面会重点介绍:React的函数式组件类组件和类组件的根本区别:在心智模型上。从函数式组件的简单案例开始就有了,但经常被忽视:Functioncomponentscapturetherenderedvalues.(函数组件捕获渲染的值。)考虑这个组件:functionProfilePage(props){constshowMessage=()=>alert('Hello'+props.user);consthandleClick=()=>setTimeout(showMessage,3000);returnFollow}上面的组件:如果props.user是Dan,它会在三秒后显示HelloDan。如果是类组件,我们怎么写呢?一个简单的重构可能看起来像这样:handleClick=()=>setTimeout(this.showMessage,3000);render(){returnFollow;}}通常我们认为这两个代码片段是等价的。人们经常在这两种模式中随意重构代码,但很少注意它们的含义:我们通过React应用程序中的一个常见错误来说明差异。我们添加一个父组件,使用下拉框来改变传递给子组件(ProfilePage)的props.user,实例地址:(https://codesandbox.io/s/pjqn...。按照步骤操作完成以下操作:点击其中一个Follow按钮,3秒内切换选中的账号,查看弹窗文字,此时会出现奇怪的结果:在使用功能组件实现的ProfilePage时,点击当前账号为Dan时关注按钮,然后立即切换当前账号为Sophie,弹窗文字仍为'FollowedDan'。使用类组件实现的ProfilePage时,弹窗文字为'FollowedSophie':在这个例子中,函数组件是正确的。如果我关注了一个人,然后导航到另一个人的帐户,我的组件应该不会混淆我关注的人,而类组件的实现显然是错误的。案例分析那么类组件为什么会有行为在我们的例子中是这样的吗?让我们仔细看看类组件中的showMessage方法:showMessage=()=>{alert('Followed'+this.props.user);};该类方法从this.props.user中读取数据。在React中Props是不可变的(immutable),所以它们永远不会改变。这是并且永远是可变的。**这也是类组件this存在的意义:在渲染方法和生命周期方法中可以获得最新的实例。因此,如果我们的组件在发出请求时重新呈现,则this.props将会更改。showMessage方法从“太新”的道具中获取用户。从this读取数据,调用回调函数读取this.props超时的行为会使showMessage回调没有“绑定”到任何特定的渲染,因此它“丢失”了正确的props。.如何使用类组件解决上述BUG?(假设功能组件不存在)我们想以某种方式“修复”使用正确道具进行渲染与读取这些道具的showMessage回调之间的联系。道具在某处丢失了。方法一:调用事件前读取this.props,然后显式传给超时回调函数:classProfilePageextendsReact.Component{showMessage=(user)=>alert('Followed'+user);handleClick=()=>{const{user}=this.props;setTimeout(()=>this.showMessage(user),3000);};render(){returnFollowbutton>;但是,这种方法使代码明显更加冗长。如果我们需要不止一个道具怎么办?如果我们还需要访问状态怎么办?如果showMessage调用另一个方法,并且该方法读取this.props.something或this.state.something,我们将再次遇到同样的问题。然后我们必须将this.props和this.state作为函数参数传递给showMessage调用的每个方法。这样做会破坏课程提供的工程。记住传递的变量或执行也很困难,这就是为什么人们总是在研究错误。这个问题可以在任何将数据放入像这样的可变对象的UI库中重现(不仅在React中)。方法二:如果我们可以使用JavaScript闭包那么问题就迎刃而解了。*如果您在特定渲染中捕获用于该渲染的道具或状态,您会发现它们将始终保持一致,正如您所期望的那样:constshowMessage=()=>{alert('已关注'+props.user);};consthandleClick=()=>{setTimeout(showMessage,3000);};返回Follow;}}你在渲染时“捕获”了道具:。这样,其中的任何代码(包括showMessage)都可以保证获得用于此特定渲染的道具。Hooks的由来But:如果在render方法中定义各种函数,而不是使用类方法,那么使用类的意义何在?事实上,我们可以通过去掉类的“包装”来简化代码:};consthandleClick=()=>{setTimeout(showMessage,3000);};return(Follow);}像上面一样,道具仍然被捕获-React将它们作为参数传递。与此不同的是,道具对象本身不会被React更改。当父组件用不同的props渲染ProfilePage时,React会再次调用ProfilePage函数。但是我们的点击事件处理器“属于”之前的render有自己的user值,showMessage回调函数也可以读取到这个值。它们都完好无损。这就是为什么在上面的函数式版本中,点击关注账户1,然后将选择更改为账户2,仍然弹出“关注账户1”:函数组件捕获用于渲染的值。对于Hooks,同样的原则适用于状态。看这个例子:functionMessageThread(){const[message,setMessage]=useState('');constshowMessage=()=>{alert('你说:'+message);};常量handleSendClick=()=>{setTimeout(showMessage,3000);};常量handleMessageChange=(e)=>{setMessage(e.target.value);};return<>Send;}如果我发送特定消息,组件不应该混淆哪个消息实际上已发送。此功能组件的消息变量捕获“属于”浏览器调用的点击处理程序的渲染。因此,当我单击“发送”时,消息设置为当时在输入中输入的内容。读取最新状态所以我们知道,React中的函数默认捕获props和状态。但是如果我们想读取不属于这个特定渲染的最新道具和状态怎么办?如果我们想[“从未来读取它们”]怎么办?在课堂上,您可以通过阅读this.props或this.state来做到这一点,因为this本身是可变的。React改变了这一点。在功能组件中,您还可以拥有一个在组件的所有渲染帧之间共享的可变变量。它叫做“ref”:functionMyComponent(){constref=useRef(null);但是,您必须自己管理它。ref扮演与实例字段相同的角色。这是进入可变命令世界的后门。您可能熟悉“DOM引用”,但引用在概念上更为通用。它只是一个可以放东西的盒子。即使在视觉上,this.something也是something.current的镜像。它们代表同一个概念。默认情况下,React不会为功能组件中最新的props和state创建refs。在许多情况下,您不需要它们,分配它们是一种浪费。但是,如果你愿意,你可以像这样手动跟踪这些值:functionMessageThread(){const[message,setMessage]=useState('');constlatestMessage=useRef('');constshowMessage=()=>{alert('你说:'+latestMessage.current);};常量handleSendClick=()=>{setTimeout(showMessage,3000);};consthandleMessageChange=(e)=>{setMessage(e.target.value);latestMessage.current=e.target.value;};如果我们在showMessage中读取消息,我们将在按下发送按钮的那一刻收到消息。但是当我们读取latestMessage.current时,我们将获得最新的值——即使我们在按下发送按钮后继续输入也是如此。refs是一种“选择退出”渲染一致性的方法,在某些情况下这很方便。通常,您应该避免在渲染期间读取或设置引用,因为它们是可变的。我们希望保持渲染的可预测性。但是,如果我们想要特定prop或state的最新值,手动更新ref可能会有点烦人。我们可以通过使用效果来自动执行此操作:functionMessageThread(){const[message,setMessage]=useState('');constlatestMessage=useRef('');useEffect(()=>{latestMessage.current=message;});constshowMessage=()=>{alert('你说:'+latestMessage.current);};我们在effect内部进行赋值,这样ref的值只会在DOM更新后发生变化。这确保我们的变量突变不会破坏依赖于可中断渲染的时间切片和Suspense等功能。通常没有必要使用这样的参考。捕获道具和状态通常是更好的默认设置。但是,在处理间隔和订阅等命令式API时,refs会派上用场。您可以像这样跟踪任何值-道具、状态变量、整个道具对象,甚至是函数。这种模式对于优化也很方便——例如当useCallback本身经常变化时。但是,使用减速器通常是更好的解决方案。闭包帮助我们解决难以察觉的细微问题。此外,它们还可以更轻松地编写在并发模式下正确运行的代码。这是有效的,因为组件内部的逻辑在渲染它时捕获并包含正确的道具和状态。函数捕获它们的属性和状态——因此它们的身份同样重要。这不是错误,而是功能组件的特性。例如,不应将函数从useEffect或useCallback的“依赖项数组”中排除。(正确的解决方案通常是使用上面提到的useReducer或useRef。)当我们在函数中编写大部分React代码时,我们需要调整我们对优化代码以及哪些变量会随时间变化的知识和直觉。到目前为止,我发现关于钩子的最佳原则是“编写代码时就好像任何值都可以随时更改一样”。React函数总是捕获它们的值——现在我们知道为什么了。文章参考:React作者DanAbramov的github最后,译者写了一个React+Hooks的UI库,方便大家学习使用。课”,认真学习前端,共同进步。