diff --git a/packages/core/src/convertor/wxToReact.js b/packages/core/src/convertor/wxToReact.js index 34e59760e1..ec7476601c 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'] +const unsupported = ['moved', 'definitionFilter'] 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.ios.js b/packages/core/src/platform/builtInMixins/relationsMixin.ios.js new file mode 100644 index 0000000000..be8dc5b06b --- /dev/null +++ b/packages/core/src/platform/builtInMixins/relationsMixin.ios.js @@ -0,0 +1,67 @@ +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] () { + this.__mpxExecRelations('linked') + }, + [BEFOREUNMOUNT] () { + this.__mpxExecRelations('unlinked') + this.__relationNodesMap = {} + }, + methods: { + getRelationNodes (path) { + return this.__relationNodesMap[path] || null + }, + __mpxExecRelations (type) { + const relations = this.__mpxProxy.options.relations + const relationContext = this.__relation + const currentPath = this.__componentPath + 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] + } + } + }) + } + }, + __mpxLinkRelationNodes (target, path) { + target.__relationNodesMap[path] = target.__relationNodesMap[path] || [] // 父级绑定子级 + target.__relationNodesMap[path].push(this) + }, + __mpxRemoveRelationNodes (target, 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 5bd699b75e..25832380c2 100644 --- a/packages/core/src/platform/patch/getDefaultOptions.ios.js +++ b/packages/core/src/platform/patch/getDefaultOptions.ios.js @@ -195,7 +195,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 () { @@ -249,6 +249,24 @@ function createInstance ({ propsRef, type, rawOptions, currentInject, validProps } }) + if (type === 'component') { + Object.defineProperty(instance, '__componentPath', { + get () { + return currentInject.componentPath || '' + }, + enumerable: false + }) + } + + if (relation) { + Object.defineProperty(instance, '__relation', { + get () { + return relation + }, + enumerable: false + }) + } + // bind this & assign methods if (rawOptions.methods) { Object.entries(rawOptions.methods).forEach(([key, method]) => { @@ -376,10 +394,43 @@ function usePageStatus (navigation, pageId) { }, [navigation]) } +const RelationsContext = createContext(null) + +const checkRelation = (options) => { + const relations = options.relations || {} + let hasDescendantRelation = false + 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)) { + hasAncestorRelation = true + } + }) + return { + hasDescendantRelation, + hasAncestorRelation + } +} + +const provideRelation = (instance, relation) => { + const componentPath = instance.__componentPath + if (relation) { + return Object.assign({}, relation, { [componentPath]: instance }) + } else { + return { + [componentPath]: instance + } + } +} + 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, hasAncestorRelation } = checkRelation(rawOptions) if (rawOptions.methods) rawOptions.methods = wrapMethodsWithErrorHandling(rawOptions.methods) const defaultOptions = memo(forwardRef((props, ref) => { const instanceRef = useRef(null) @@ -387,12 +438,16 @@ export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) { const intersectionCtx = useContext(IntersectionObserverContext) const pageId = useContext(RouteContext) const parentProvides = useContext(ProviderContext) + let relation = null + if (hasDescendantRelation || hasAncestorRelation) { + relation = useContext(RelationsContext) + } propsRef.current = props let isFirst = false if (!instanceRef.current) { isFirst = true rawOptions.parentProvides = parentProvides - 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, () => { @@ -477,25 +532,28 @@ 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 createElement( - ProviderContext.Provider, - { value: proxy.provides }, - cloneElement(root, rootProps) - ) + root = cloneElement(root, rootProps) } const provides = useMemo(() => proxy.provides, [proxy.provides]) if (provides) { - return createElement(ProviderContext.Provider, { value: provides }, root) + root = createElement(ProviderContext.Provider, { value: provides }, root) } - return root + return hasDescendantRelation + ? createElement(RelationsContext.Provider, + { + value: provideRelation(instance, relation) + }, + root + ) + : root })) if (rawOptions.options?.isCustomText) { diff --git a/packages/webpack-plugin/lib/react/processScript.js b/packages/webpack-plugin/lib/react/processScript.js index 7ad2a9913f..fe5b2e2ad3 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 @@ -64,7 +65,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..943aa0889b 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,9 @@ global.currentInject.firstPage = ${JSON.stringify(firstPage)}\n` content += `global.currentInject.getComponents = function () { return ${shallowStringify(componentsMap)} }\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`