马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
概述
在Function Component项目中当我们必要操纵dom的时间,第一时间想到的就是使用useRef这个Hook来绑定dom。但是这个仅仅是使用这个Hook而已,为了更好的学习React Hooks内部实现原理,知其以是然。以是本文根据源码从useRef的底子使用场景一步一步到内部实现来对其举行先容。
根本使用
在React中useRef是如许界说的:useRef生存一个可变的长期化引用,重新渲染时不会重值,更新值也不会渲染页面。
- export function useRef<T>(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} type='text' />
- }
复制代码 源码剖析
由于这里的mount、update逻辑很简单,并当useRef转达值/函数和转达dom时的处置惩罚是不一样的,以是我们以此来分开先容。
转达平常值时
当转达平常值时(包罗恣意范例值、函数),告急实验mountRef、updateRef两个函数。在mount挂载时创建一个包罗current属性的对象,然后在更新时返回雷同的引用memoizedState生存的,以是这里就在一起写了。
- function mountRef<T>(initialValue: T): { current: T } {
- // 创建hook链表
- const hook = mountWorkInProgressHook();
- // ref初始化
- const ref = { current: initialValue };
- hook.memoizedState = ref;
- // 返回ref
- return ref;
- }
- function updateRef<T>(initialValue: T): { current: T } {
- // 复用hook
- const 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 {
- // 普通对象赋值到current
- ref.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中。
题外话 - 号外
本文也是根据这些文章学习举行梳理在本身明确的底子上誊写的,如有标题,还请指正。
|