From ec0f0dd2c4451eeb6de2486da9e98d9e1617a0d2 Mon Sep 17 00:00:00 2001 From: * <8> Date: Thu, 21 Apr 2022 14:54:34 +0800 Subject: [PATCH] Match-id-aad38a74387ab110540aeb594a643b1be7c42a17 --- .../src/components/ComponentInfo.tsx | 23 +- libs/extension/src/components/VTree.less | 2 +- libs/extension/src/injector/index.ts | 11 +- libs/extension/src/panel/App.tsx | 24 ++- libs/extension/src/parser/parseAttr.ts | 196 +++++++++++------- libs/horizon/src/renderer/hooks/HookType.ts | 1 + .../src/renderer/hooks/UseReducerHook.ts | 1 + libs/horizon/src/renderer/hooks/UseRefHook.ts | 1 + 8 files changed, 167 insertions(+), 92 deletions(-) diff --git a/libs/extension/src/components/ComponentInfo.tsx b/libs/extension/src/components/ComponentInfo.tsx index 7d78c814..f5ff0051 100644 --- a/libs/extension/src/components/ComponentInfo.tsx +++ b/libs/extension/src/components/ComponentInfo.tsx @@ -3,8 +3,9 @@ import Eye from '../svgs/Eye'; import Debug from '../svgs/Debug'; import Copy from '../svgs/Copy'; import Triangle from '../svgs/Triangle'; -import { useState } from 'horizon'; +import { useState, useEffect } from 'horizon'; import { IData } from './VTree'; +import { IAttr } from '../parser/parseAttr'; type IComponentInfo = { name: string; @@ -18,13 +19,6 @@ type IComponentInfo = { onClickParent: (item: IData) => void; }; -export type IAttr = { - name: string; - type: string; - value: string | boolean; - indentation: number; -} - function collapseAllNodes(attrs: IAttr[]) { return attrs.filter((item, index) => { const nextItem = attrs[index + 1]; @@ -34,6 +28,9 @@ function collapseAllNodes(attrs: IAttr[]) { function ComponentAttr({ name, attrs }: { name: string, attrs: IAttr[] }) { const [collapsedNode, setCollapsedNode] = useState(collapseAllNodes(attrs)); + useEffect(() => { + setCollapsedNode(collapseAllNodes(attrs)); + }, [attrs]); const handleCollapse = (item: IAttr) => { const nodes = [...collapsedNode]; const i = nodes.indexOf(item); @@ -64,7 +61,9 @@ function ComponentAttr({ name, attrs }: { name: string, attrs: IAttr[] }) { {hasChild && } {`${item.name}`} {' :'} - {item.value} + {item.type === 'string' || item.type === 'number' + ? {item.value} + : {item.value}} ); if (isCollapsed) { @@ -106,9 +105,9 @@ export default function ComponentInfo({ name, attrs, parents, onClickParent }: I
{context && } - {props && } - {state && } - {hooks && } + {props && props.length !== 0 && } + {state && state.length !== 0 && } + {hooks && hooks.length !== 0 && }
{name &&
parents: { diff --git a/libs/extension/src/components/VTree.less b/libs/extension/src/components/VTree.less index 0f34f9cd..a95f6986 100644 --- a/libs/extension/src/components/VTree.less +++ b/libs/extension/src/components/VTree.less @@ -6,7 +6,7 @@ .treeItem { width: 100%; position: absolute; - line-height: 18px; + line-height: 1.125rem; &:hover { background-color: @select-color; diff --git a/libs/extension/src/injector/index.ts b/libs/extension/src/injector/index.ts index 0d1ad023..e0372d10 100644 --- a/libs/extension/src/injector/index.ts +++ b/libs/extension/src/injector/index.ts @@ -10,7 +10,8 @@ import { } from './../utils/constants'; import { VNode } from './../../../horizon/src/renderer/vnode/VNode'; import { ClassComponent } from '../../../horizon/src/renderer/vnode/VNodeTags'; -import { parseAttr } from '../parser/parseAttr'; +import { parseAttr, parseHooks } from '../parser/parseAttr'; +import { FunctionComponent } from './../../../horizon/src/renderer/vnode/VNodeTags'; const roots = []; @@ -56,6 +57,14 @@ function parseCompAttrs(id: number) { parsedProps, parsedState, }); + } else if (tag === FunctionComponent) { + const { props, hooks } = vNode; + const parsedProps = parseAttr(props); + const parsedHooks = parseHooks(hooks); + postMessage(ComponentAttrs, { + parsedProps, + parsedHooks, + }); } } diff --git a/libs/extension/src/panel/App.tsx b/libs/extension/src/panel/App.tsx index a0455831..af6e5d9a 100644 --- a/libs/extension/src/panel/App.tsx +++ b/libs/extension/src/panel/App.tsx @@ -67,6 +67,8 @@ if (!isDev) { }); } +let reconnectTimes = 0; + function postMessage(type: string, data: any) { try { connection.postMessage(packagePayload({ @@ -75,14 +77,21 @@ function postMessage(type: string, data: any) { }, DevToolPanel)); } catch(err) { // 可能出现 port 关闭的场景,需要重新建立连接,增加可靠性 + if (reconnectTimes === 20) { + reconnectTimes = 0; + console.error('reconnect failed'); + return; + } console.error(err); + reconnectTimes++; + // 重建连接 connection = chrome.runtime.connect({ name: 'panel' }); - connection.postMessage(packagePayload({ - type: type, - data: data, - }, DevToolPanel)); + // 重新发送初始化消息 + postMessage(InitDevToolPageConnection, chrome.devtools.inspectedWindow.tabId); + // 初始化成功后才会重新发送消息 + postMessage(type, data); } } @@ -134,10 +143,11 @@ function App() { }, []); setParsedVNodeData(allTreeData); } else if (type === ComponentAttrs) { - const {parsedProps, parsedState} = data; + const {parsedProps, parsedState, parsedHooks} = data; setComponentAttrs({ - state: parsedProps, - props: parsedState, + props: parsedProps, + state: parsedState, + hooks: parsedHooks, }); } } diff --git a/libs/extension/src/parser/parseAttr.ts b/libs/extension/src/parser/parseAttr.ts index f710141e..e18f9bad 100644 --- a/libs/extension/src/parser/parseAttr.ts +++ b/libs/extension/src/parser/parseAttr.ts @@ -1,79 +1,133 @@ -import { IAttr } from "../components/ComponentInfo"; -// 将状态的值解析成固定格式 +import { Hook, Reducer, Ref } from './../../../horizon/src/renderer/hooks/HookType'; + +// 展示值为 string 的可编辑类型 +type editableStringType = 'string' | 'number' | 'undefined' | 'null'; +// 展示值为 string 的不可编辑类型 +type unEditableStringType = 'function' | 'symbol' | 'object' | 'map' | 'set' | 'array' + | 'dom' // 值为 dom 元素的 ref 类型 + | 'ref'; // 值为其他数据的 ref 类型 + +type showAsStringType = editableStringType | unEditableStringType; + + +export type IAttr = { + name: string; + indentation: number; + hIndex?: number; // 用于记录 hook 的 hIndex 值 +} & ({ + type: showAsStringType; + value: string; +} | { + type: 'boolean'; + value: boolean; +}) + +type showType = showAsStringType | 'boolean'; + +const parseSubAttr = ( + attr: any, + parentIndentation: number, + attrName: string, + result: IAttr[], + hIndex?: number) => { + const attrType = typeof attr; + let value: any; + let showType: showType; + let addSubState; + if (attrType === 'boolean' || + attrType === 'number' || + attrType === 'string' || + attrType === 'undefined') { + value = attr; + showType = attrType; + } else if (attrType === 'function') { + const funName = attr.name; + value = `f() ${funName}{}`; + } else if (attrType === 'symbol') { + value = attr.description; + } else if (attrType === 'object') { + if (attr === null) { + showType = 'null'; + } else if (attr instanceof Map) { + showType = 'map'; + const size = attr.size; + value = `Map(${size})`; + addSubState = () => { + attr.forEach((value, key) => { + parseSubAttr(value, parentIndentation + 2, key, result); + }); + }; + } else if (attr instanceof Set) { + showType = 'set'; + const size = attr.size; + value = `Set(${size})`; + addSubState = () => { + let i = 0; + attr.forEach((value) => { + parseSubAttr(value, parentIndentation + 2, String(i), result); + }); + i++; + }; + } else if (Array.isArray(attr)) { + showType = 'array'; + value = `Array(${attr.length})`; + addSubState = () => { + attr.forEach((value, index) => { + parseSubAttr(value, parentIndentation + 2, String(index), result); + }); + }; + } else if (attr instanceof Element) { + showType = 'dom'; + value = attr.tagName; + } else { + showType = attrType; + value = '{...}'; + addSubState = () => { + Object.keys(attr).forEach((key) => { + parseSubAttr(attr[key], parentIndentation + 2, key, result); + }); + }; + } + } + const item: IAttr = { + name: attrName, + type: showType, + value, + indentation: parentIndentation + 1, + }; + if (hIndex) { + item.hIndex = hIndex; + } + result.push(item); + if (addSubState) { + addSubState(); + } +}; + +// 将属性的值解析成固定格式,props 和 类组件的 state 必须是一个对象 export function parseAttr(rootAttr: any) { const result: IAttr[] = []; const indentation = 0; - const parseSubAttr = (attr: any, parentIndentation: number, attrName: string) => { - const stateType = typeof attr; - let value: any; - let showType; - let addSubState; - if (stateType === 'boolean' || - stateType === 'number' || - stateType === 'string' || - stateType === 'undefined') { - value = attr; - showType = stateType; - } else if (stateType === 'function') { - const funName = attr.name; - value = `f() ${funName}{}`; - } else if (stateType === 'symbol') { - value = attr.description; - } else if (stateType === 'object') { - if (attr === null) { - showType = 'null'; - } else if (attr instanceof Map) { - showType = 'map'; - const size = attr.size; - value = `Map(${size})`; - addSubState = () => { - attr.forEach((value, key) => { - parseSubAttr(value, parentIndentation + 2, key); - }); - }; - } else if (attr instanceof Set) { - showType = 'set'; - const size = attr.size; - value = `Set(${size})`; - addSubState = () => { - let i = 0; - attr.forEach((value) => { - parseSubAttr(value, parentIndentation + 2, String(i)); - }); - i++; - }; - } else if (Array.isArray(attr)) { - showType = 'array'; - value = `Array(${attr.length})`; - addSubState = () => { - attr.forEach((value, index) => { - parseSubAttr(value, parentIndentation + 2, String(index)); - }); - }; - } else { - showType = stateType; - value = '{...}'; - addSubState = () => { - Object.keys(attr).forEach((key) => { - parseSubAttr(attr[key], parentIndentation + 2, key); - }); - }; - } - } - - result.push({ - name: attrName, - type: showType, - value, - indentation: parentIndentation + 1, - }); - if (addSubState) { - addSubState(); - } - }; + if (typeof rootAttr === 'object' && rootAttr !== null) Object.keys(rootAttr).forEach(key => { - parseSubAttr(rootAttr[key], indentation, key); + parseSubAttr(rootAttr[key], indentation, key, result); + }); + return result; +} + +export function parseHooks(hooks: Hook[]) { + const result: IAttr[] = []; + const indentation = 0; + hooks.forEach(hook => { + const { hIndex, state ,type } = hook; + if (type === 'useState') { + parseSubAttr((state as Reducer).stateValue, indentation, 'state', result, hIndex); + } else if (type === 'useRef') { + parseSubAttr((state as Ref).current, indentation, 'ref', result, hIndex); + } else if (type === 'useReducer') { + parseSubAttr((state as Reducer).stateValue, indentation, 'reducer', result, hIndex); + } }); return result; } diff --git a/libs/horizon/src/renderer/hooks/HookType.ts b/libs/horizon/src/renderer/hooks/HookType.ts index e965fdf1..cb8be892 100644 --- a/libs/horizon/src/renderer/hooks/HookType.ts +++ b/libs/horizon/src/renderer/hooks/HookType.ts @@ -3,6 +3,7 @@ import {EffectConstant} from './EffectConstant'; export interface Hook { state: Reducer | Effect | Memo | CallBack | Ref; hIndex: number; + type?: 'useState' | 'useRef' | 'useReducer'; } export interface Reducer { diff --git a/libs/horizon/src/renderer/hooks/UseReducerHook.ts b/libs/horizon/src/renderer/hooks/UseReducerHook.ts index 52399713..480f43bb 100644 --- a/libs/horizon/src/renderer/hooks/UseReducerHook.ts +++ b/libs/horizon/src/renderer/hooks/UseReducerHook.ts @@ -87,6 +87,7 @@ export function useReducerForInit(reducer, initArg, init, isUseState?: boo } const hook = createHook(); + hook.type = isUseState ? 'useState' : 'useReducer'; // 为hook.state赋值{状态值, 触发函数, reducer, updates更新数组, 是否是useState} hook.state = { stateValue: stateValue, diff --git a/libs/horizon/src/renderer/hooks/UseRefHook.ts b/libs/horizon/src/renderer/hooks/UseRefHook.ts index 754a16d2..381ef61e 100644 --- a/libs/horizon/src/renderer/hooks/UseRefHook.ts +++ b/libs/horizon/src/renderer/hooks/UseRefHook.ts @@ -12,6 +12,7 @@ export function useRefImpl(value: V): Ref { if (stage === HookStage.Init) { hook = createHook(); hook.state = {current: value}; + hook.type = 'useRef'; } else if (stage === HookStage.Update) { hook = getCurrentHook(); }