Skip to content

Commit

Permalink
Merge pull request #1679 from CommanderXL/feat-rn-relations
Browse files Browse the repository at this point in the history
[feat]rn-relatons
  • Loading branch information
hiyuki authored Jan 21, 2025
2 parents 87d27fd + 41a8415 commit 6edcd65
Show file tree
Hide file tree
Showing 6 changed files with 145 additions and 10 deletions.
2 changes: 1 addition & 1 deletion packages/core/src/convertor/wxToReact.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/platform/builtInMixins/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ export default function getBuiltInMixins ({ type, rawOptions = {} }) {
directiveHelperMixin(),
styleHelperMixin(),
refsMixin(),
i18nMixin()
i18nMixin(),
relationsMixin(type)
]
} else if (__mpx_mode__ === 'web') {
bulitInMixins = [
Expand Down
67 changes: 67 additions & 0 deletions packages/core/src/platform/builtInMixins/relationsMixin.ios.js
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
}
}
74 changes: 68 additions & 6 deletions packages/core/src/platform/patch/getDefaultOptions.ios.js
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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 () {
Expand Down Expand Up @@ -247,6 +247,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]) => {
Expand Down Expand Up @@ -374,21 +392,58 @@ 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)
const propsRef = useRef(null)
const intersectionCtx = useContext(IntersectionObserverContext)
const pageId = useContext(RouteContext)
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 })
instanceRef.current = createInstance({ propsRef, type, rawOptions, currentInject, validProps, components, pageId, intersectionCtx, relation })
}
const instance = instanceRef.current
useImperativeHandle(ref, () => {
Expand Down Expand Up @@ -473,15 +528,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 cloneElement(root, rootProps)
root = cloneElement(root, rootProps)
}
return root
return hasDescendantRelation
? createElement(RelationsContext.Provider,
{
value: provideRelation(instance, relation)
},
root
)
: root
}))

if (rawOptions.options?.isCustomText) {
Expand Down
3 changes: 2 additions & 1 deletion packages/webpack-plugin/lib/react/processScript.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ module.exports = function (script, {
moduleId,
isProduction,
jsonConfig,
outputPath,
builtInComponentsMap,
localComponentsMap,
localPagesMap
Expand Down Expand Up @@ -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`
}
Expand Down
6 changes: 5 additions & 1 deletion packages/webpack-plugin/lib/react/script-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ function buildGlobalParams ({
jsonConfig,
componentsMap,
pagesMap,
firstPage
firstPage,
outputPath
}) {
let content = ''
if (ctorType === 'app') {
Expand Down Expand Up @@ -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`
Expand Down

0 comments on commit 6edcd65

Please sign in to comment.