React Hooks 调试

React Hooks 调试

使用Preact来代替React调试

Preact几乎与React完全相同,不过更加简洁

flowchart TD
    A[Package] --> src
    A --> hooks
    A --> compat

    B[package]--> root[react]
    B --> react-client
    B --> react-cache
    B --> react-devtools
    B --> react-dom
    B -->	scheduler
    B --> ......

前置工作

  1. 用webpack/vite搭一个前端工程
  2. 为了在调试过程中走preact源码,而不是dist,修改preact及其子包的package.json,将导出路径指向源码

cold task

  1. 有哪些hooks,常用的有useState、useEffect、useRef、useImperativeHandle

    此外还有:useReducer、useLayoutEffect、useContext、useMemo、useCallback…

    flowchart TD
    	useState ==> useReducer
    	useImperativeHandle ==> useLayoutEffect
    	useEffect
    	useRef ==> useMemo
    	useCallback ==> useMemo
    	useContext
  2. hooks是伴随着函数式组件出现的,class组件通过特定的几个函数作为组件的生命周期,函数式组件则通过useEffect,useLayoutEffect替代了。class组件的props和state存在实例上,而函数式组件是通过useState,数据实际上被存在dom属性上

调试

简单的例子

通常一个前端React项目的页面渲染从render(<App/>, document.querySelector('#roout')),其中App函数编译后的代码如下:

1
2
3
4
5
6
7
8
9
const App = () => {
  const [num, setNum] = (0,preact_hooks__WEBPACK_IMPORTED_MODULE_0__.useState)(1);
  return createVNode("div", {
    children: createVNode("button", {
      onClick: () => setNum(num + 1),
      children: [" ", num]
    })
  });
};

<App/>就是函数App执行后返回的数据。在上示代码中,可以关注到主要有两个函数useStatecreateVNode,我们首先来了解一下Vnode是什么,以及其内部结构。

VNode类似与React中的Fiber,让我们回到React中,相应的从createRoot函数开始向下找,可以找到createFiber

stateDiagram-v2
	
    createRoot

    state createRoot {
        [*] -->createContainer
        state createContainer {
           	[*] -->createFiberRoot
            state createFiberRoot {
               [*] --> createHostRootFiber
               state createHostRootFiber {
               [*] --> createFiber
               }
            }
        }
    }

所以可以理解为createVNode等同于createFiber,不过也有一点不同的地方,React中的createElement返回的是ReactElement,然后由ReactElement去调用createFiberFromElement,而Preact的策略是createElement直接返回VNode

FiberNode对比VNode

两者有一些类似的结构,不同的是有一些属性React是放在ReactElement上,而Preact并没有类似的Element结构,直接将Element和Fiber合并成VNode

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
function FiberNode(
  tag: WorkTag,
  pendingProps: mixed,
  key: null | string,
  mode: TypeOfMode,
) {
  // Instance
  this.tag = tag;
  this.key = key;
  this.elementType = null;
  this.type = null;
  this.stateNode = null;

  // Fiber
  this.return = null;
  this.child = null;
  this.sibling = null;
  this.index = 0;

  this.ref = null;

  this.pendingProps = pendingProps;
  this.memoizedProps = null;
  this.updateQueue = null;
  this.memoizedState = null;
  this.dependencies = null;

  this.mode = mode;

  // Effects
  this.flags = NoFlags;
  this.subtreeFlags = NoFlags;
  this.deletions = null;

  this.lanes = NoLanes;
  this.childLanes = NoLanes;

  this.alternate = null;
}


/** @type {VNode & { __source: any; __self: any }} */
const vnode = {
	type,
	props: normalizedProps,
	key,
	ref,
	_children: null,
	_parent: null,
	_depth: 0,
	_dom: null,
	_nextDom: undefined,
	_component: null,
	constructor: undefined,
	_original: --vnodeId,
	_index: -1,
	_flags: 0,
	__source,
	__self
};
0%