Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat]rn-relatons #1679

Merged
merged 19 commits into from
Jan 21, 2025
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
58 changes: 58 additions & 0 deletions packages/core/src/platform/builtInMixins/relationsMixin.ios.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { MOUNTED, BEFOREUNMOUNT } from '../../core/innerLifecycle'

export default function relationsMixin (mixinType) {
if (mixinType === 'component') {
return {
[MOUNTED] () {
if (this.__relations) {
this.__mpxExecRelations('linked')
}
},
[BEFOREUNMOUNT] () {
if (this.__relations) {
this.__mpxExecRelations('unlinked')
this.__relations = {}
}
if (this.__relationNodesMap) {
this.__relationNodesMap = {}
}
},
methods: {
getRelationNodes (path) {
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)
}
})
},
__mpxLinkRelationNodes (target, path) {
if (!target.__relationNodesMap) {
target.__relationNodesMap = {}
}
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)
}
}
}
}
}
104 changes: 97 additions & 7 deletions packages/core/src/platform/patch/getDefaultOptions.ios.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
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'
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'
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, relationInfo }) {
const instance = Object.create(instanceProto, {
dataset: {
get () {
Expand Down Expand Up @@ -247,6 +247,20 @@ function createInstance ({ propsRef, type, rawOptions, currentInject, validProps
}
})

if (type === 'component') {
Object.defineProperty(instance, '__componentPath', {
get () {
return currentInject.componentPath || ''
},
enumerable: false
})
}

if (!isEmptyObject(relationInfo.relations)) {
instance.__relations = relationInfo.relations
instance.__relationNodesMap = relationInfo.relationNodesMap
}

// bind this & assign methods
if (rawOptions.methods) {
Object.entries(rawOptions.methods).forEach(([key, method]) => {
Expand Down Expand Up @@ -374,21 +388,90 @@ function usePageStatus (navigation, pageId) {
}, [navigation])
}

const RelationsContext = createContext(null)

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
if (['child', 'descendant'].includes(type)) {
hasDescendantRelation = true
} else if (['parent', 'ancestor'].includes(type)) {
ancestorRelations[path] = relation
}
})
return {
hasDescendantRelation,
ancestorRelations
}
}

const provideRelation = (instance, relation) => {
const componentPath = instance.__componentPath
if (relation) {
if (!relation[componentPath]) {
relation[componentPath] = instance
}
return relation
} else {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

需要返回一个新对象,而不是修改原有对象

return {
[componentPath]: instance
}
}
}

const relationTypeMap = {
parent: 'child',
ancestor: 'descendant'
}

const collectRelations = (ancestorRelations, relationMap, componentPath = '') => {
const relations = {}
const relationNodesMap = {}
Object.keys(ancestorRelations).forEach(path => {
const relation = ancestorRelations[path]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个遍历还是放在mounted时执行吧,可以和exec('linked')用一个循环

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)
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)
Copy link
Collaborator

@hiyuki hiyuki Jan 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

解析放到mount中进行,render中简单透传relation即可

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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, relationInfo })
}
const instance = instanceRef.current
useImperativeHandle(ref, () => {
Expand Down Expand Up @@ -473,15 +556,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
Loading