diff --git a/libs/extension/src/injector/index.ts b/libs/extension/src/injector/index.ts index ad8c5714..09f3b6de 100644 --- a/libs/extension/src/injector/index.ts +++ b/libs/extension/src/injector/index.ts @@ -6,11 +6,14 @@ import { RequestComponentAttrs, ComponentAttrs, DevToolHook, - DevToolContentScript, ModifyAttrs, ModifyHooks, ModifyState, + DevToolContentScript, + ModifyAttrs, + ModifyHooks, + ModifyState, + ModifyProps, } from '../utils/constants'; import { VNode } from '../../../horizon/src/renderer/vnode/VNode'; import { parseVNodeAttrs } from '../parser/parseAttr'; -import { Reducer } from '../../../horizon/src/renderer/hooks/HookType'; const roots = []; @@ -22,7 +25,7 @@ function addIfNotInclude(treeRoot: VNode) { function send() { const result = roots.reduce((pre, current) => { - const info = parseTreeRoot(current); + const info = parseTreeRoot(helper.travelVNodeTree ,current); pre.push(info); return pre; }, []); @@ -55,6 +58,34 @@ function parseCompAttrs(id: number) { postMessage(ComponentAttrs, parsedAttrs); } +function calculateNextValue(editValue, value, attrPath) { + let nextState; + const editValueType = typeof editValue; + if (editValueType === 'string' || editValueType === 'undefined' || editValueType === 'boolean') { + nextState = value; + } else if (editValueType === 'number') { + const numValue = Number(value); + nextState = isNaN(numValue) ? value : numValue; // 如果能转为数字,转数字,不能转数字,用原值 + } else if(editValueType === 'object') { + if (editValue === null) { + nextState = value; + } else { + const newValue = Array.isArray(editValue) ? [...editValue] : {...editValue}; + // 遍历读取到直接指向需要修改值的对象 + let attr = newValue; + for(let i = 0; i < attrPath.length - 1; i++) { + attr = attr[attrPath[i]]; + } + // 修改对象上的值 + attr[attrPath[attrPath.length - 1]] = value; + nextState = newValue; + } + } else { + console.error('The devTool tried to edit a non-editable value, this is a bug, please report', editValue); + } + return nextState; +} + function modifyVNodeAttrs(data) { const {type, id, value, path} = data; const vNode = queryVNode(id); @@ -62,49 +93,39 @@ function modifyVNodeAttrs(data) { console.error('Do not find match vNode, this is a bug, please report us'); return; } - if (type === ModifyHooks) { + if (type === ModifyProps) { + const nextProps = calculateNextValue(vNode.props, value, path); + helper.updateProps(vNode, nextProps); + } else if (type === ModifyHooks) { const hooks = vNode.hooks; const editHook = hooks[path[0]]; - if ((editHook.state as Reducer).trigger) { - const editState = editHook.state as Reducer; - const editValue = editState.stateValue; - const editValueType = typeof editValue; - if (editValueType === 'string') { - editState.trigger(value); - } else if (editValueType === 'number') { - const numValue = Number(value); - const targetValue = isNaN(numValue) ? value : numValue; // 如果能转为数字,转数字,不能转数字,用原值 - editState.trigger(targetValue); - } else if(editValueType === 'object') { - if (editValue === null) { - editState.trigger(value); - } else { - const newValue = {...editValue}; - // 遍历读取到直接指向需要修改值的对象 - const attrPath = path.slice(1); - let attr = newValue; - for(let i = 0; i < attrPath.length - 1; i++) { - attr = attr[attrPath[i]]; - } - // 修改对象上的值 - attr[attrPath[attrPath.length - 1]] = value; - editState.trigger(newValue); - } - } + const hookInfo = helper.getHookInfo(editHook); + if (hookInfo) { + const editValue = hookInfo.value; + // path 的第一个值指向 hIndex,从第二个值才开始指向具体属性访问路径 + const nextState = calculateNextValue(editValue, value, path.slice(1)); + helper.updateHooks(vNode, path[0], nextState); + } else { + console.error('The devTool tried to edit a non-editable hook, this is a bug, please report', hooks); } } else if (type === ModifyState) { - const instance = vNode.realNode; - const oldState = instance.state || {}; - const nextState = Object.assign({}, oldState); + const oldState = vNode.state || {}; + const nextState = {...oldState}; let accessRef = nextState; for(let i = 0; i < path.length - 1; i++) { accessRef = accessRef[path[i]]; } accessRef[path[path.length - 1]] = value; - instance.setState(nextState); + helper.updateState(vNode, nextState); } } +let helper; + +function init(horizonHelper) { + helper = horizonHelper; +} + function injectHook() { if (window.__HORIZON_DEV_HOOK__) { return; @@ -112,6 +133,7 @@ function injectHook() { Object.defineProperty(window, '__HORIZON_DEV_HOOK__', { enumerable: false, value: { + init, addIfNotInclude, send, deleteVNode, diff --git a/libs/extension/src/parser/parseVNode.ts b/libs/extension/src/parser/parseVNode.ts index 314e80e4..a70e7189 100644 --- a/libs/extension/src/parser/parseVNode.ts +++ b/libs/extension/src/parser/parseVNode.ts @@ -1,4 +1,3 @@ -import { travelVNodeTree } from '../../../horizon/src/renderer/vnode/VNodeUtils'; import { VNode } from '../../../horizon/src/renderer/vnode/VNode'; import { ClassComponent, FunctionComponent } from '../../../horizon/src/renderer/vnode/VNodeTags'; @@ -32,7 +31,7 @@ function getParentUserComponent(node: VNode) { return parent; } -function parseTreeRoot(treeRoot: VNode) { +function parseTreeRoot(travelVNodeTree, treeRoot: VNode) { const result: any[] = []; travelVNodeTree(treeRoot, (node: VNode) => { const tag = node.tag; @@ -57,7 +56,7 @@ function parseTreeRoot(treeRoot: VNode) { VNodeToIdMap.set(node, id); IdToVNodeMap.set(id, node); } - }, null, treeRoot, null); + }); return result; } diff --git a/libs/horizon/index.ts b/libs/horizon/index.ts index cd194973..7004f914 100644 --- a/libs/horizon/index.ts +++ b/libs/horizon/index.ts @@ -13,6 +13,7 @@ import { createContext } from './src/renderer/components/context/CreateContext'; import { lazy } from './src/renderer/components/Lazy'; import { forwardRef } from './src/renderer/components/ForwardRef'; import { memo } from './src/renderer/components/Memo'; +import './src/external/devtools'; import { useCallback, diff --git a/libs/horizon/src/external/devtools.ts b/libs/horizon/src/external/devtools.ts new file mode 100644 index 00000000..c9dcba9a --- /dev/null +++ b/libs/horizon/src/external/devtools.ts @@ -0,0 +1,53 @@ +import { travelVNodeTree } from '../renderer/vnode/VNodeUtils'; +import { Hook, Reducer, Ref } from '../renderer/hooks/HookType'; +import { VNode } from '../renderer/vnode/VNode'; +import { launchUpdateFromVNode } from '../renderer/TreeBuilder'; + +export const helper = { + travelVNodeTree: (rootVNode, fun) => { + travelVNodeTree(rootVNode, fun, null, rootVNode, null); + }, + // 获取 hook 名,hIndex值和存储的值 + // 目前只处理 useState和useRef + getHookInfo:(hook: Hook) => { + const { hIndex, state } = hook; + if ((state as Reducer).trigger) { + if ((state as Reducer).isUseState) { + return {name: 'state', hIndex, value: (state as Reducer).stateValue}; + } + } else if ((state as Ref).current) { + return {name: 'ref', hIndex, value: (state as Ref).current}; + } + return null; + }, + updateProps: (vNode: VNode, props: any) =>{ + vNode.devProps = props; + launchUpdateFromVNode(vNode); + }, + updateState: (vNode: VNode, nextState) => { + const instance = vNode.realNode; + instance.setState(nextState); + }, + updateHooks: (vNode: VNode, hIndex, nextState) => { + const hooks = vNode.hooks; + if (hooks) { + const editHook = hooks[hIndex]; + const editState = editHook.state as Reducer; + // 暂时只支持更新 useState 的值 + if (editState.trigger && editState.isUseState) { + editState.trigger(nextState); + } + } else { + console.error('Target vNode is not a hook vNode: ', vNode); + } + }, +}; + +function injectUpdater() { + const hook = window.__HORIZON_DEV_HOOK__; + if (hook) { + hook.init(helper); + } +} + +injectUpdater(); diff --git a/libs/horizon/src/renderer/TreeBuilder.ts b/libs/horizon/src/renderer/TreeBuilder.ts index 394319e0..8fff0435 100644 --- a/libs/horizon/src/renderer/TreeBuilder.ts +++ b/libs/horizon/src/renderer/TreeBuilder.ts @@ -236,7 +236,11 @@ function buildVNodeTree(treeRoot: VNode) { // 重置环境变量,为重新进行深度遍历做准备 resetProcessingVariables(startVNode); - + // devProps 用于插件手动更新props值 + if (startVNode.devProps !== undefined) { + startVNode.props = startVNode.devProps; + startVNode.devProps = undefined; + } while (processing !== null) { try { while (processing !== null) { diff --git a/libs/horizon/src/renderer/vnode/VNode.ts b/libs/horizon/src/renderer/vnode/VNode.ts index 29fb008b..812d2b8e 100644 --- a/libs/horizon/src/renderer/vnode/VNode.ts +++ b/libs/horizon/src/renderer/vnode/VNode.ts @@ -70,7 +70,7 @@ export class VNode { oldRef: RefType | ((handle: any) => void) | null = null; oldChild: VNode | null = null; promiseResolve: boolean; // suspense的promise是否resolve - + devProps: any; // 用于dev插件临时保存更新props值 suspenseState: SuspenseState; path = ''; // 保存从根到本节点的路径