From 503e9278e92352d0929f5c6a266a247a5145e662 Mon Sep 17 00:00:00 2001 From: xiaolei <1017653702@qq.com> Date: Wed, 30 Oct 2024 14:49:48 +0800 Subject: [PATCH 01/13] [feat]rn-relatons --- packages/core/src/convertor/wxToReact.js | 2 +- .../core/src/platform/builtInMixins/index.js | 3 +- .../builtInMixins/relationsMixin.android.js | 2 + .../builtInMixins/relationsMixin.ios.js | 101 ++++++++++++++++++ .../patch/react/getDefaultOptions.ios.js | 37 ++++++- .../webpack-plugin/lib/react/processScript.js | 3 +- .../webpack-plugin/lib/react/script-helper.js | 4 +- 7 files changed, 145 insertions(+), 7 deletions(-) create mode 100644 packages/core/src/platform/builtInMixins/relationsMixin.android.js create mode 100644 packages/core/src/platform/builtInMixins/relationsMixin.ios.js diff --git a/packages/core/src/convertor/wxToReact.js b/packages/core/src/convertor/wxToReact.js index 2f265a9663..c4d97197c5 100644 --- a/packages/core/src/convertor/wxToReact.js +++ b/packages/core/src/convertor/wxToReact.js @@ -5,7 +5,7 @@ import { import { implemented } from '../core/implement' // 暂不支持的wx选项,后期需要各种花式支持 -const unsupported = ['relations', 'moved', 'definitionFilter', 'onShareAppMessage'] +const unsupported = ['moved', 'definitionFilter', 'onShareAppMessage'] function convertErrorDesc (key) { error(`Options.${key} is not supported in runtime conversion from wx to react native.`, global.currentResource) diff --git a/packages/core/src/platform/builtInMixins/index.js b/packages/core/src/platform/builtInMixins/index.js index e8548f8c36..8ce7787817 100644 --- a/packages/core/src/platform/builtInMixins/index.js +++ b/packages/core/src/platform/builtInMixins/index.js @@ -22,7 +22,8 @@ export default function getBuiltInMixins ({ type, rawOptions = {} }) { directiveHelperMixin(), styleHelperMixin(), refsMixin(), - i18nMixin() + i18nMixin(), + relationsMixin(type) ] } else if (__mpx_mode__ === 'web') { bulitInMixins = [ diff --git a/packages/core/src/platform/builtInMixins/relationsMixin.android.js b/packages/core/src/platform/builtInMixins/relationsMixin.android.js new file mode 100644 index 0000000000..1f5b94fb95 --- /dev/null +++ b/packages/core/src/platform/builtInMixins/relationsMixin.android.js @@ -0,0 +1,2 @@ +import relationsMixin from './relationsMixin.ios' +export default relationsMixin diff --git a/packages/core/src/platform/builtInMixins/relationsMixin.ios.js b/packages/core/src/platform/builtInMixins/relationsMixin.ios.js new file mode 100644 index 0000000000..e0290897fd --- /dev/null +++ b/packages/core/src/platform/builtInMixins/relationsMixin.ios.js @@ -0,0 +1,101 @@ +import { BEFORECREATE, MOUNTED, BEFOREUNMOUNT } from '../../core/innerLifecycle' +import { isArray } from '@mpxjs/utils' + +const relationTypeMap = { + parent: 'child', + ancestor: 'descendant' +} + +const isChildNode = (target, instance) => { + let children = target.props.children + if (!children) return + if (!isArray(children)) { + children = [children] + } + return children.some((item = {}) => { + if (item.type?.__mpxBuiltIn) { // 如果是基础节点,继续向下查找 + return isChildNode(item, instance) + } else { + return item.type === instance.__getReactFunctionComponent() + } + }) +} + +export default function relationsMixin (mixinType) { + if (mixinType === 'component') { + return { + [BEFORECREATE] () { + this.__mpxRelations = {} + this.__mpxRelationNodesMap = {} + }, + [MOUNTED] () { + this.__mpxCollectRelations() + this.__mpxExecRelations('linked') + }, + [BEFOREUNMOUNT] () { + this.__mpxExecRelations('unlinked') + this.__mpxRelations = {} + this.__mpxRelationNodesMap = {} + }, + methods: { + getRelationNodes (path) { + return this.__mpxRelationNodesMap(path) || null + }, + __mpxCollectRelations () { + const relations = this.__mpxProxy.options.relations + if (!relations) return + Object.keys(relations).forEach(path => { + const relation = relations[path] + this.__mpxCheckParent(this, relation, path) + }) + }, + __mpxCheckParent (current, relation, path) { + const type = relation.type + const target = current.__getRelation() + if (!target) return + + // parent 只需要处理一层,ancestor 需要考虑多个层级 + if ((type === 'parent' && isChildNode(target, this)) || type === 'ancestor') { + const targetRelation = target.__mpxProxy.options.relations?.[this.__componentPath] + if (targetRelation && targetRelation.type === relationTypeMap[type] && target.__componentPath === path) { + this.__mpxRelations[path] = { + target, + targetRelation, + relation + } + this.__mpxRelationNodesMap[path] = [target] + } else if (type === 'ancestor') { + this.__mpxCheckParent(target, relation, path) + } + } + }, + __mpxExecRelations (type) { + Object.keys(this.__mpxRelations).forEach(path => { + const { target, targetRelation, relation } = this.__mpxRelations[path] + const currentPath = this.__componentPath + if (type === 'linked') { + this.__mpxLinkRelationNodes(target, currentPath) + } else if (type === 'unlinked') { + this.__mpxRemoveRelationNodes(target, currentPath) + } + if (typeof targetRelation[type] === 'function') { + targetRelation[type].call(target, this) + } + if (typeof relation[type] === 'function') { + relation[type].call(this, target) + } + }) + }, + __mpxLinkRelationNodes (target, path) { + target.__mpxRelationNodesMap[path] = target.__mpxRelationNodesMap[path] || [] // 父级绑定子级 + target.__mpxRelationNodesMap[path].push(this) + }, + __mpxRemoveRelationNodes (target, path) { + const arr = target.__mpxRelationNodesMap[path] || [] + const index = arr.indexOf(this) + if (index !== -1) arr.splice(index, 1) + } + } + } + } +} diff --git a/packages/core/src/platform/patch/react/getDefaultOptions.ios.js b/packages/core/src/platform/patch/react/getDefaultOptions.ios.js index a2b4b9adea..f957793c37 100644 --- a/packages/core/src/platform/patch/react/getDefaultOptions.ios.js +++ b/packages/core/src/platform/patch/react/getDefaultOptions.ios.js @@ -68,7 +68,7 @@ function getRootProps (props) { return rootProps } -function createInstance ({ propsRef, type, rawOptions, currentInject, validProps, components, pageId }) { +function createInstance ({ propsRef, type, rawOptions, currentInject, validProps, components, pageId, relation, reactFunctionComponent }) { const instance = Object.create({ setData (data, callback) { return this.__mpxProxy.forceUpdate(data, { sync: true }, callback) @@ -76,6 +76,12 @@ function createInstance ({ propsRef, type, rawOptions, currentInject, validProps getPageId () { return pageId }, + __getRelation () { + return relation + }, + __getReactFunctionComponent () { + return reactFunctionComponent + }, __getProps () { const props = propsRef.current const propsData = {} @@ -127,6 +133,7 @@ function createInstance ({ propsRef, type, rawOptions, currentInject, validProps } return null }, + __componentPath: currentInject.componentPath, __injectedRender: currentInject.render || noop, __getRefsData: currentInject.getRefsData || noop, // render helper @@ -342,6 +349,18 @@ function usePageStatus (navigation, pageId) { }, [navigation]) } +const needRelationContext = (options) => { + const relations = options.relations + if (!relations) return false + return Object.keys(relations).some(path => { + const relation = relations[path] + const type = relation.type + return type === 'child' || type === 'descendant' + }) +} + +const RelationsContext = createContext(null) + export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) { rawOptions = mergeOptions(rawOptions, type, false) const components = Object.assign({}, rawOptions.components, currentInject.getComponents()) @@ -349,12 +368,14 @@ export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) { const defaultOptions = memo(forwardRef((props, ref) => { const instanceRef = useRef(null) const propsRef = useRef(null) + const reactFunctionComponent = defaultOptions const pageId = useContext(RouteContext) + const relation = useContext(RelationsContext) propsRef.current = props let isFirst = false if (!instanceRef.current) { isFirst = true - instanceRef.current = createInstance({ propsRef, type, rawOptions, currentInject, validProps, components, pageId }) + instanceRef.current = createInstance({ propsRef, type, rawOptions, currentInject, validProps, components, pageId, relation, reactFunctionComponent }) } const instance = instanceRef.current useImperativeHandle(ref, () => { @@ -403,7 +424,17 @@ export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) { useSyncExternalStore(proxy.subscribe, proxy.getSnapshot) - const root = rawOptions.options?.disableMemo ? proxy.effect.run() : useMemo(() => proxy.effect.run(), [proxy.stateVersion]) + const runRenderEffect = () => { + if (needRelationContext(rawOptions)) { + return createElement(RelationsContext.Provider, { + value: instance + }, proxy.effect.run()) + } + + return proxy.effect.run() + } + + const root = rawOptions.options?.disableMemo ? runRenderEffect() : useMemo(runRenderEffect, [proxy.stateVersion]) if (root) { const rootProps = getRootProps(props) rootProps.style = { ...root.props.style, ...rootProps.style } diff --git a/packages/webpack-plugin/lib/react/processScript.js b/packages/webpack-plugin/lib/react/processScript.js index 76d3b8fb0f..167c70ef32 100644 --- a/packages/webpack-plugin/lib/react/processScript.js +++ b/packages/webpack-plugin/lib/react/processScript.js @@ -9,6 +9,7 @@ module.exports = function (script, { moduleId, isProduction, jsonConfig, + outputPath, builtInComponentsMap, localComponentsMap, localPagesMap @@ -65,7 +66,7 @@ global.__navigationHelper = { jsonConfig }) - output += buildGlobalParams({ moduleId, scriptSrcMode, loaderContext, isProduction, ctorType, jsonConfig, componentsMap }) + output += buildGlobalParams({ moduleId, scriptSrcMode, loaderContext, isProduction, ctorType, jsonConfig, componentsMap, outputPath }) output += getRequireScript({ ctorType, script, loaderContext }) output += `export default global.__mpxOptionsMap[${JSON.stringify(moduleId)}]\n` } diff --git a/packages/webpack-plugin/lib/react/script-helper.js b/packages/webpack-plugin/lib/react/script-helper.js index 5994b781eb..2e9e97b0ca 100644 --- a/packages/webpack-plugin/lib/react/script-helper.js +++ b/packages/webpack-plugin/lib/react/script-helper.js @@ -88,7 +88,8 @@ function buildGlobalParams ({ jsonConfig, componentsMap, pagesMap, - firstPage + firstPage, + outputPath }) { let content = '' if (ctorType === 'app') { @@ -117,6 +118,7 @@ global.currentInject.firstPage = ${JSON.stringify(firstPage)}\n` content += `global.currentInject.getComponents = function () { return ${shallowStringify(componentsMap)} }\n` + content += `global.currentInject.componentPath = '/' + ${JSON.stringify(outputPath)}\n` } content += `global.currentModuleId = ${JSON.stringify(moduleId)}\n` content += `global.currentSrcMode = ${JSON.stringify(scriptSrcMode)}\n` From a2fd798ac24de6f10ef4a31bf2c56cd069c15f1b Mon Sep 17 00:00:00 2001 From: xiaolei <1017653702@qq.com> Date: Fri, 10 Jan 2025 17:53:09 +0800 Subject: [PATCH 02/13] update --- .../platform/patch/getDefaultOptions.ios.js | 38 +++++++++++++++++-- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/packages/core/src/platform/patch/getDefaultOptions.ios.js b/packages/core/src/platform/patch/getDefaultOptions.ios.js index 0189823b53..7684753cde 100644 --- a/packages/core/src/platform/patch/getDefaultOptions.ios.js +++ b/packages/core/src/platform/patch/getDefaultOptions.ios.js @@ -1,4 +1,4 @@ -import { useEffect, useLayoutEffect, useSyncExternalStore, useRef, useMemo, useState, useCallback, createElement, memo, forwardRef, useImperativeHandle, useContext, Fragment, cloneElement } from 'react' +import { useEffect, useLayoutEffect, useSyncExternalStore, useRef, useMemo, useState, useCallback, createElement, memo, forwardRef, useImperativeHandle, useContext, Fragment, cloneElement, createContext } from 'react' import * as ReactNative from 'react-native' import { ReactiveEffect } from '../../observer/effect' import { watch } from '../../observer/watch' @@ -193,7 +193,7 @@ const instanceProto = { } } -function createInstance ({ propsRef, type, rawOptions, currentInject, validProps, components, pageId, intersectionCtx }) { +function createInstance ({ propsRef, type, rawOptions, currentInject, validProps, components, pageId, intersectionCtx, relation }) { const instance = Object.create(instanceProto, { dataset: { get () { @@ -233,6 +233,18 @@ function createInstance ({ propsRef, type, rawOptions, currentInject, validProps }, enumerable: false }, + __componentPath: { + get () { + return currentInject.componentPath || '' + }, + enumerable: false + }, + __getRelation: { + get () { + return relation + }, + enumerable: false + }, __injectedRender: { get () { return currentInject.render || noop @@ -374,6 +386,18 @@ function usePageStatus (navigation, pageId) { }, [navigation]) } +const needRelationContext = (options) => { + const relations = options.relations + if (!relations) return false + return Object.keys(relations).some((path) => { + const relation = relations[path] + const type = relation.type + return type === 'child' || type === 'descendant' + }) +} + +const RelationsContext = createContext(null) + export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) { rawOptions = mergeOptions(rawOptions, type, false) const components = Object.assign({}, rawOptions.components, currentInject.getComponents()) @@ -384,11 +408,12 @@ export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) { const propsRef = useRef(null) const intersectionCtx = useContext(IntersectionObserverContext) const pageId = useContext(RouteContext) + const relation = useContext(RelationsContext) propsRef.current = props let isFirst = false if (!instanceRef.current) { isFirst = true - instanceRef.current = createInstance({ propsRef, type, rawOptions, currentInject, validProps, components, pageId, intersectionCtx }) + instanceRef.current = createInstance({ propsRef, type, rawOptions, currentInject, validProps, components, pageId, intersectionCtx, relation }) } const instance = instanceRef.current useImperativeHandle(ref, () => { @@ -466,7 +491,12 @@ export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) { return proxy.finalMemoVersion }, [proxy.stateVersion, proxy.memoVersion]) - const root = useMemo(() => proxy.effect.run(), [finalMemoVersion]) + let root = useMemo(() => proxy.effect.run(), [finalMemoVersion]) + if (needRelationContext(rawOptions)) { + root = createElement(RelationsContext.Provider, { + value: 'componentPath' + }, root) + } if (root && root.props.ishost) { // 对于组件未注册的属性继承到host节点上,如事件、样式和其他属性等 const rootProps = getRootProps(props, validProps) From c5fe79c3f830896b54a88cbe659c2ec21cdc1dd3 Mon Sep 17 00:00:00 2001 From: xiaolei <1017653702@qq.com> Date: Tue, 14 Jan 2025 12:50:05 +0800 Subject: [PATCH 03/13] [optimize]context --- .../builtInMixins/relationsMixin.ios.js | 100 +++++++++++++----- .../platform/patch/getDefaultOptions.ios.js | 18 +++- 2 files changed, 88 insertions(+), 30 deletions(-) diff --git a/packages/core/src/platform/builtInMixins/relationsMixin.ios.js b/packages/core/src/platform/builtInMixins/relationsMixin.ios.js index e0290897fd..fe8e02d61c 100644 --- a/packages/core/src/platform/builtInMixins/relationsMixin.ios.js +++ b/packages/core/src/platform/builtInMixins/relationsMixin.ios.js @@ -1,25 +1,25 @@ import { BEFORECREATE, MOUNTED, BEFOREUNMOUNT } from '../../core/innerLifecycle' -import { isArray } from '@mpxjs/utils' +// import { isArray } from '@mpxjs/utils' const relationTypeMap = { parent: 'child', ancestor: 'descendant' } -const isChildNode = (target, instance) => { - let children = target.props.children - if (!children) return - if (!isArray(children)) { - children = [children] - } - return children.some((item = {}) => { - if (item.type?.__mpxBuiltIn) { // 如果是基础节点,继续向下查找 - return isChildNode(item, instance) - } else { - return item.type === instance.__getReactFunctionComponent() - } - }) -} +// const isChildNode = (target, instance) => { +// let children = target.props.children +// if (!children) return +// if (!isArray(children)) { +// children = [children] +// } +// return children.some((item = {}) => { +// if (item.type?.__mpxBuiltIn) { // 如果是基础节点,继续向下查找 +// return isChildNode(item, instance) +// } else { +// return item.type === instance.__getReactFunctionComponent() +// } +// }) +// } export default function relationsMixin (mixinType) { if (mixinType === 'component') { @@ -51,23 +51,65 @@ export default function relationsMixin (mixinType) { }, __mpxCheckParent (current, relation, path) { const type = relation.type - const target = current.__getRelation() - if (!target) return + const relationMap = current.__getRelation + if (!relationMap) return - // parent 只需要处理一层,ancestor 需要考虑多个层级 - if ((type === 'parent' && isChildNode(target, this)) || type === 'ancestor') { - const targetRelation = target.__mpxProxy.options.relations?.[this.__componentPath] - if (targetRelation && targetRelation.type === relationTypeMap[type] && target.__componentPath === path) { - this.__mpxRelations[path] = { - target, - targetRelation, - relation + // parent 只处理一个层级,ancestor 遍历到就返回 + if (relationMap[path]) { + relationMap[path].forEach((target, index) => { + if ((type === 'parent' && index === 0) || type === 'ancestor') { + const targetRelation = target.__mpxProxy.options.relations?.[this.__componentPath] + if (targetRelation && targetRelation.type === relationTypeMap[type] && target.__componentPath) { + this.__mpxRelations[path] = { + target, + targetRelation, + relation + } + this.__mpxRelationNodesMap[path] = [target] + return + } } - this.__mpxRelationNodesMap[path] = [target] - } else if (type === 'ancestor') { - this.__mpxCheckParent(target, relation, path) - } + }) + // if (type === 'parent') { + // const target = relationMap[path][0] + // const targetRelation = target.__mpxProxy.options.relations?.[this.__componentPath] + // if (targetRelation && targetRelation.type === relationTypeMap[type] && target.__componentPath) { + // this.__mpxRelations[path] = { + // target, + // targetRelation, + // relation + // } + // this.__mpxRelationNodesMap[path] = [target] + // } + // } else if (type === 'ancestor') { + // relationMap[path].forEach(target => { + // const targetRelation = target.__mpxProxy.options.relations?.[this.__componentPath] + // if (targetRelation && targetRelation.type === relationTypeMap[type] && target.__componentPath) { + // this.__mpxRelations[path] = { + // target, + // targetRelation, + // relation + // } + // this.__mpxRelationNodesMap[path] = [target] + // } + // }) + // } } + + // parent 只需要处理一层,ancestor 需要考虑多个层级 + // if ((type === 'parent' && isChildNode(target, this)) || type === 'ancestor') { + // const targetRelation = target.__mpxProxy.options.relations?.[this.__componentPath] + // if (targetRelation && targetRelation.type === relationTypeMap[type] && target.__componentPath === path) { + // this.__mpxRelations[path] = { + // target, + // targetRelation, + // relation + // } + // this.__mpxRelationNodesMap[path] = [target] + // } else if (type === 'ancestor') { + // this.__mpxCheckParent(target, relation, path) + // } + // } }, __mpxExecRelations (type) { Object.keys(this.__mpxRelations).forEach(path => { diff --git a/packages/core/src/platform/patch/getDefaultOptions.ios.js b/packages/core/src/platform/patch/getDefaultOptions.ios.js index 7684753cde..681a5480d1 100644 --- a/packages/core/src/platform/patch/getDefaultOptions.ios.js +++ b/packages/core/src/platform/patch/getDefaultOptions.ios.js @@ -396,6 +396,22 @@ const needRelationContext = (options) => { }) } +const provideRelation = (relation, instance) => { + const componentPath = instance.__componentPath + if (relation) { + if (relation[componentPath]) { + relation[componentPath].unshift(instance) + } else { + relation[componentPath] = [instance] + } + return relation + } else { + return { + [componentPath]: [instance] + } + } +} + const RelationsContext = createContext(null) export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) { @@ -494,7 +510,7 @@ export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) { let root = useMemo(() => proxy.effect.run(), [finalMemoVersion]) if (needRelationContext(rawOptions)) { root = createElement(RelationsContext.Provider, { - value: 'componentPath' + value: provideRelation(relation, instance) }, root) } if (root && root.props.ishost) { From a2150262a4456ffee1d24a64d735c587d447c5d0 Mon Sep 17 00:00:00 2001 From: xiaolei <1017653702@qq.com> Date: Tue, 14 Jan 2025 14:57:29 +0800 Subject: [PATCH 04/13] [feat]useContext for relation --- .../builtInMixins/relationsMixin.ios.js | 82 +++---------------- .../platform/patch/getDefaultOptions.ios.js | 36 ++++---- .../webpack-plugin/lib/react/script-helper.js | 4 +- 3 files changed, 34 insertions(+), 88 deletions(-) diff --git a/packages/core/src/platform/builtInMixins/relationsMixin.ios.js b/packages/core/src/platform/builtInMixins/relationsMixin.ios.js index fe8e02d61c..c28aadf19f 100644 --- a/packages/core/src/platform/builtInMixins/relationsMixin.ios.js +++ b/packages/core/src/platform/builtInMixins/relationsMixin.ios.js @@ -1,26 +1,10 @@ import { BEFORECREATE, MOUNTED, BEFOREUNMOUNT } from '../../core/innerLifecycle' -// import { isArray } from '@mpxjs/utils' const relationTypeMap = { parent: 'child', ancestor: 'descendant' } -// const isChildNode = (target, instance) => { -// let children = target.props.children -// if (!children) return -// if (!isArray(children)) { -// children = [children] -// } -// return children.some((item = {}) => { -// if (item.type?.__mpxBuiltIn) { // 如果是基础节点,继续向下查找 -// return isChildNode(item, instance) -// } else { -// return item.type === instance.__getReactFunctionComponent() -// } -// }) -// } - export default function relationsMixin (mixinType) { if (mixinType === 'component') { return { @@ -39,7 +23,7 @@ export default function relationsMixin (mixinType) { }, methods: { getRelationNodes (path) { - return this.__mpxRelationNodesMap(path) || null + return this.__mpxRelationNodesMap[path] || null }, __mpxCollectRelations () { const relations = this.__mpxProxy.options.relations @@ -51,65 +35,21 @@ export default function relationsMixin (mixinType) { }, __mpxCheckParent (current, relation, path) { const type = relation.type - const relationMap = current.__getRelation + const relationMap = current.__relation if (!relationMap) return - // parent 只处理一个层级,ancestor 遍历到就返回 if (relationMap[path]) { - relationMap[path].forEach((target, index) => { - if ((type === 'parent' && index === 0) || type === 'ancestor') { - const targetRelation = target.__mpxProxy.options.relations?.[this.__componentPath] - if (targetRelation && targetRelation.type === relationTypeMap[type] && target.__componentPath) { - this.__mpxRelations[path] = { - target, - targetRelation, - relation - } - this.__mpxRelationNodesMap[path] = [target] - return - } + const target = relationMap[path] + const targetRelation = target.__mpxProxy.options.relations?.[this.__componentPath] + if (targetRelation && targetRelation.type === relationTypeMap[type] && target.__componentPath) { + this.__mpxRelations[path] = { + target, + targetRelation, + relation } - }) - // if (type === 'parent') { - // const target = relationMap[path][0] - // const targetRelation = target.__mpxProxy.options.relations?.[this.__componentPath] - // if (targetRelation && targetRelation.type === relationTypeMap[type] && target.__componentPath) { - // this.__mpxRelations[path] = { - // target, - // targetRelation, - // relation - // } - // this.__mpxRelationNodesMap[path] = [target] - // } - // } else if (type === 'ancestor') { - // relationMap[path].forEach(target => { - // const targetRelation = target.__mpxProxy.options.relations?.[this.__componentPath] - // if (targetRelation && targetRelation.type === relationTypeMap[type] && target.__componentPath) { - // this.__mpxRelations[path] = { - // target, - // targetRelation, - // relation - // } - // this.__mpxRelationNodesMap[path] = [target] - // } - // }) - // } + this.__mpxRelationNodesMap[path] = [target] + } } - - // parent 只需要处理一层,ancestor 需要考虑多个层级 - // if ((type === 'parent' && isChildNode(target, this)) || type === 'ancestor') { - // const targetRelation = target.__mpxProxy.options.relations?.[this.__componentPath] - // if (targetRelation && targetRelation.type === relationTypeMap[type] && target.__componentPath === path) { - // this.__mpxRelations[path] = { - // target, - // targetRelation, - // relation - // } - // this.__mpxRelationNodesMap[path] = [target] - // } else if (type === 'ancestor') { - // this.__mpxCheckParent(target, relation, path) - // } - // } }, __mpxExecRelations (type) { Object.keys(this.__mpxRelations).forEach(path => { diff --git a/packages/core/src/platform/patch/getDefaultOptions.ios.js b/packages/core/src/platform/patch/getDefaultOptions.ios.js index 681a5480d1..b6db4e2f94 100644 --- a/packages/core/src/platform/patch/getDefaultOptions.ios.js +++ b/packages/core/src/platform/patch/getDefaultOptions.ios.js @@ -239,7 +239,7 @@ function createInstance ({ propsRef, type, rawOptions, currentInject, validProps }, enumerable: false }, - __getRelation: { + __relation: { get () { return relation }, @@ -386,6 +386,8 @@ function usePageStatus (navigation, pageId) { }, [navigation]) } +const RelationsContext = createContext(null) + const needRelationContext = (options) => { const relations = options.relations if (!relations) return false @@ -396,23 +398,30 @@ const needRelationContext = (options) => { }) } -const provideRelation = (relation, instance) => { +const provideRelation = (instance) => { const componentPath = instance.__componentPath + const relation = instance.__relation if (relation) { - if (relation[componentPath]) { - relation[componentPath].unshift(instance) - } else { - relation[componentPath] = [instance] + if (!relation[componentPath]) { + relation[componentPath] = instance } return relation } else { return { - [componentPath]: [instance] + [componentPath]: instance } } } -const RelationsContext = createContext(null) +const wrapRelationContext = (element, instance) => { + if (needRelationContext(instance.__mpxProxy.options)) { + return createElement(RelationsContext.Provider, { + value: provideRelation(instance) + }, element) + } else { + return element + } +} export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) { rawOptions = mergeOptions(rawOptions, type, false) @@ -507,20 +516,15 @@ export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) { return proxy.finalMemoVersion }, [proxy.stateVersion, proxy.memoVersion]) - let root = useMemo(() => proxy.effect.run(), [finalMemoVersion]) - if (needRelationContext(rawOptions)) { - root = createElement(RelationsContext.Provider, { - value: provideRelation(relation, instance) - }, root) - } + const root = useMemo(() => proxy.effect.run(), [finalMemoVersion]) if (root && root.props.ishost) { // 对于组件未注册的属性继承到host节点上,如事件、样式和其他属性等 const rootProps = getRootProps(props, validProps) rootProps.style = Object.assign({}, root.props.style, rootProps.style) // update root props - return cloneElement(root, rootProps) + return wrapRelationContext(cloneElement(root, rootProps), instance) } - return root + return wrapRelationContext(root, instance) })) if (rawOptions.options?.isCustomText) { diff --git a/packages/webpack-plugin/lib/react/script-helper.js b/packages/webpack-plugin/lib/react/script-helper.js index 2e9e97b0ca..943aa0889b 100644 --- a/packages/webpack-plugin/lib/react/script-helper.js +++ b/packages/webpack-plugin/lib/react/script-helper.js @@ -118,7 +118,9 @@ global.currentInject.firstPage = ${JSON.stringify(firstPage)}\n` content += `global.currentInject.getComponents = function () { return ${shallowStringify(componentsMap)} }\n` - content += `global.currentInject.componentPath = '/' + ${JSON.stringify(outputPath)}\n` + if (ctorType === 'component') { + content += `global.currentInject.componentPath = '/' + ${JSON.stringify(outputPath)}\n` + } } content += `global.currentModuleId = ${JSON.stringify(moduleId)}\n` content += `global.currentSrcMode = ${JSON.stringify(scriptSrcMode)}\n` From a998767652c1d677496ad07fe9a73c2f3716aa25 Mon Sep 17 00:00:00 2001 From: xiaolei <1017653702@qq.com> Date: Tue, 14 Jan 2025 15:03:44 +0800 Subject: [PATCH 05/13] [feat]useContext for relation --- .../builtInMixins/relationsMixin.ios.js | 30 ++-------- .../platform/patch/getDefaultOptions.ios.js | 60 +++++++++++++++++-- .../webpack-plugin/lib/react/script-helper.js | 4 +- 3 files changed, 64 insertions(+), 30 deletions(-) diff --git a/packages/core/src/platform/builtInMixins/relationsMixin.ios.js b/packages/core/src/platform/builtInMixins/relationsMixin.ios.js index e0290897fd..c28aadf19f 100644 --- a/packages/core/src/platform/builtInMixins/relationsMixin.ios.js +++ b/packages/core/src/platform/builtInMixins/relationsMixin.ios.js @@ -1,26 +1,10 @@ import { BEFORECREATE, MOUNTED, BEFOREUNMOUNT } from '../../core/innerLifecycle' -import { isArray } from '@mpxjs/utils' const relationTypeMap = { parent: 'child', ancestor: 'descendant' } -const isChildNode = (target, instance) => { - let children = target.props.children - if (!children) return - if (!isArray(children)) { - children = [children] - } - return children.some((item = {}) => { - if (item.type?.__mpxBuiltIn) { // 如果是基础节点,继续向下查找 - return isChildNode(item, instance) - } else { - return item.type === instance.__getReactFunctionComponent() - } - }) -} - export default function relationsMixin (mixinType) { if (mixinType === 'component') { return { @@ -39,7 +23,7 @@ export default function relationsMixin (mixinType) { }, methods: { getRelationNodes (path) { - return this.__mpxRelationNodesMap(path) || null + return this.__mpxRelationNodesMap[path] || null }, __mpxCollectRelations () { const relations = this.__mpxProxy.options.relations @@ -51,21 +35,19 @@ export default function relationsMixin (mixinType) { }, __mpxCheckParent (current, relation, path) { const type = relation.type - const target = current.__getRelation() - if (!target) return + const relationMap = current.__relation + if (!relationMap) return - // parent 只需要处理一层,ancestor 需要考虑多个层级 - if ((type === 'parent' && isChildNode(target, this)) || type === 'ancestor') { + if (relationMap[path]) { + const target = relationMap[path] const targetRelation = target.__mpxProxy.options.relations?.[this.__componentPath] - if (targetRelation && targetRelation.type === relationTypeMap[type] && target.__componentPath === path) { + if (targetRelation && targetRelation.type === relationTypeMap[type] && target.__componentPath) { this.__mpxRelations[path] = { target, targetRelation, relation } this.__mpxRelationNodesMap[path] = [target] - } else if (type === 'ancestor') { - this.__mpxCheckParent(target, relation, path) } } }, diff --git a/packages/core/src/platform/patch/getDefaultOptions.ios.js b/packages/core/src/platform/patch/getDefaultOptions.ios.js index 0189823b53..b6db4e2f94 100644 --- a/packages/core/src/platform/patch/getDefaultOptions.ios.js +++ b/packages/core/src/platform/patch/getDefaultOptions.ios.js @@ -1,4 +1,4 @@ -import { useEffect, useLayoutEffect, useSyncExternalStore, useRef, useMemo, useState, useCallback, createElement, memo, forwardRef, useImperativeHandle, useContext, Fragment, cloneElement } from 'react' +import { useEffect, useLayoutEffect, useSyncExternalStore, useRef, useMemo, useState, useCallback, createElement, memo, forwardRef, useImperativeHandle, useContext, Fragment, cloneElement, createContext } from 'react' import * as ReactNative from 'react-native' import { ReactiveEffect } from '../../observer/effect' import { watch } from '../../observer/watch' @@ -193,7 +193,7 @@ const instanceProto = { } } -function createInstance ({ propsRef, type, rawOptions, currentInject, validProps, components, pageId, intersectionCtx }) { +function createInstance ({ propsRef, type, rawOptions, currentInject, validProps, components, pageId, intersectionCtx, relation }) { const instance = Object.create(instanceProto, { dataset: { get () { @@ -233,6 +233,18 @@ function createInstance ({ propsRef, type, rawOptions, currentInject, validProps }, enumerable: false }, + __componentPath: { + get () { + return currentInject.componentPath || '' + }, + enumerable: false + }, + __relation: { + get () { + return relation + }, + enumerable: false + }, __injectedRender: { get () { return currentInject.render || noop @@ -374,6 +386,43 @@ function usePageStatus (navigation, pageId) { }, [navigation]) } +const RelationsContext = createContext(null) + +const needRelationContext = (options) => { + const relations = options.relations + if (!relations) return false + return Object.keys(relations).some((path) => { + const relation = relations[path] + const type = relation.type + return type === 'child' || type === 'descendant' + }) +} + +const provideRelation = (instance) => { + const componentPath = instance.__componentPath + const relation = instance.__relation + if (relation) { + if (!relation[componentPath]) { + relation[componentPath] = instance + } + return relation + } else { + return { + [componentPath]: instance + } + } +} + +const wrapRelationContext = (element, instance) => { + if (needRelationContext(instance.__mpxProxy.options)) { + return createElement(RelationsContext.Provider, { + value: provideRelation(instance) + }, element) + } else { + return element + } +} + export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) { rawOptions = mergeOptions(rawOptions, type, false) const components = Object.assign({}, rawOptions.components, currentInject.getComponents()) @@ -384,11 +433,12 @@ export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) { const propsRef = useRef(null) const intersectionCtx = useContext(IntersectionObserverContext) const pageId = useContext(RouteContext) + const relation = useContext(RelationsContext) propsRef.current = props let isFirst = false if (!instanceRef.current) { isFirst = true - instanceRef.current = createInstance({ propsRef, type, rawOptions, currentInject, validProps, components, pageId, intersectionCtx }) + instanceRef.current = createInstance({ propsRef, type, rawOptions, currentInject, validProps, components, pageId, intersectionCtx, relation }) } const instance = instanceRef.current useImperativeHandle(ref, () => { @@ -472,9 +522,9 @@ export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) { const rootProps = getRootProps(props, validProps) rootProps.style = Object.assign({}, root.props.style, rootProps.style) // update root props - return cloneElement(root, rootProps) + return wrapRelationContext(cloneElement(root, rootProps), instance) } - return root + return wrapRelationContext(root, instance) })) if (rawOptions.options?.isCustomText) { diff --git a/packages/webpack-plugin/lib/react/script-helper.js b/packages/webpack-plugin/lib/react/script-helper.js index 2e9e97b0ca..943aa0889b 100644 --- a/packages/webpack-plugin/lib/react/script-helper.js +++ b/packages/webpack-plugin/lib/react/script-helper.js @@ -118,7 +118,9 @@ global.currentInject.firstPage = ${JSON.stringify(firstPage)}\n` content += `global.currentInject.getComponents = function () { return ${shallowStringify(componentsMap)} }\n` - content += `global.currentInject.componentPath = '/' + ${JSON.stringify(outputPath)}\n` + if (ctorType === 'component') { + content += `global.currentInject.componentPath = '/' + ${JSON.stringify(outputPath)}\n` + } } content += `global.currentModuleId = ${JSON.stringify(moduleId)}\n` content += `global.currentSrcMode = ${JSON.stringify(scriptSrcMode)}\n` From c44dc0c974b6756adcc01bd33947833918f371ad Mon Sep 17 00:00:00 2001 From: xiaolei <1017653702@qq.com> Date: Tue, 14 Jan 2025 15:16:10 +0800 Subject: [PATCH 06/13] [fix]change function name --- packages/core/src/platform/patch/getDefaultOptions.ios.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core/src/platform/patch/getDefaultOptions.ios.js b/packages/core/src/platform/patch/getDefaultOptions.ios.js index b6db4e2f94..088afe76f2 100644 --- a/packages/core/src/platform/patch/getDefaultOptions.ios.js +++ b/packages/core/src/platform/patch/getDefaultOptions.ios.js @@ -413,7 +413,7 @@ const provideRelation = (instance) => { } } -const wrapRelationContext = (element, instance) => { +const wrapRelationProvider = (element, instance) => { if (needRelationContext(instance.__mpxProxy.options)) { return createElement(RelationsContext.Provider, { value: provideRelation(instance) @@ -522,9 +522,9 @@ export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) { const rootProps = getRootProps(props, validProps) rootProps.style = Object.assign({}, root.props.style, rootProps.style) // update root props - return wrapRelationContext(cloneElement(root, rootProps), instance) + return wrapRelationProvider(cloneElement(root, rootProps), instance) } - return wrapRelationContext(root, instance) + return wrapRelationProvider(root, instance) })) if (rawOptions.options?.isCustomText) { From 9cecc80d22f9de32086cf28d3ce3122227412309 Mon Sep 17 00:00:00 2001 From: xiaolei <1017653702@qq.com> Date: Mon, 20 Jan 2025 14:18:09 +0800 Subject: [PATCH 07/13] [fix]getRelationNodes method --- packages/core/src/platform/builtInMixins/relationsMixin.ios.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/platform/builtInMixins/relationsMixin.ios.js b/packages/core/src/platform/builtInMixins/relationsMixin.ios.js index e0290897fd..c952f4bc14 100644 --- a/packages/core/src/platform/builtInMixins/relationsMixin.ios.js +++ b/packages/core/src/platform/builtInMixins/relationsMixin.ios.js @@ -39,7 +39,7 @@ export default function relationsMixin (mixinType) { }, methods: { getRelationNodes (path) { - return this.__mpxRelationNodesMap(path) || null + return this.__mpxRelationNodesMap[path] || null }, __mpxCollectRelations () { const relations = this.__mpxProxy.options.relations From 0f7fc850d2d73711a2c7f5c78279e815019d44a1 Mon Sep 17 00:00:00 2001 From: xiaolei <1017653702@qq.com> Date: Mon, 20 Jan 2025 18:41:43 +0800 Subject: [PATCH 08/13] [optimize]optimize code --- .../builtInMixins/relationsMixin.android.js | 2 - .../builtInMixins/relationsMixin.ios.js | 69 ++++------- .../platform/patch/getDefaultOptions.ios.js | 116 +++++++++++++----- 3 files changed, 106 insertions(+), 81 deletions(-) delete mode 100644 packages/core/src/platform/builtInMixins/relationsMixin.android.js diff --git a/packages/core/src/platform/builtInMixins/relationsMixin.android.js b/packages/core/src/platform/builtInMixins/relationsMixin.android.js deleted file mode 100644 index 1f5b94fb95..0000000000 --- a/packages/core/src/platform/builtInMixins/relationsMixin.android.js +++ /dev/null @@ -1,2 +0,0 @@ -import relationsMixin from './relationsMixin.ios' -export default relationsMixin diff --git a/packages/core/src/platform/builtInMixins/relationsMixin.ios.js b/packages/core/src/platform/builtInMixins/relationsMixin.ios.js index c28aadf19f..64755c0e44 100644 --- a/packages/core/src/platform/builtInMixins/relationsMixin.ios.js +++ b/packages/core/src/platform/builtInMixins/relationsMixin.ios.js @@ -1,59 +1,30 @@ -import { BEFORECREATE, MOUNTED, BEFOREUNMOUNT } from '../../core/innerLifecycle' - -const relationTypeMap = { - parent: 'child', - ancestor: 'descendant' -} +import { MOUNTED, BEFOREUNMOUNT } from '../../core/innerLifecycle' export default function relationsMixin (mixinType) { if (mixinType === 'component') { return { - [BEFORECREATE] () { - this.__mpxRelations = {} - this.__mpxRelationNodesMap = {} - }, [MOUNTED] () { - this.__mpxCollectRelations() - this.__mpxExecRelations('linked') + if (this.__relations) { + this.__mpxExecRelations('linked') + } }, [BEFOREUNMOUNT] () { - this.__mpxExecRelations('unlinked') - this.__mpxRelations = {} - this.__mpxRelationNodesMap = {} + if (this.__relations) { + this.__mpxExecRelations('unlinked') + this.__relations = {} + } + if (this.__relationNodesMap) { + this.__relationNodesMap = {} + } }, methods: { getRelationNodes (path) { - return this.__mpxRelationNodesMap[path] || null - }, - __mpxCollectRelations () { - const relations = this.__mpxProxy.options.relations - if (!relations) return - Object.keys(relations).forEach(path => { - const relation = relations[path] - this.__mpxCheckParent(this, relation, path) - }) - }, - __mpxCheckParent (current, relation, path) { - const type = relation.type - const relationMap = current.__relation - if (!relationMap) return - - if (relationMap[path]) { - const target = relationMap[path] - const targetRelation = target.__mpxProxy.options.relations?.[this.__componentPath] - if (targetRelation && targetRelation.type === relationTypeMap[type] && target.__componentPath) { - this.__mpxRelations[path] = { - target, - targetRelation, - relation - } - this.__mpxRelationNodesMap[path] = [target] - } - } + return this.__relationNodesMap?.[path] || null }, __mpxExecRelations (type) { - Object.keys(this.__mpxRelations).forEach(path => { - const { target, targetRelation, relation } = this.__mpxRelations[path] + const relations = this.__relations + Object.keys(relations).forEach(path => { + const { target, targetRelation, relation } = relations[path] const currentPath = this.__componentPath if (type === 'linked') { this.__mpxLinkRelationNodes(target, currentPath) @@ -69,11 +40,15 @@ export default function relationsMixin (mixinType) { }) }, __mpxLinkRelationNodes (target, path) { - target.__mpxRelationNodesMap[path] = target.__mpxRelationNodesMap[path] || [] // 父级绑定子级 - target.__mpxRelationNodesMap[path].push(this) + if (!target.__relationNodesMap) { + target.__relationNodesMap = {} + } + target.__relationNodesMap[path] = target.__relationNodesMap[path] || [] // 父级绑定子级 + target.__relationNodesMap[path].push(this) }, __mpxRemoveRelationNodes (target, path) { - const arr = target.__mpxRelationNodesMap[path] || [] + const relationNodesMap = target.__relationNodesMap + const arr = relationNodesMap[path] || [] const index = arr.indexOf(this) if (index !== -1) arr.splice(index, 1) } diff --git a/packages/core/src/platform/patch/getDefaultOptions.ios.js b/packages/core/src/platform/patch/getDefaultOptions.ios.js index b6db4e2f94..a9b1ab82d7 100644 --- a/packages/core/src/platform/patch/getDefaultOptions.ios.js +++ b/packages/core/src/platform/patch/getDefaultOptions.ios.js @@ -3,7 +3,7 @@ import * as ReactNative from 'react-native' import { ReactiveEffect } from '../../observer/effect' import { watch } from '../../observer/watch' import { reactive, set, del } from '../../observer/reactive' -import { hasOwn, isFunction, noop, isObject, isArray, getByPath, collectDataset, hump2dash, dash2hump, callWithErrorHandling, wrapMethodsWithErrorHandling } from '@mpxjs/utils' +import { hasOwn, isFunction, noop, isObject, isArray, getByPath, collectDataset, hump2dash, dash2hump, callWithErrorHandling, wrapMethodsWithErrorHandling, isEmptyObject } from '@mpxjs/utils' import MpxProxy from '../../core/proxy' import { BEFOREUPDATE, ONLOAD, UPDATED, ONSHOW, ONHIDE, ONRESIZE, REACTHOOKSEXEC } from '../../core/innerLifecycle' import mergeOptions from '../../core/mergeOptions' @@ -193,7 +193,7 @@ const instanceProto = { } } -function createInstance ({ propsRef, type, rawOptions, currentInject, validProps, components, pageId, intersectionCtx, relation }) { +function createInstance ({ propsRef, type, rawOptions, currentInject, validProps, components, pageId, intersectionCtx, relationInfo }) { const instance = Object.create(instanceProto, { dataset: { get () { @@ -233,18 +233,6 @@ function createInstance ({ propsRef, type, rawOptions, currentInject, validProps }, enumerable: false }, - __componentPath: { - get () { - return currentInject.componentPath || '' - }, - enumerable: false - }, - __relation: { - get () { - return relation - }, - enumerable: false - }, __injectedRender: { get () { return currentInject.render || noop @@ -259,6 +247,32 @@ function createInstance ({ propsRef, type, rawOptions, currentInject, validProps } }) + if (type === 'component') { + Object.defineProperty(instance, '__componentPath', { + get () { + return currentInject.componentPath || '' + }, + enumerable: false + }) + } + + if (!isEmptyObject(relationInfo.relations)) { + Object.defineProperties(instance, { + __relations: { + get() { + return relationInfo.relations + }, + enumerable: false + }, + __relationNodesMap: { + get() { + return relationInfo.relationNodesMap + }, + enumerable: false + } + }) + } + // bind this & assign methods if (rawOptions.methods) { Object.entries(rawOptions.methods).forEach(([key, method]) => { @@ -388,19 +402,27 @@ function usePageStatus (navigation, pageId) { const RelationsContext = createContext(null) -const needRelationContext = (options) => { - const relations = options.relations - if (!relations) return false - return Object.keys(relations).some((path) => { +const checkRelation = (options) => { + const relations = options.relations || {} + let hasDescendantRelation = false + const ancestorRelations = {} + Object.keys(relations).forEach((path) => { const relation = relations[path] const type = relation.type - return type === 'child' || type === 'descendant' + if (['child', 'descendant'].includes(type)) { + hasDescendantRelation = true + } else if (['parent', 'ancestor'].includes(type)) { + ancestorRelations[path] = relation + } }) + return { + hasDescendantRelation, + ancestorRelations + } } -const provideRelation = (instance) => { +const provideRelation = (instance, relation) => { const componentPath = instance.__componentPath - const relation = instance.__relation if (relation) { if (!relation[componentPath]) { relation[componentPath] = instance @@ -413,13 +435,34 @@ const provideRelation = (instance) => { } } -const wrapRelationContext = (element, instance) => { - if (needRelationContext(instance.__mpxProxy.options)) { - return createElement(RelationsContext.Provider, { - value: provideRelation(instance) - }, element) - } else { - return element +const relationTypeMap = { + parent: 'child', + ancestor: 'descendant' +} + +const collectRelations = (ancestorRelations, relationMap, componentPath = '') => { + const relations = {} + const relationNodesMap = {} + Object.keys(ancestorRelations).forEach(path => { + const relation = ancestorRelations[path] + const type = relation.type + if (relationMap[path]) { + const target = relationMap[path] + const targetRelation = target.__mpxProxy.options.relations?.[componentPath] + if (targetRelation && targetRelation.type === relationTypeMap[type] && target.__componentPath === path) { + relations[path] = { + target, + targetRelation, + relation + } + relationNodesMap[path] = [target] + } + } + }) + + return { + relations, + relationNodesMap } } @@ -427,6 +470,7 @@ export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) { rawOptions = mergeOptions(rawOptions, type, false) const components = Object.assign({}, rawOptions.components, currentInject.getComponents()) const validProps = Object.assign({}, rawOptions.props, rawOptions.properties) + const { hasDescendantRelation, ancestorRelations } = checkRelation(rawOptions) if (rawOptions.methods) rawOptions.methods = wrapMethodsWithErrorHandling(rawOptions.methods) const defaultOptions = memo(forwardRef((props, ref) => { const instanceRef = useRef(null) @@ -434,11 +478,12 @@ export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) { const intersectionCtx = useContext(IntersectionObserverContext) const pageId = useContext(RouteContext) const relation = useContext(RelationsContext) + const relationInfo = collectRelations(ancestorRelations, relation, currentInject.componentPath) propsRef.current = props let isFirst = false if (!instanceRef.current) { isFirst = true - instanceRef.current = createInstance({ propsRef, type, rawOptions, currentInject, validProps, components, pageId, intersectionCtx, relation }) + instanceRef.current = createInstance({ propsRef, type, rawOptions, currentInject, validProps, components, pageId, intersectionCtx, relationInfo }) } const instance = instanceRef.current useImperativeHandle(ref, () => { @@ -516,15 +561,22 @@ export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) { return proxy.finalMemoVersion }, [proxy.stateVersion, proxy.memoVersion]) - const root = useMemo(() => proxy.effect.run(), [finalMemoVersion]) + let root = useMemo(() => proxy.effect.run(), [finalMemoVersion]) if (root && root.props.ishost) { // 对于组件未注册的属性继承到host节点上,如事件、样式和其他属性等 const rootProps = getRootProps(props, validProps) rootProps.style = Object.assign({}, root.props.style, rootProps.style) // update root props - return wrapRelationContext(cloneElement(root, rootProps), instance) + root = cloneElement(root, rootProps) } - return wrapRelationContext(root, instance) + return hasDescendantRelation + ? createElement(RelationsContext.Provider, + { + value: provideRelation(instance, relation) + }, + root + ) + : root })) if (rawOptions.options?.isCustomText) { From 5bf606f8c4731c388777387c6c54f63577cbe17b Mon Sep 17 00:00:00 2001 From: xiaolei <1017653702@qq.com> Date: Mon, 20 Jan 2025 18:50:09 +0800 Subject: [PATCH 09/13] [fix]clear code --- .../core/src/platform/patch/getDefaultOptions.ios.js | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/packages/core/src/platform/patch/getDefaultOptions.ios.js b/packages/core/src/platform/patch/getDefaultOptions.ios.js index 3acade48e1..03f338cc84 100644 --- a/packages/core/src/platform/patch/getDefaultOptions.ios.js +++ b/packages/core/src/platform/patch/getDefaultOptions.ios.js @@ -233,18 +233,6 @@ function createInstance ({ propsRef, type, rawOptions, currentInject, validProps }, enumerable: false }, - __componentPath: { - get () { - return currentInject.componentPath || '' - }, - enumerable: false - }, - __relation: { - get () { - return relation - }, - enumerable: false - }, __injectedRender: { get () { return currentInject.render || noop From f227571d146f44f192fdebbbeedbb41094fd13d3 Mon Sep 17 00:00:00 2001 From: xiaolei <1017653702@qq.com> Date: Mon, 20 Jan 2025 18:51:40 +0800 Subject: [PATCH 10/13] [lint] --- packages/core/src/platform/patch/getDefaultOptions.ios.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/platform/patch/getDefaultOptions.ios.js b/packages/core/src/platform/patch/getDefaultOptions.ios.js index 03f338cc84..03d5bb835f 100644 --- a/packages/core/src/platform/patch/getDefaultOptions.ios.js +++ b/packages/core/src/platform/patch/getDefaultOptions.ios.js @@ -259,13 +259,13 @@ function createInstance ({ propsRef, type, rawOptions, currentInject, validProps if (!isEmptyObject(relationInfo.relations)) { Object.defineProperties(instance, { __relations: { - get() { + get () { return relationInfo.relations }, enumerable: false }, __relationNodesMap: { - get() { + get () { return relationInfo.relationNodesMap }, enumerable: false From 64853bb0e19211d0545fdafa5d78675e2f6595fc Mon Sep 17 00:00:00 2001 From: xiaolei <1017653702@qq.com> Date: Mon, 20 Jan 2025 19:03:43 +0800 Subject: [PATCH 11/13] [update]fix instance property --- .../src/platform/patch/getDefaultOptions.ios.js | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/packages/core/src/platform/patch/getDefaultOptions.ios.js b/packages/core/src/platform/patch/getDefaultOptions.ios.js index 03d5bb835f..9fd5335d30 100644 --- a/packages/core/src/platform/patch/getDefaultOptions.ios.js +++ b/packages/core/src/platform/patch/getDefaultOptions.ios.js @@ -257,20 +257,8 @@ function createInstance ({ propsRef, type, rawOptions, currentInject, validProps } if (!isEmptyObject(relationInfo.relations)) { - Object.defineProperties(instance, { - __relations: { - get () { - return relationInfo.relations - }, - enumerable: false - }, - __relationNodesMap: { - get () { - return relationInfo.relationNodesMap - }, - enumerable: false - } - }) + instance.__relations = relationInfo.relations + instance.__relationNodesMap = relationInfo.relationNodesMap } // bind this & assign methods From c7d477df520a236fe8a007d0e2b8f1b59bceaf77 Mon Sep 17 00:00:00 2001 From: xiaolei <1017653702@qq.com> Date: Mon, 20 Jan 2025 20:37:21 +0800 Subject: [PATCH 12/13] [optimize] --- .../builtInMixins/relationsMixin.ios.js | 65 ++++++++++-------- .../platform/patch/getDefaultOptions.ios.js | 66 ++++++------------- 2 files changed, 55 insertions(+), 76 deletions(-) diff --git a/packages/core/src/platform/builtInMixins/relationsMixin.ios.js b/packages/core/src/platform/builtInMixins/relationsMixin.ios.js index 64755c0e44..ba4ab3ce1a 100644 --- a/packages/core/src/platform/builtInMixins/relationsMixin.ios.js +++ b/packages/core/src/platform/builtInMixins/relationsMixin.ios.js @@ -1,48 +1,55 @@ -import { MOUNTED, BEFOREUNMOUNT } from '../../core/innerLifecycle' +import { BEFORECREATE, MOUNTED, BEFOREUNMOUNT } from '../../core/innerLifecycle' + +const relationTypeMap = { + parent: 'child', + ancestor: 'descendant' +} export default function relationsMixin (mixinType) { if (mixinType === 'component') { return { + [BEFORECREATE] () { + this.__relationNodesMap = {} + }, [MOUNTED] () { - if (this.__relations) { - this.__mpxExecRelations('linked') - } + this.__mpxExecRelations('linked') }, [BEFOREUNMOUNT] () { - if (this.__relations) { - this.__mpxExecRelations('unlinked') - this.__relations = {} - } - if (this.__relationNodesMap) { - this.__relationNodesMap = {} - } + this.__mpxExecRelations('unlinked') + this.__relationNodesMap = {} }, methods: { getRelationNodes (path) { - return this.__relationNodesMap?.[path] || null + return this.__relationNodesMap[path] || null }, __mpxExecRelations (type) { - const relations = this.__relations - Object.keys(relations).forEach(path => { - const { target, targetRelation, relation } = relations[path] - const currentPath = this.__componentPath - if (type === 'linked') { - this.__mpxLinkRelationNodes(target, currentPath) - } else if (type === 'unlinked') { - this.__mpxRemoveRelationNodes(target, currentPath) - } - if (typeof targetRelation[type] === 'function') { - targetRelation[type].call(target, this) - } - if (typeof relation[type] === 'function') { - relation[type].call(this, target) + const relations = this.__mpxProxy.options.relations + const relationContext = this.__relation + const currentPath = this.__componentPath + Object.keys(relations).forEach((path) => { + const relation = relations[path] + const relationType = relation.type + if ((relationType === 'parent' || relationType === 'ancestor') && relationContext[path]) { + const target = relationContext[path] + const targetRelation = target.__mpxProxy.options.relations?.[currentPath] + if (targetRelation && targetRelation.type === relationTypeMap[relationType] && target.__componentPath === path) { + if (type === 'linked') { + this.__mpxLinkRelationNodes(target, currentPath) + } else if (type === 'unlinked') { + this.__mpxRemoveRelationNodes(target, currentPath) + } + if (typeof targetRelation[type] === 'function') { + targetRelation[type].call(target, this) + } + if (typeof relation[type] === 'function') { + relation[type].call(this, target) + } + this.__relationNodesMap[path] = [target] + } } }) }, __mpxLinkRelationNodes (target, path) { - if (!target.__relationNodesMap) { - target.__relationNodesMap = {} - } target.__relationNodesMap[path] = target.__relationNodesMap[path] || [] // 父级绑定子级 target.__relationNodesMap[path].push(this) }, diff --git a/packages/core/src/platform/patch/getDefaultOptions.ios.js b/packages/core/src/platform/patch/getDefaultOptions.ios.js index 9fd5335d30..86ad50d04b 100644 --- a/packages/core/src/platform/patch/getDefaultOptions.ios.js +++ b/packages/core/src/platform/patch/getDefaultOptions.ios.js @@ -3,7 +3,7 @@ import * as ReactNative from 'react-native' import { ReactiveEffect } from '../../observer/effect' import { watch } from '../../observer/watch' import { reactive, set, del } from '../../observer/reactive' -import { hasOwn, isFunction, noop, isObject, isArray, getByPath, collectDataset, hump2dash, dash2hump, callWithErrorHandling, wrapMethodsWithErrorHandling, isEmptyObject } from '@mpxjs/utils' +import { hasOwn, isFunction, noop, isObject, isArray, getByPath, collectDataset, hump2dash, dash2hump, callWithErrorHandling, wrapMethodsWithErrorHandling } from '@mpxjs/utils' import MpxProxy from '../../core/proxy' import { BEFOREUPDATE, ONLOAD, UPDATED, ONSHOW, ONHIDE, ONRESIZE, REACTHOOKSEXEC } from '../../core/innerLifecycle' import mergeOptions from '../../core/mergeOptions' @@ -193,7 +193,7 @@ const instanceProto = { } } -function createInstance ({ propsRef, type, rawOptions, currentInject, validProps, components, pageId, intersectionCtx, relationInfo }) { +function createInstance ({ propsRef, type, rawOptions, currentInject, validProps, components, pageId, intersectionCtx, relation }) { const instance = Object.create(instanceProto, { dataset: { get () { @@ -256,9 +256,13 @@ function createInstance ({ propsRef, type, rawOptions, currentInject, validProps }) } - if (!isEmptyObject(relationInfo.relations)) { - instance.__relations = relationInfo.relations - instance.__relationNodesMap = relationInfo.relationNodesMap + if (relation) { + Object.defineProperty(instance, '__relation', { + get () { + return relation + }, + enumerable: false + }) } // bind this & assign methods @@ -393,29 +397,26 @@ const RelationsContext = createContext(null) const checkRelation = (options) => { const relations = options.relations || {} let hasDescendantRelation = false - const ancestorRelations = {} + let hasAncestorRelation = false Object.keys(relations).forEach((path) => { const relation = relations[path] const type = relation.type if (['child', 'descendant'].includes(type)) { hasDescendantRelation = true } else if (['parent', 'ancestor'].includes(type)) { - ancestorRelations[path] = relation + hasAncestorRelation = true } }) return { hasDescendantRelation, - ancestorRelations + hasAncestorRelation } } const provideRelation = (instance, relation) => { const componentPath = instance.__componentPath if (relation) { - if (!relation[componentPath]) { - relation[componentPath] = instance - } - return relation + return Object.assign({}, relation, { [componentPath]: instance }) } else { return { [componentPath]: instance @@ -423,55 +424,26 @@ const provideRelation = (instance, relation) => { } } -const relationTypeMap = { - parent: 'child', - ancestor: 'descendant' -} - -const collectRelations = (ancestorRelations, relationMap, componentPath = '') => { - const relations = {} - const relationNodesMap = {} - Object.keys(ancestorRelations).forEach(path => { - const relation = ancestorRelations[path] - const type = relation.type - if (relationMap[path]) { - const target = relationMap[path] - const targetRelation = target.__mpxProxy.options.relations?.[componentPath] - if (targetRelation && targetRelation.type === relationTypeMap[type] && target.__componentPath === path) { - relations[path] = { - target, - targetRelation, - relation - } - relationNodesMap[path] = [target] - } - } - }) - - return { - relations, - relationNodesMap - } -} - export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) { rawOptions = mergeOptions(rawOptions, type, false) const components = Object.assign({}, rawOptions.components, currentInject.getComponents()) const validProps = Object.assign({}, rawOptions.props, rawOptions.properties) - const { hasDescendantRelation, ancestorRelations } = checkRelation(rawOptions) + const { hasDescendantRelation, hasAncestorRelation } = checkRelation(rawOptions) if (rawOptions.methods) rawOptions.methods = wrapMethodsWithErrorHandling(rawOptions.methods) const defaultOptions = memo(forwardRef((props, ref) => { const instanceRef = useRef(null) const propsRef = useRef(null) const intersectionCtx = useContext(IntersectionObserverContext) const pageId = useContext(RouteContext) - const relation = useContext(RelationsContext) - const relationInfo = collectRelations(ancestorRelations, relation, currentInject.componentPath) + let relation = null + if (hasDescendantRelation || hasAncestorRelation) { + relation = useContext(RelationsContext) + } propsRef.current = props let isFirst = false if (!instanceRef.current) { isFirst = true - instanceRef.current = createInstance({ propsRef, type, rawOptions, currentInject, validProps, components, pageId, intersectionCtx, relationInfo }) + instanceRef.current = createInstance({ propsRef, type, rawOptions, currentInject, validProps, components, pageId, intersectionCtx, relation }) } const instance = instanceRef.current useImperativeHandle(ref, () => { From 41a84156cd513607ac8d95eee0d4767ac50baa24 Mon Sep 17 00:00:00 2001 From: xiaolei <1017653702@qq.com> Date: Tue, 21 Jan 2025 13:49:32 +0800 Subject: [PATCH 13/13] [fix]edge case --- .../builtInMixins/relationsMixin.ios.js | 42 ++++++++++--------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/packages/core/src/platform/builtInMixins/relationsMixin.ios.js b/packages/core/src/platform/builtInMixins/relationsMixin.ios.js index ba4ab3ce1a..be8dc5b06b 100644 --- a/packages/core/src/platform/builtInMixins/relationsMixin.ios.js +++ b/packages/core/src/platform/builtInMixins/relationsMixin.ios.js @@ -26,28 +26,30 @@ export default function relationsMixin (mixinType) { const relations = this.__mpxProxy.options.relations const relationContext = this.__relation const currentPath = this.__componentPath - Object.keys(relations).forEach((path) => { - const relation = relations[path] - const relationType = relation.type - if ((relationType === 'parent' || relationType === 'ancestor') && relationContext[path]) { - const target = relationContext[path] - const targetRelation = target.__mpxProxy.options.relations?.[currentPath] - if (targetRelation && targetRelation.type === relationTypeMap[relationType] && target.__componentPath === path) { - if (type === 'linked') { - this.__mpxLinkRelationNodes(target, currentPath) - } else if (type === 'unlinked') { - this.__mpxRemoveRelationNodes(target, currentPath) + if (relations && relationContext) { + Object.keys(relations).forEach((path) => { + const relation = relations[path] + const relationType = relation.type + if ((relationType === 'parent' || relationType === 'ancestor') && relationContext[path]) { + const target = relationContext[path] + const targetRelation = target.__mpxProxy.options.relations?.[currentPath] + if (targetRelation && targetRelation.type === relationTypeMap[relationType] && target.__componentPath === path) { + if (type === 'linked') { + this.__mpxLinkRelationNodes(target, currentPath) + } else if (type === 'unlinked') { + this.__mpxRemoveRelationNodes(target, currentPath) + } + if (typeof targetRelation[type] === 'function') { + targetRelation[type].call(target, this) + } + if (typeof relation[type] === 'function') { + relation[type].call(this, target) + } + this.__relationNodesMap[path] = [target] } - if (typeof targetRelation[type] === 'function') { - targetRelation[type].call(target, this) - } - if (typeof relation[type] === 'function') { - relation[type].call(this, target) - } - this.__relationNodesMap[path] = [target] } - } - }) + }) + } }, __mpxLinkRelationNodes (target, path) { target.__relationNodesMap[path] = target.__relationNodesMap[path] || [] // 父级绑定子级