Redian新闻
>
React Hooks 源码深度解析

React Hooks 源码深度解析

公众号新闻

来源 | OSCHINA 社区

作者 | 京东云开发者-京东零售 郑炳懿

原文链接:https://my.oschina.net/u/4090830/blog/8527395

前言

React Hooks 是 React16.8 引入的一个新特性,它允许函数组件中使用 state 和其他 React 特性,而不必使用类组件。Hooks 是一个非常重要的概念,因为它们提供了更简单、更易于理解的 React 开发体验。
React Hooks 的核心源码主要包括两个部分:React 内部的 Hook 管理器和一系列预置的 Hook 函数
首先,让我们看一下 React 内部的 Hook 管理器。这个管理器是 React 内部的一个重要机制,它负责管理组件中的所有 Hook,并确保它们在组件渲染期间以正确的顺序调用。

内部 Hook 管理器

示例:
const Hook = {
queue: [],
current: null,
};

function useState(initialState) {
const state = Hook.current[Hook.queue.length];
if (!state) {
Hook.queue.push({
state: typeof initialState === 'function' ? initialState() : initialState,
setState(value) {
this.state = value;
render();
},
});
}
return [state.state, state.setState.bind(state)];
}

function useHook(callback) {
Hook.current = {
__proto__: Hook.current,
};
try {
callback();
} finally {
Hook.current = Hook.current.__proto__;
}
}

function render() {
useHook(() => {
const [count, setCount] = useState(0);
console.log('count:', count);
setTimeout(() => {
setCount(count + 1);
}, 1000);
});
}

render();

在这个示例中,Hook 对象有两个重要属性:queue 和 currentqueue 存储组件中所有 Hook 的状态和更新函数,current 存储当前正在渲染的组件的 Hook 链表。useState 和 useHook 函数则分别负责创建新的 Hook 状态和在组件中使用 Hook

预置 Hook 函数

useState Hook

以下是 useState Hook 的实现示例
function useState(initialState) {
const hook = updateWorkInProgressHook();
if (!hook.memoizedState) {
hook.memoizedState = [
typeof initialState === 'function' ? initialState() : initialState,
action => {
hook.queue.pending = true;
hook.queue.dispatch = action;
scheduleWork();
},
];
}
return hook.memoizedState;
}

上述代码实现了 useState Hook,其主要作用是返回一个 state 和更新函数的数组,state 初始值为 initialState
在这个实现中,updateWorkInProgressHook() 函数用来获取当前正在执行的函数组件的 fiber 对象并判断是否存在对应的 hook。它的实现如下:
function updateWorkInProgressHook() {
const fiber = getWorkInProgressFiber();
let hook = fiber.memoizedState;
if (hook) {
fiber.memoizedState = hook.next;
hook.next = null;
} else {
hook = {
memoizedState: null,
queue: {
pending: null,
dispatch: null,
last: null,
},
next: null,
};
}
workInProgressHook = hook;
return hook;
}

getWorkInProgressFiber() 函数用来获取当前正在执行的函数组件的 fiber 对象,workInProgressHook 则用来存储当前正在执行的 hook 对象。在函数组件中,每一个 useState 调用都会创建一个新的 hook 对象,并将其添加到 fiber 对象的 hooks 链表中。这个 hooks 链表是通过 fiber 对象的 memoizedState 属性来维护的。
我们还需要注意到在 useState Hook 的实现中,每一个 hook 对象都包含了一个 queue 对象,用来存储待更新的状态以及更新函数。scheduleWork() 函数则用来通知 React 调度器有任务需要执行。
在 React 的源码中,useState 函数实际上是一个叫做 useStateImpl 的内部函数。
下面是 useStateImpl 的源码:
function useStateImpl<S>(initialState: (() => S) | S): [S, Dispatch<SetStateAction<S>>] {
const dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}

可以看到,useStateImpl 函数的作用就是获取当前的 dispatcher 并调用它的 useState 方法,返回一个数组,第一个元素是状态的值,第二个元素是一个 dispatch 函数,用来更新状态。这里的 resolveDispatcher 函数用来获取当前的 dispatcher,其实现如下:
function resolveDispatcher(): Dispatcher {
const dispatcher = currentlyRenderingFiber?.dispatcher;
if (dispatcher === undefined) {
throw new Error('Hooks can only be called inside the body of a function component. (https://fb.me/react-invalid-hook-call)');
}
return dispatcher;
}

resolveDispatcher 函数首先尝试获取当前正在渲染的 fiber 对象的 dispatcher 属性,如果获取不到则说
明当前不在组件的渲染过程中,就会抛出一个错误。
最后,我们来看一下 useState 方法在具体的 dispatcher 实现中是如何实现的。我们以 useReducer 的
dispatcher 为例,它的实现如下:
export function useReducer<S, A>(
reducer: (prevState: S, action: A) => S,
initialState: S,
initialAction?: A,
): [S, Dispatch<A>]
{
const [dispatch, currentState] = updateReducer<S, A>(
reducer,
// $FlowFixMe: Flow doesn't like mixed types
[initialState, initialAction],
// $FlowFixMe: Flow doesn't like mixed types
reducer === basicStateReducer ? basicStateReducer : updateStateReducer,
);
return [currentState, dispatch];
}

可以看到,useReducer 方法实际上是调用了一个叫做 updateReducer 的函数,返回了一个包含当前状态和 dispatch 函数的数组。updateReducer 的实现比较复杂,涉及到了很多细节,这里不再展开介绍。

useEffect Hook

useEffect 是 React 中常用的一个 Hook 函数,用于在组件中执行副作用操作,例如访问远程数据、添加 / 移除事件监听器、手动操作 DOM 等等。useEffect 的核心功能是在组件的渲染过程结束之后异步执行回调函数,它的实现方式涉及到 React 中的异步渲染机制。
以下是 useEffect Hook 的实现示例:
function useEffect(callback, dependencies) {
// 通过调用 useLayoutEffect 或者 useEffect 方法来获取当前的渲染批次
const batch = useContext(BatchContext);

// 根据当前的渲染批次判断是否需要执行回调函数
if (shouldFireEffect(batch, dependencies)) {
callback();
}

// 在组件被卸载时清除当前 effect 的状态信息
return () => clearEffect(batch);
}

在这个示例中,useEffect 接收两个参数:回调函数和依赖项数组。当依赖项数组中的任何一个值发生变化时,
React 会在下一次渲染时重新执行 useEffect 中传入的回调函数。
useEffect 函数的实现方式主要依赖于 React 中的异步渲染机制。当一个组件需要重新渲染时,React 会将所有的 state 更新操作加入到一个队列中,在当前渲染批次结束之后再异步执行这些更新操作,从而避免在同一个渲染批次中连续执行多次更新操作。
在 useEffect 函数中,我们通过调用 useContext(BatchContext) 方法来获取当前的渲染批次,并根据 shouldFireEffect 方法判断是否需要执行回调函数。在回调函数执行完毕后,我们需要通过 clearEffect 方法来清除当前 effect 的状态信息,避免对后续的渲染批次产生影响。

总结

总的来说,React Hooks 的实现原理并不复杂,它主要依赖于 React 内部的 fiber 数据结构和调度系统,通过这些机制来实现对组件状态的管理和更新。Hooks 能够让我们在函数组件中使用状态和其他 React 特性,使得函数组件的功能可以和类组件媲美。
除了 useStateuseEffect 等 hookReact 还有 useContext 等常用的 Hook。它们的实现原理也基本相似,都是利用 fiber 架构来实现状态管理和生命周期钩子等功能。
以上是 hook 简单实现示例,它们并不是 React 中实际使用的代码,但是可以帮助我们更好地理解 hook 的核心实现方式。


END



一场持续20年曾威胁Linux存亡的诉讼


这里有最新开源资讯、软件更新、技术干货等内容

点这里 ↓↓↓ 记得 关注✔ 标星⭐ 哦

微信扫码关注该文公众号作者

戳这里提交新闻线索和高质量文章给我们。
相关阅读
AP学生爬藤指南|深度解析AP体系的美本规划与申请!到底谁是凶手?IDC & 首都在线 | 渲染算力云2.0深度解析没有索引也能用SQL ?深度解析 SLS Schema-on-Read 分析原理与应用深度解析神秘的ChatGPT的插件!!!德国巴伐利亚州长接见第一波枫糖节抢鲜玩:Brooks Farm枫糖节活动本周开始!名利场||从CP粉取而代之成巨星妻子,深度解析北美意难忘三位这缠斗的14年(上)React官方网站更新,并启用新域名:react.dev重磅发布!深度解析2023美高录取最新趋势想训练ChatGPT?得先弄明白Reward Model怎么训(附源码)北上广是幻象,县城才是中国的底色 l 1.8万字深度解析中国县城现状CFA考生看过来:今晚带你深度解析一级成绩单!周末愉快 过小年【惠宜美高】恭喜两位惠宜学子补录Brooks School!源码中常见的 where 1=1 是一种高级优化技巧?圣诞节回忆我们的美国人生World Book Day: A Bookstore for the Blind拜年贴,我最喜欢的服装Hangzhou Plans Easing ‘Hukou’ Restrictions to Attract Talent"𝙇𝙚𝙖𝙙 𝙏𝙝𝙚 𝘾𝙝𝙖𝙧𝙜𝙚"广告#创译挑战今日直播|前埃森哲招生官带你深度解析《简历怎么写更容易拿面试》文末送书 | 入门SLAM优选好书!视觉惯性SLAM:理论与源码解析Brooks Farm枫糖节本周末开始,快来品尝甜甜的枫糖浆...精选SDE岗位 | MathWorks、Adobe 、Vectorworks发布新岗位!23年雄起!硅谷一线VC教你深度解析AIGC+生物科技新视界11月考生看过来:CFA二级成绩单深度解析!ThoughtWorks CTO:2025 年之前,我们会看到架构的演进,但不会看到革命名利场||从CP粉取而代之成巨星妻子,深度解析北美意难忘三位这缠斗的14年(下)重磅喜讯!!比肩藤校的顶级美本Barnard转正一枚!深度解析转正背后的酸甜苦辣!Kith23年春夏cookbook现已开售!模特竟是绝命毒师的秃头老白?素色体现高级搭配北上广是幻象,县城才是中国的底色 l 1.8万字深度解析中国县城现状三星 Galaxy Book 3 Ultra 笔记本真机曝光,对标苹果 MacBook Pro使用 BookStack 写文档,一个开源的 Confluence 替代品深度解析王老吉,百万小店如何成为品牌的新增长
logo
联系我们隐私协议©2024 redian.news
Redian新闻
Redian.news刊载任何文章,不代表同意其说法或描述,仅为提供更多信息,也不构成任何建议。文章信息的合法性及真实性由其作者负责,与Redian.news及其运营公司无关。欢迎投稿,如发现稿件侵权,或作者不愿在本网发表文章,请版权拥有者通知本网处理。