PReact10.5.13源码理解之hook
options._diff = vnode => {
currentComponent = null;
if (oldBeforeDiff) oldBeforeDiff(vnode);
};
options._render = vnode => {
if (oldBeforeRender) oldBeforeRender(vnode);
currentComponent = vnode._component;
currentIndex = 0;
const hooks = currentComponent.__hooks;
if (hooks) {
hooks._pendingEffects.forEach(invokeCleanup);
hooks._pendingEffects.forEach(invokeEffect);
hooks._pendingEffects = [];
}
};
function getHookState(index, type) {
if (options._hook) {
options._hook(currentComponent, index, currentHook || type);
}
currentHook = 0; // 可能有别的用,目前在源码中没有看到用处
// Largely inspired by:
// * https://github.com/michael-klein/funcy.js/blob/f6be73468e6ec46b0ff5aa3cc4c9baf72a29025a/src/hooks/core_hooks.mjs
// * https://github.com/michael-klein/funcy.js/blob/650beaa58c43c33a74820a3c98b3c7079cf2e333/src/renderer.mjs
// Other implementations to look at:
// * https://codesandbox.io/s/mnox05qp8
const hooks = // 如果没有用过hook就在组件上添加一个__hooks属性
currentComponent.__hooks ||
(currentComponent.__hooks = {
_list: [],
_pendingEffects: []
});
// 如果index大于当前list长度就产生一个新的对象
// 所以除了useEffect外其他都不会用到_pendingEffects属性
if (index >= hooks._list.length) {
hooks._list.push({});
}
return hooks._list[index]; // 返回当前的hook state
}
export function useMemo(factory, args) {
/** @type {import('./internal').MemoHookState} */
const state = getHookState(currentIndex++, 7); // 获取一个hook的state
if (argsChanged(state._args, args)) { // 可以看到只有当参数改变时,hook的state会被重新修改;旧的参数被存储在state中
state._value = factory(); // 通过factory生成,如果args不变那么久不会执行factory
state._args = args;
state._factory = factory;
}
return state._value; // 返回状态值
}
export function useRef(initialValue) {
currentHook = 5;
// 可以看到useRef只是一个有current的一个对象;
return useMemo(() => ({ current: initialValue }), []);
}
export function useCallback(callback, args) {
currentHook = 8;
return useMemo(() => callback, args);
}
/**
* @param {any[]} oldArgs
* @param {any[]} newArgs
*/
function argsChanged(oldArgs, newArgs) {
return (
!oldArgs ||
oldArgs.length !== newArgs.length ||
newArgs.some((arg, index) => arg !== oldArgs[index])
);
}
/**
* @param {import('./internal').Effect} callback
* @param {any[]} args
*/
export function useEffect(callback, args) {
/** @type {import('./internal').EffectHookState} */
const state = getHookState(currentIndex++, 3);
if (!options._skipEffects && argsChanged(state._args, args)) {
state._value = callback;
state._args = args;
currentComponent.__hooks._pendingEffects.push(state);
}
}
/**
* @param {import('./internal').Effect} callback
* @param {any[]} args
*/
export function useLayoutEffect(callback, args) {
/** @type {import('./internal').EffectHookState} */
const state = getHookState(currentIndex++, 4);
if (!options._skipEffects && argsChanged(state._args, args)) {
state._value = callback;
state._args = args;
currentComponent._renderCallbacks.push(state);
}
}
options._commit = (vnode, commitQueue) => {
commitQueue.some(component => {
try {
component._renderCallbacks.forEach(invokeCleanup);
component._renderCallbacks = component._renderCallbacks.filter(cb =>
// 如果是useLayoutEffect产生的,就直接执行,否则返回true保证其他的renderCallbacks在正常的阶段执行
cb._value ? invokeEffect(cb) : true
);
} catch (e) {
commitQueue.some(c => {
if (c._renderCallbacks) c._renderCallbacks = [];
});
commitQueue = [];
options._catchError(e, component._vnode);
}
});
if (oldCommit) oldCommit(vnode, commitQueue);
};
options._render = vnode => {
if (oldBeforeRender) oldBeforeRender(vnode);
currentComponent = vnode._component;
currentIndex = 0;
const hooks = currentComponent.__hooks;
if (hooks) {
hooks._pendingEffects.forEach(invokeCleanup);
hooks._pendingEffects.forEach(invokeEffect);
hooks._pendingEffects = [];
}
};
options.diffed = vnode => {
if (oldAfterDiff) oldAfterDiff(vnode);
const c = vnode._component;
// 如果hooks中存在pendingEffects数组,那么就在渲染结束后执行
if (c && c.__hooks && c.__hooks._pendingEffects.length) {
afterPaint(afterPaintEffects.push(c));
}
currentComponent = previousComponent;
};
function afterPaint(newQueueLength) {
if (newQueueLength === 1 || prevRaf !== options.requestAnimationFrame) {
prevRaf = options.requestAnimationFrame;
(prevRaf || afterNextFrame)(flushAfterPaintEffects);
}
}
let HAS_RAF = typeof requestAnimationFrame == 'function';
function afterNextFrame(callback) {
const done = () => {
clearTimeout(timeout);
if (HAS_RAF) cancelAnimationFrame(raf);
setTimeout(callback);
};
const timeout = setTimeout(done, RAF_TIMEOUT);
let raf;
if (HAS_RAF) {
raf = requestAnimationFrame(done);
}
}
function flushAfterPaintEffects() {
afterPaintEffects.forEach(component => {
if (component._parentDom) { // 有父组件的组件才会进行,第一次渲染如果么有挂载到父组件可能不会执行
try {
component.__hooks._pendingEffects.forEach(invokeCleanup);
component.__hooks._pendingEffects.forEach(invokeEffect);
component.__hooks._pendingEffects = [];
} catch (e) {
component.__hooks._pendingEffects = [];
options._catchError(e, component._vnode);
}
}
});
afterPaintEffects = [];
}
options._render = vnode => {
if (oldBeforeRender) oldBeforeRender(vnode);
currentComponent = vnode._component;
currentIndex = 0;
const hooks = currentComponent.__hooks;
if (hooks) {
hooks._pendingEffects.forEach(invokeCleanup);
hooks._pendingEffects.forEach(invokeEffect);
hooks._pendingEffects = [];
}
};
- 如果设置第二个参数为空数组,这种情况下在diffed和_render中都会将pendingEffects进行清除,永远不会执行到清除函数。
- 当useEffect没有第二个参数,那么第一次渲染后options.diffed函数中的state._value执行,生成state._cleanup,清除pendingEffects;如果函数任意状态改变,在options._render阶段没有pendingEffects不会执行cleanup和state._value;在组件render阶段,state._value被重新改变,将state装入pendingEffects中;在options.diffed中执行invokeCleanup和invokeEffect
- 当useEffect设置第二个参数为非空数组,那么第一次渲染后options.diffed函数中的state._value执行,生成state._cleanup,清除pendingEffects;只有当useEffect的依赖项改变时(非依赖项变动不会执行该useEffect的清除函数),在options._render阶段没有pendingEffects不会执行cleanup和state._value;在组件render阶段,state._value被重新改变,将state装入pendingEffects中;在options.diffed中执行invokeCleanup和invokeEffect
action => {
// 通过action来执行reducer获取到下一个状态
const nextValue = hookState._reducer(hookState._value[0], action);
// 状态不等就进行重新赋值,并且触发渲染,新的渲染还是返回hookState._value,但是_value的值已经被修改了
if (hookState._value[0] !== nextValue) {
hookState._value = [nextValue, hookState._value[1]];
// 在diff/index.js中可以看到如果是函数组件没有render方法,那么会对PReact.Component进行实例化
// 这时候调用setState方法同样会触发组件的渲染流程
hookState._component.setState({});
}
}
export function useReducer(reducer, initialState, init) {
const hookState = getHookState(currentIndex++, 2);
hookState._reducer = reducer; // 挂载reducer
if (!hookState._component) { // hookState么有_component属性代表第一次渲染
hookState._value = [
!init ? invokeOrReturn(undefined, initialState) : init(initialState),
action => {
// 通过action来执行reducer获取到下一个状态
const nextValue = hookState._reducer(hookState._value[0], action);
// 状态不等就进行重新赋值,并且触发渲染,新的渲染还是返回hookState._value,但是_value的值已经被修改了
if (hookState._value[0] !== nextValue) {
hookState._value = [nextValue, hookState._value[1]];
// 在diff/index.js中可以看到如果是函数组件没有render方法,那么会对PReact.Component进行实例化
// 这时候调用setState方法同样会触发组件的渲染流程
hookState._component.setState({});
}
}
];
hookState._component = currentComponent;
}
return hookState._value;
}
而useState就很简单了,只是调用一下useReducer,
export function useState(initialState) { currentHook = 1; return useReducer(invokeOrReturn, initialState); } function invokeOrReturn(arg, f) { return typeof f == 'function' ? f(arg) : f; }
export function useContext(context) {
// create-context中返回的是一个context对象,得到provide对象
// Provider组件在diff时,判断没有render方法时,会先用Compoent来实例化一个对象
// 并将render方法设置为doRender,并将constructor指向newType(当前函数),在doRender中调用this.constructor方法
const provider = currentComponent.context[context._id];
const state = getHookState(currentIndex++, 9);
state._context = context; // 挂载到state的_context属性中
if (!provider) return context._defaultValue; // 如果么有provider永远返回context的初始值。
if (state._value == null) { // 初次渲染则将组件对provider进行订阅
state._value = true;
provider.sub(currentComponent);
}
return provider.props.value;
}
useContext使用示例:
import React, { useState ,,useContext, createContext} from 'react';
import './App.css';
// 创建一个 context
const Context = createContext(0)
// 组件一, useContext 写法
function Item3 () {
const count = useContext(Context);
return (
<div>{ count }</div>
)
}
function App () {
const [ count, setCount ] = useState(0)
return (
<div>
点击次数: { count }
<button onClick={() => { setCount(count + 1)}}>点我</button>
<Context.Provider value={count}>
{/* <Item1></Item1>
<Item2></Item2> */}
<Item3></Item3>
</Context.Provider>
</div>
)
}
export default App;
博客园我也真是服了,一个以技术为主的博客网站,竟然每次进入编辑器,插入代码功能只能用一次,第二次死活提交不上,必须关闭浏览器重新打开,我真特么服!!!!
这是TMD把人往掘金、简书、知乎、segmentfault上逼啊!!!!!!!!!!!!!!!!!!
PReact10.5.13源码理解之hook的更多相关文章
- PReact10.5.13源码理解
React源码看过几次,每次都没有坚持下来,索性学习一下PReact部分,网上讲解源码的不少,但是基本已经过时,所以自己来梳理下 render.js部分 import { EMPTY_OBJ, EMP ...
- .NET Core 3.0之深入源码理解Startup的注册及运行
原文:.NET Core 3.0之深入源码理解Startup的注册及运行 写在前面 开发.NET Core应用,直接映入眼帘的就是Startup类和Program类,它们是.NET Core应用程 ...
- HashMap源码理解一下?
HashMap 是一个散列桶(本质是数组+链表),散列桶就是数据结构里面的散列表,每个数组元素是一个Node节点,该节点又链接着多个节点形成一个链表,故一个数组元素 = 一个链表,利用了数组线性查找和 ...
- Caffe源码理解2:SyncedMemory CPU和GPU间的数据同步
目录 写在前面 成员变量的含义及作用 构造与析构 内存同步管理 参考 博客:blog.shinelee.me | 博客园 | CSDN 写在前面 在Caffe源码理解1中介绍了Blob类,其中的数据成 ...
- 基于SpringBoot的Environment源码理解实现分散配置
前提 org.springframework.core.env.Environment是当前应用运行环境的公开接口,主要包括应用程序运行环境的两个关键方面:配置文件(profiles)和属性.Envi ...
- jedis的源码理解-基础篇
[jedis的源码理解-基础篇][http://my.oschina.net/u/944165/blog/127998] (关注实现关键功能的类) 基于jedis 2.2.0-SNAPSHOT ...
- VUEJS2.0源码理解--优
VUEJS2.0源码理解 http://jiongks.name/blog/vue-code-review/#pingback-112428
- AdvanceEast源码理解
目录 文章思路 源码理解 一. 标签点形式 按顺序排列四个点,逆时针旋转,且第一个点为左上角点(刚开始选择最左边的点, 二. 标签切边 三. loss计算 四. NMS 最后说明 文章思路 大神的gi ...
- Pytorch学习之源码理解:pytorch/examples/mnists
Pytorch学习之源码理解:pytorch/examples/mnists from __future__ import print_function import argparse import ...
随机推荐
- You, Me & SVG!
You, Me & SVG! SVG refs code-school-you-me-svg https://www.youtube.com/watch?v=a8Y0L5q63y8 https ...
- nodejs package.json中的exports
test/package.json { "name": "test", "main": "index.js", &quo ...
- HDFS 01 - HDFS是什么?它的适用场景有哪些?它的架构是什么?
目录 1.HDFS 是什么 1.1 简单介绍 1.2 发展历史 2.HDFS 应用场景 2.1 适合的应用场景 2.2 不适合的应用场景 3.HDFS 的架构 4.NameNode 和 DataNod ...
- [转]ROS学习笔记十一:ROS中数据的记录与重放
本节主要介绍如何记录一个正在运行的ROS系统中的数据,然后在一个运行的系统中根据记录文件重新产生和记录时类似的运动情况.本例子还是以小海龟例程为例. 记录数据(创建一个bag文件) 首先运行小海龟例程 ...
- 020_CSS3
目录 如何学习CSS 什么是CSS 发展史 快速入门 css的优势 三种CSS导入方式 拓展:外部样式两种写法 选择器 基本选择器 层次选择器 结构伪类选择器 属性选择器 美化网页元素 为什么要美化网 ...
- js中this指向的问题与联系
前言 JavaScript 中最大的一个安全问题,也是最令人困惑的一个问题,就是在某些情况下this的值是如何确定的.有js基础的同学面对这个问题基本可以想到:this的指向和函数调用的方式相关.这当 ...
- Java内存模型(JMM)是什么?JMM 通过控制主内存与每个线程的本地内存之间的交互,来提供内存可见性保证
Java内存模型就是一种符合内存模型规范的,屏蔽了各种硬件和操作系统的访问差异的,保证了Java程序在各种平台下对内存的访问都能保证效果一致的机制及规范. Java内存模型是根据英文Java Memo ...
- oracle 导入导出dmp
exp 用户名/密码@地址:端口/serviceName file=D:\710.dmp exp test710/test710@192.168.15.134:1521/doit file=D:\71 ...
- 高级FTP
一.作业需求 1. 用户加密认证(已完成) 2. 多用户同时登陆(已完成) 3. 每个用户有自己的家目录且只能访问自己的家目录(已完成) 4. 对用户进行磁盘配额.不同用户配额可不同(已完成) 5 ...
- Codeforces Global Round 13
比赛地址 A(水题) 题目链接 题目: 给出一个\(01\)序列,有2种操作:1.将某个位置取反:2.询问\(01\)序列中第\(k\)大的数 解析: 显然维护1的数目即可 #include<b ...