-
Notifications
You must be signed in to change notification settings - Fork 382
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
[feat]rn-relatons #1679
Changes from 17 commits
503e927
4418593
4ded781
a2fd798
c5fe79c
a215026
a998767
6f6774d
c44dc0c
9cecc80
0f7fc85
d3ccb55
6ec95f0
81e85fe
5bf606f
f227571
64853bb
c7d477d
41a8415
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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) | ||
} | ||
} | ||
} | ||
} | ||
} |
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' | ||
|
@@ -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 () { | ||
|
@@ -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]) => { | ||
|
@@ -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 { | ||
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] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 解析放到mount中进行,render中简单透传relation即可 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if(hasDescendantRelation || hasAncestorRelation) { |
||
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, () => { | ||
|
@@ -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) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
需要返回一个新对象,而不是修改原有对象