Match-id-760cbbb23b2babb6aab29450b9834f032fec4914

This commit is contained in:
* 2022-11-30 23:00:15 +08:00
parent 26d41bf933
commit 0b11860c9d
8 changed files with 140 additions and 29 deletions

View File

@ -6,3 +6,5 @@ export const ACTION = 'horizonx action';
export const ACTION_QUEUED = 'horizonx action queued';
export const QUEUE_PENDING = 'horizonx queue pending';
export const QUEUE_FINISHED = 'horizonx queue finished';
export const RENDER_TRIGGERED = 'horizonx render triggered';
export const OBSERVED_COMPONENTS = 'horizonx observed components';

View File

@ -1,5 +1,12 @@
import { getStore, getAllStores } from '../store/StoreHandler';
import { OBSERVED_COMPONENTS } from './constants';
const sessionId = Date.now();
export function isPanelActive() {
return window['__HORIZON_DEV_HOOK__'];
}
function makeStoreSnapshot({ type, data }) {
const expanded = {};
Object.keys(data.store.$c).forEach(key => {
@ -40,9 +47,12 @@ function makeProxySnapshot(obj) {
}
export const devtools = {
getVNodeId: vNode => {
if (!isPanelActive()) return;
getVNodeId(vNode);
},
emit: (type, data) => {
if (!window['__HORIZON_DEV_HOOK__']) return;
console.log('store snapshot:', makeStoreSnapshot({ type, data }));
if (!isPanelActive()) return;
window.postMessage({
type: 'HORIZON_DEV_TOOLS',
payload: makeStoreSnapshot({ type, data }),
@ -50,3 +60,58 @@ export const devtools = {
});
},
};
function getAffectedComponents() {
const allStores = getAllStores();
const keys = Object.keys(allStores);
let res = {};
keys.forEach(key => {
const subRes = new Set();
const process = Array.from(allStores[key].$config.state._horizonObserver.keyVNodes.values());
while (process.length) {
let pivot = process.shift();
if (pivot?.tag) subRes.add(pivot);
if (pivot?.toString() === '[object Set]') Array.from(pivot).forEach(item => process.push(item));
}
res[key] = Array.from(subRes).map(vnode => {
return {
name: vnode?.type
.toString()
.replace(/\{.*\}/gms, '{...}')
.replace('function ', ''),
nodeId: window.__HORIZON_DEV_HOOK__.getVnodeId(vnode),
};
});
});
return res;
}
window.addEventListener('message', messageEvent => {
if (messageEvent.data.payload.type === 'horizonx request observed components') {
// get observed components
setTimeout(() => {
window.postMessage({
type: 'HORIZON_DEV_TOOLS',
payload: { type: OBSERVED_COMPONENTS, data: getAffectedComponents() },
from: 'dev tool hook',
});
}, 100);
}
if (messageEvent.data.payload.type === 'horizonx executue action') {
const data = messageEvent.data.payload.data;
const store = getStore(data.storeId);
if (!store?.[data.action]) {
}
const action = store[data.action];
const params = data.params;
action(...params).bind(store);
}
});
export function getVNodeId(vNode) {
window['__HORIZON_DEV_HOOK__'].send();
return window['__HORIZON_DEV_HOOK__'].getVnodeId(vNode);
}

View File

@ -16,6 +16,7 @@
import { launchUpdateFromVNode } from '../../renderer/TreeBuilder';
import { getProcessingVNode } from '../../renderer/GlobalVar';
import { VNode } from '../../renderer/vnode/VNode';
import { devtools } from '../devtools';
export interface IObserver {
useProp: (key: string) => void;
@ -89,7 +90,8 @@ export class Observer implements IObserver {
this.triggerUpdate(vNode);
});
this.triggerChangeListeners(mutation);
// NOTE: mutations are different in dev and production.
this.triggerChangeListeners({ mutation, vNodes });
}
triggerUpdate(vNode: VNode): void {
@ -109,8 +111,27 @@ export class Observer implements IObserver {
this.listeners = this.listeners.filter(item => item != listener);
}
triggerChangeListeners(mutation: any): void {
this.listeners.forEach(listener => listener(mutation));
triggerChangeListeners({ mutation, vNodes }): void {
const nodesList = vNodes ? Array.from(vNodes) : [];
this.listeners.forEach(listener =>
listener({
mutation,
vNodes: nodesList.map(vNode => {
let realNode = vNode.realNode;
let searchedNode = vNode;
while (!realNode) {
searchedNode = searchedNode.child;
realNode = searchedNode.realNode;
}
return {
type: vNode?.type?.name,
id: devtools.getVNodeId(vNode),
path: vNode.path,
element: realNode?.outerHTML?.substr(0, 100),
};
}),
})
);
}
// 触发所有使用的props的VNode更新

View File

@ -27,7 +27,7 @@ const proxyMap = new WeakMap();
export const hookObserverMap = new WeakMap();
export function createProxy(rawObj: any, isHookObserver = true): any {
export function createProxy(rawObj: any, id, isHookObserver = true): any {
// 不是对象(是原始数据类型)不用代理
if (!(rawObj && isObject(rawObj))) {
return rawObj;

View File

@ -17,6 +17,7 @@ import { getObserver } from '../ProxyHandler';
import { isSame, isValidIntegerKey } from '../../CommonUtils';
import { get as objectGet } from './ObjectProxyHandler';
import { resolveMutation } from '../../CommonUtils';
import { isPanelActive } from '../../devtools';
export function createArrayProxy(rawObj: any[]): any[] {
const handle = {
@ -54,28 +55,30 @@ function set(rawObj: any[], key: string, value: any, receiver: any) {
const oldLength = rawObj.length;
const newValue = value;
const oldArray = JSON.parse(JSON.stringify(rawObj));
const oldArray = isPanelActive() ? JSON.parse(JSON.stringify(rawObj)) : null;
const ret = Reflect.set(rawObj, key, newValue, receiver);
const newLength = rawObj.length;
const observer = getObserver(rawObj);
const mutation = isPanelActive() ? resolveMutation(oldArray, rawObj) : { mutation: true, from: [], to: rawObj };
if (!isSame(newValue, oldValue)) {
// 值不一样,触发监听器
if (observer.watchers?.[key]) {
observer.watchers[key].forEach(cb => {
cb(key, oldValue, newValue, resolveMutation(oldArray, rawObj));
cb(key, oldValue, newValue, mutation);
});
}
// 触发属性变化
observer.setProp(key, resolveMutation(oldValue, rawObj));
observer.setProp(key, mutation);
}
if (oldLength !== newLength) {
// 触发数组的大小变化
observer.setProp('length', resolveMutation(oldValue, rawObj));
observer.setProp('length', mutation);
}
return ret;

View File

@ -16,6 +16,7 @@
import { createProxy, getObserver, hookObserverMap } from '../ProxyHandler';
import { isMap, isWeakMap, isSame } from '../../CommonUtils';
import { resolveMutation } from '../../CommonUtils';
import { isPanelActive } from '../../devtools';
const COLLECTION_CHANGE = '_collectionChange';
const handler = {
@ -91,18 +92,20 @@ function set(
const valChange = !isSame(newValue, oldValue);
const observer = getObserver(rawObj);
const mutation = isPanelActive() ? resolveMutation(oldValue, rawObj) : { mutation: true, from: null, to: rawObj };
if (valChange || !rawObj.has(key)) {
observer.setProp(COLLECTION_CHANGE, resolveMutation(oldValue, rawObj));
observer.setProp(COLLECTION_CHANGE, mutation);
}
if (valChange) {
if (observer.watchers?.[key]) {
observer.watchers[key].forEach(cb => {
cb(key, oldValue, newValue, resolveMutation(oldValue, rawObj));
cb(key, oldValue, newValue, mutation);
});
}
observer.setProp(key, resolveMutation(oldValue, rawObj));
observer.setProp(key, mutation);
}
return rawObj;
@ -110,13 +113,16 @@ function set(
// Set的add方法
function add(rawObj: { add: (any) => void; set: (string, any) => any; has: (any) => boolean }, value: any): Object {
const oldCollection = JSON.parse(JSON.stringify(rawObj));
const oldCollection = isPanelActive() ? JSON.parse(JSON.stringify(rawObj)) : null;
if (!rawObj.has(value)) {
rawObj.add(value);
const observer = getObserver(rawObj);
observer.setProp(value, resolveMutation(oldCollection, rawObj));
observer.setProp(COLLECTION_CHANGE, resolveMutation(oldCollection, rawObj));
const mutation = isPanelActive()
? resolveMutation(oldCollection, rawObj)
: { mutation: true, from: null, to: rawObj };
observer.setProp(value, mutation);
observer.setProp(COLLECTION_CHANGE, mutation);
}
return rawObj;
@ -140,13 +146,16 @@ function clear(rawObj: { size: number; clear: () => void }) {
}
function deleteFun(rawObj: { has: (key: any) => boolean; delete: (key: any) => void }, key: any) {
const oldCollection = JSON.parse(JSON.stringify(rawObj));
const oldCollection = isPanelActive() ? JSON.parse(JSON.stringify(rawObj)) : null;
if (rawObj.has(key)) {
rawObj.delete(key);
const observer = getObserver(rawObj);
observer.setProp(key, resolveMutation(oldCollection, rawObj));
observer.setProp(COLLECTION_CHANGE, resolveMutation(oldCollection, rawObj));
const mutation = isPanelActive()
? resolveMutation(oldCollection, rawObj)
: { mutation: true, from: null, to: rawObj };
observer.setProp(key, mutation);
observer.setProp(COLLECTION_CHANGE, mutation);
return true;
}

View File

@ -16,6 +16,7 @@
import { isSame, resolveMutation } from '../../CommonUtils';
import { createProxy, getObserver, hookObserverMap } from '../ProxyHandler';
import { OBSERVER_KEY } from '../../Constants';
import { isPanelActive } from '../../devtools';
export function createObjectProxy<T extends object>(rawObj: T, singleLevel = false): ProxyHandler<T> {
const proxy = new Proxy(rawObj, {
@ -70,7 +71,7 @@ export function get(rawObj: object, key: string | symbol, receiver: any, singleL
}
export function set(rawObj: object, key: string, value: any, receiver: any): boolean {
const oldObject = JSON.parse(JSON.stringify(rawObj));
const oldObject = isPanelActive() ? JSON.parse(JSON.stringify(rawObj)) : null;
const observer = getObserver(rawObj);
if (value && key == 'removeListener') {
@ -80,14 +81,15 @@ export function set(rawObj: object, key: string, value: any, receiver: any): boo
const newValue = value;
const ret = Reflect.set(rawObj, key, newValue, receiver);
const mutation = isPanelActive() ? resolveMutation(oldObject, rawObj) : { mutation: true, from: null, to: rawObj };
if (!isSame(newValue, oldValue)) {
if (observer.watchers?.[key]) {
observer.watchers[key].forEach(cb => {
cb(key, oldValue, newValue, resolveMutation(oldObject, rawObj));
cb(key, oldValue, newValue, mutation);
});
}
observer.setProp(key, resolveMutation(oldObject, rawObj));
observer.setProp(key, mutation);
}
return ret;
}

View File

@ -14,7 +14,7 @@
*/
import { useEffect, useRef } from '../../renderer/hooks/HookExternal';
import { getProcessingVNode } from '../../renderer/GlobalVar';
import { getProcessingVNode, getStartVNode } from '../../renderer/GlobalVar';
import { createProxy } from '../proxy/ProxyHandler';
import readonlyProxy from '../proxy/readonlyProxy';
import { Observer } from '../proxy/Observer';
@ -60,7 +60,9 @@ export function createStore<S extends object, A extends UserActions<S>, C extend
throw new Error('store obj must be pure object');
}
const proxyObj = createProxy(config.state, !config.options?.isReduxAdapter);
const id = config.id || idGenerator.get('UNNAMED_STORE');
const proxyObj = createProxy(config.state, id, !config.options?.isReduxAdapter);
proxyObj.$pending = false;
@ -68,6 +70,7 @@ export function createStore<S extends object, A extends UserActions<S>, C extend
const $queue: Partial<StoreActions<S, A>> = {};
const $c: Partial<ComputedValues<S, C>> = {};
const storeObj = {
id,
$s: proxyObj,
$a: $a as StoreActions<S, A>,
$c: $c as ComputedValues<S, C>,
@ -183,18 +186,16 @@ export function createStore<S extends object, A extends UserActions<S>, C extend
});
}
if (config.id) {
storeMap.set(config.id, storeObj);
}
storeMap.set(id, storeObj);
devtools.emit(INITIALIZED, {
store: storeObj,
});
proxyObj.addListener(mutation => {
proxyObj.addListener(change => {
devtools.emit(STATE_CHANGE, {
store: storeObj,
mutation,
change,
});
});
@ -299,6 +300,14 @@ export function useStore<S extends object, A extends UserActions<S>, C extends U
return storeObj as StoreObj<S, A, C>;
}
export function getStore(id: string) {
return storeMap.get(id);
}
export function getAllStores() {
return Object.fromEntries(storeMap);
}
export function clearStore(id: string): void {
storeMap.delete(id);
}