网站制作中搜索栏怎么做6,腾讯建站平台官网,2023年最建议买的手机,杭州网站建设开发概述
在Function Component项目中当我们需要操作dom的时候#xff0c;第一时间想到的就是使用useRef这个Hook来绑定dom。但是这个仅仅是使用这个Hook而已#xff0c;为了更好的学习React Hooks内部实现原理#xff0c;知其所以然。所以本文根据源码从useRef的基础使用场景一…概述
在Function Component项目中当我们需要操作dom的时候第一时间想到的就是使用useRef这个Hook来绑定dom。但是这个仅仅是使用这个Hook而已为了更好的学习React Hooks内部实现原理知其所以然。所以本文根据源码从useRef的基础使用场景一步一步到内部实现来对其进行介绍。
基本使用
在React中useRef是这样定义的useRef保存一个可变的持久化引用重新渲染时不会重值更新值也不会渲染页面。
export function useRefT(initialValue: T): { current: T } {const dispatcher resolveDispatcher();return dispatcher.useRef(initialValue);
}由代码能看出useRef接收任意类型的值包含普通值、函数、dom然后经过dispather进行派发处理返回一个包含current属性的对象引用该对象和普通Js对象一致更新不收React约束。
一般在项目中useRef常用的有两个使用场景
通过useRef保持持久化的值且不需要重新渲染通过useRef绑定dom以便直接进行dom操作
比如在项目中常用的定时器我们都会在组件销毁时通过clear函数进行定时器的清除避免内存泄露等问题这时候就可以通过useRef来绑定timerId
import { useRef } from react;let timerId useRef(null);useEffect(() {timerId.current setInterval(() {console.log(setInterval);}, 1000);return () {clearInterval(timerId.current);}
}, [])export default function Counter() {return /
}当我们需要进行dom操作时比如获取焦点、自动滚动等就可以通过useRef来绑定dom进行操作
import { useRef } from react;let inputRef useRef(null);useEffect(() {// 在组件挂载后聚焦输入框inputRef.current.focus();
}, [])export default function Counter() {return input ref{inputRef} typetext /
}源码解析
由于这里的mount、update逻辑很简单并当useRef传递值/函数和传递dom时的处理是不一样的所以我们以此来分开介绍。
传递普通值时
当传递普通值时包含任意类型值、函数主要执行mountRef、updateRef两个函数。在mount挂载时创建一个包含current属性的对象然后在更新时返回相同的引用memoizedState保存的所以这里就在一起写了。
function mountRefT(initialValue: T): { current: T } {// 创建hook链表const hook mountWorkInProgressHook();// ref初始化const ref { current: initialValue };hook.memoizedState ref;// 返回refreturn ref;
}function updateRefT(initialValue: T): { current: T } {// 复用hookconst hook updateWorkInProgressHook();// 返回相同引用return hook.memoizedState;
}从源码能看出useRef接收一个初始化参数可以为值/返回值的函数然后在mountRef中创建了一个包含current的对象在updateRef中仍然返回的该对象引用。 如果初始值是函数因为React内部不会做判断直接将初始值赋予current如何是函数则需要手动显式调用 由于不管在mount挂载时还是在update更新时都是返回的对象引用以此来保持持久化当我们通过ref.current修改值时本质修改的是同一个引用对象所以也不会触发重新渲染(object.is对比一直都是true)。
传递DOM时
当传递DOM时在mount、update阶段也和传值一样不会做任何处理会返回相应的对象引用但是如果传递的是DOM时在Reconciler协调器中通过React.createElement将JSX转换为React元素后进行fiber构造在构造完成生产fiber树之后会进入到commit阶段在该阶段会遍历节点对副作用和ref进行处理其中在layout阶段会判断当前节点类型(tag)如何是dom(tag HostComponent)时如果该dom有ref则会对ref进行处理commitAttachRef函数 在commit阶段即renderer阶段针对dom的不同状态和处理分为了三个阶段 Before Mutation、Mutation、Layout。有兴趣的可以查看这篇文章【React源码 - Fiber架构之Renderer】 以下commitAttachRef代码省略了部分代码
function commitAttachRef(finishedWork: Fiber) {// 获取节点的ref属性const ref finishedWork.ref;if (ref ! null) {// 获取dom实例fiber.stateNode就是绑定的dom,在completeWork中会创建dom然后绑定到fiber.stateNode上const instance finishedWork.stateNode;let instanceToUse;switch (finishedWork.tag) {case HostHoistable:case HostSingleton:case HostComponent:// 获取dom实例instanceToUse getPublicInstance(instance);break;default:instanceToUse instance;}//if (typeof ref function) {// 将dom实例回传给传递的ref函数finishedWork.refCleanup ref(instanceToUse);} else {// 普通对象赋值到currentref.current instanceToUse;}}
}从代码能看出该函数主要就是获取ref绑定的dom实例然后根据传入ref的不同进行处理如果是函数则将dom实例传递给函数由开发者显式调用否则则绑定到current属性上进行返回。
传递函数显式处理ref的demo
import React, { useEffect, useRef } from react;function App() {const divRef useRef(null);useEffect(() {if (divRef.current) {console.log(Element mounted:, divRef.current);}return () {console.log(Element unmounted:, divRef.current);};}, []);return div ref{divRef}Hello, World!/div;
}export default App;
总结
基于以上了解我们知道了useRef的基础使用和场景以及背后的代码处理简要总结一下就是useRef用于持久化引用返回普通Js引用修改其值不会导致组件重新渲染。当传递普通值时不会进行特殊处理只是返回相同的对象引用。当绑定dom时在mount、update阶段初始化对象然后在commit阶段进行ref处理函数显式处理则会将dom实例作为参数回传普通值则会绑定到ref.current中。