From ab7136d8e5514d7cc8c325369b16047e7e4eae0f Mon Sep 17 00:00:00 2001 From: dos1in Date: Thu, 14 May 2026 19:45:22 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat(drn):=20=E6=94=AF=E6=8C=81=E8=87=AA?= =?UTF-8?q?=E5=AE=9A=E4=B9=89=E7=BB=84=E4=BB=B6=E5=8F=AF=E9=80=9A=E8=BF=87?= =?UTF-8?q?=20createSelectorQuery=20=E8=8E=B7=E5=8F=96=E5=BC=95=E7=94=A8?= =?UTF-8?q?=E5=AF=B9=E8=B1=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../rn/create-selector-query.spec.js | 90 +++++++++++++++++++ .../api/create-selector-query/rnNodesRef.js | 13 ++- .../create-selector-query/rnSelectQuery.js | 2 +- packages/core/__tests__/ios/refsMixin.spec.js | 30 +++++++ .../platform/builtInMixins/refsMixin.ios.js | 3 +- .../platform/patch/getDefaultOptions.ios.js | 11 +++ 6 files changed, 143 insertions(+), 6 deletions(-) create mode 100644 packages/api-proxy/__tests__/rn/create-selector-query.spec.js create mode 100644 packages/core/__tests__/ios/refsMixin.spec.js diff --git a/packages/api-proxy/__tests__/rn/create-selector-query.spec.js b/packages/api-proxy/__tests__/rn/create-selector-query.spec.js new file mode 100644 index 0000000000..5755c29e72 --- /dev/null +++ b/packages/api-proxy/__tests__/rn/create-selector-query.spec.js @@ -0,0 +1,90 @@ +import SelectorQuery from '../../src/platform/api/create-selector-query/rnSelectQuery' + +describe('createSelectorQuery for RN', () => { + test('select should support custom component refs', (done) => { + const componentRef = { + __getNodeInstance () { + return { + nodeRef: { + current: true + }, + props: { + current: {} + }, + instance: { + ref: 'custom-component-ref' + } + } + } + } + const component = { + __selectRef: jest.fn(() => componentRef) + } + const query = new SelectorQuery() + + query.in(component).select('.custom').ref() + query.exec((res) => { + expect(component.__selectRef).toHaveBeenCalledWith('.custom', ['node', 'component'], undefined) + expect(res).toEqual([{ ref: 'custom-component-ref' }]) + done() + }) + }) + + test('select should support custom component refs by id', (done) => { + const componentRef = { + __getNodeInstance () { + return { + nodeRef: { + current: true + }, + props: { + current: {} + }, + instance: { + ref: 'custom-component-ref' + } + } + } + } + const component = { + __selectRef: jest.fn(() => componentRef) + } + const query = new SelectorQuery() + + query.in(component).select('#custom').ref() + query.exec((res) => { + expect(component.__selectRef).toHaveBeenCalledWith('#custom', ['node', 'component'], undefined) + expect(res).toEqual([{ ref: 'custom-component-ref' }]) + done() + }) + }) + + test('selectAll should support custom component refs', (done) => { + const componentRefs = ['custom-component-ref-1', 'custom-component-ref-2'].map(ref => ({ + __getNodeInstance () { + return { + nodeRef: { + current: true + }, + props: { + current: {} + }, + instance: { + ref + } + } + } + })) + const component = { + __selectRef: jest.fn(() => componentRefs) + } + const query = new SelectorQuery() + + query.in(component).selectAll('.custom').ref() + query.exec((res) => { + expect(component.__selectRef).toHaveBeenCalledWith('.custom', ['node', 'component'], true) + expect(res).toEqual([[{ ref: 'custom-component-ref-1' }, { ref: 'custom-component-ref-2' }]]) + done() + }) + }) +}) diff --git a/packages/api-proxy/src/platform/api/create-selector-query/rnNodesRef.js b/packages/api-proxy/src/platform/api/create-selector-query/rnNodesRef.js index 63741b9860..ec23057686 100644 --- a/packages/api-proxy/src/platform/api/create-selector-query/rnNodesRef.js +++ b/packages/api-proxy/src/platform/api/create-selector-query/rnNodesRef.js @@ -11,8 +11,8 @@ import { const flushRefFns = (nodeInstances, fns, single) => { // wx的数据格式:对于具体方法接受到的回调传参,如果获取的 nodeRef 只有一个,那么只需要返回一条数据而不是数组,但是 exec 里面统一都是数组 const mountedNodeInstance = nodeInstances - .map((instance) => instance.getNodeInstance()) - .filter(({ nodeRef }) => nodeRef.current) // 如果有 nodeRef,表明目前组件处于挂载中 + .map(getNodeInstance) + .filter((nodeInstance) => nodeInstance && nodeInstance.nodeRef && nodeInstance.nodeRef.current) // 如果有 nodeRef,表明目前组件处于挂载中 if (mountedNodeInstance.length) { return Promise.all( mountedNodeInstance.map((instance) => flushFns(instance, fns)) @@ -30,6 +30,11 @@ const flushFns = (nodeInstance, fns) => { }) } +const getNodeInstance = (ref) => { + const getNodeInstance = ref && (ref.getNodeInstance || ref.__getNodeInstance) + return getNodeInstance && getNodeInstance.call(ref) +} + const wrapFn = (fn) => { return (nodeRef) => { return new Promise((resolve) => { @@ -191,10 +196,10 @@ class NodeRef { fns.push(getPlainProps(plainProps)) } if (measureProps.length) { - const nodeInstance = - this.nodeRefs[0] && this.nodeRefs[0].getNodeInstance() + const nodeInstance = getNodeInstance(this.nodeRefs[0]) const hasMeasureFn = nodeInstance && + nodeInstance.nodeRef && nodeInstance.nodeRef.current && nodeInstance.nodeRef.current.measure if (hasMeasureFn) { diff --git a/packages/api-proxy/src/platform/api/create-selector-query/rnSelectQuery.js b/packages/api-proxy/src/platform/api/create-selector-query/rnSelectQuery.js index 14a0ea2be0..e79f485b4e 100644 --- a/packages/api-proxy/src/platform/api/create-selector-query/rnSelectQuery.js +++ b/packages/api-proxy/src/platform/api/create-selector-query/rnSelectQuery.js @@ -34,7 +34,7 @@ export default class SelectorQuery { warn('SelectQuery.select don\'t support combinator selector, it only supports selector like #a or .a or .a.b now.') return new NodeRef([], this, !all) } - const refs = this._component && this._component.__selectRef(selector, 'node', all) + const refs = this._component && this._component.__selectRef(selector, ['node', 'component'], all) return new NodeRef(refs, this, !all) } diff --git a/packages/core/__tests__/ios/refsMixin.spec.js b/packages/core/__tests__/ios/refsMixin.spec.js new file mode 100644 index 0000000000..d696402d1b --- /dev/null +++ b/packages/core/__tests__/ios/refsMixin.spec.js @@ -0,0 +1,30 @@ +import getRefsMixin from '../../src/platform/builtInMixins/refsMixin.ios' + +jest.mock('@mpxjs/api-proxy', () => ({ + createSelectorQuery: jest.fn() +})) + +describe('refsMixin for RN', () => { + test('__selectRef should support multiple ref types', () => { + const { methods } = getRefsMixin() + const componentRef = {} + const nodeRef = {} + const target = { + __refs: { + '.custom': [ + { + type: 'component', + instance: componentRef + }, + { + type: 'node', + instance: nodeRef + } + ] + } + } + + expect(methods.__selectRef.call(target, '.custom', ['node', 'component'])).toBe(componentRef) + expect(methods.__selectRef.call(target, '.custom', ['node', 'component'], true)).toEqual([componentRef, nodeRef]) + }) +}) diff --git a/packages/core/src/platform/builtInMixins/refsMixin.ios.js b/packages/core/src/platform/builtInMixins/refsMixin.ios.js index 9a761624d1..e8bd1627af 100644 --- a/packages/core/src/platform/builtInMixins/refsMixin.ios.js +++ b/packages/core/src/platform/builtInMixins/refsMixin.ios.js @@ -53,12 +53,13 @@ export default function getRefsMixin () { return this.__refCache[refFnId] }, __selectRef (selector, refType, all = false) { + const refTypes = Array.isArray(refType) ? refType : [refType] const splitedSelector = selector.match(/(#|\.)?[^.#]+/g) || [] const refsArr = splitedSelector.map(selector => { const refs = this.__refs[selector] || [] const res = [] refs.forEach(({ type, instance }) => { - if (type === refType) { + if (refTypes.indexOf(type) > -1) { res.push(instance) } }) diff --git a/packages/core/src/platform/patch/getDefaultOptions.ios.js b/packages/core/src/platform/patch/getDefaultOptions.ios.js index a3368d2761..1f75adf6d0 100644 --- a/packages/core/src/platform/patch/getDefaultOptions.ios.js +++ b/packages/core/src/platform/patch/getDefaultOptions.ios.js @@ -124,6 +124,10 @@ const instanceProto = { createIntersectionObserver (opt) { return createIntersectionObserver(this, opt, this.__intersectionCtx) }, + __getNodeInstance () { + const hostRef = this.__hostRef && this.__hostRef.current + return hostRef && hostRef.getNodeInstance && hostRef.getNodeInstance() + }, // 触发页面范围内的所有observer的计算 __triggerIntersectionObserver () { const intersectionObservers = this.__intersectionCtx @@ -645,6 +649,7 @@ export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) { if (rawOptions.methods) rawOptions.methods = wrapMethodsWithErrorHandling(rawOptions.methods) const defaultOptions = memo(forwardRef((props, ref) => { const instanceRef = useRef(null) + const hostRef = useRef(null) const propsRef = useRef(null) const intersectionCtx = useContext(IntersectionObserverContext) const { pageId } = useContext(RouteContext) || {} @@ -660,6 +665,9 @@ export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) { instanceRef.current = createInstance({ propsRef, type, rawOptions, currentInject, validProps, componentsMap, pageId, intersectionCtx, relation, parentProvides }) } const instance = instanceRef.current + if (type === 'component') { + instance.__hostRef = hostRef + } useImperativeHandle(ref, () => { return instance }) @@ -736,6 +744,9 @@ export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) { // 对于组件未注册的属性继承到host节点上,如事件、样式和其他属性等 const rootProps = getRootProps(props, validProps) rootProps.style = Object.assign({}, root.props.style, rootProps.style) + if (type === 'component') { + rootProps.ref = hostRef + } // update root props root = cloneElement(root, rootProps) } From e46f1ea1c273635624089b0a4d4a7f8f4629e6f0 Mon Sep 17 00:00:00 2001 From: dos1in Date: Thu, 14 May 2026 19:45:42 +0800 Subject: [PATCH 2/2] =?UTF-8?q?doc:=20=E6=9B=B4=E6=96=B0=E5=AF=B9=E5=BA=94?= =?UTF-8?q?=E7=9A=84=20skills=20=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .agents/skills/mpx2rn/SKILL.md | 10 +++++----- .../skills/mpx2rn/references/rn-api-reference.md | 2 ++ .../mpx2rn/references/rn-script-reference.md | 14 +++++++------- .../api-proxy/wxml/createSelectorQuery.md | 7 ++++--- docs-vitepress/guide/rn/application-api.md | 9 +++++++-- docs-vitepress/guide/rn/component.md | 4 ++-- 6 files changed, 27 insertions(+), 19 deletions(-) diff --git a/.agents/skills/mpx2rn/SKILL.md b/.agents/skills/mpx2rn/SKILL.md index ce4377e920..5112eeab58 100644 --- a/.agents/skills/mpx2rn/SKILL.md +++ b/.agents/skills/mpx2rn/SKILL.md @@ -68,7 +68,7 @@ Mpx 是一个以微信小程序语法为基础、进行了类 Vue 语法拓展 1. **生命周期 / 构造选项**:仅使用 [逻辑能力参考](./references/rn-script-reference.md) 中标注 RN 支持的生命周期与构造选项;避免使用 `onShareTimeline` / `onTabItemTap` / `onAddToFavorites` / `onSaveExitState` 等 RN 不支持项。 2. **环境 API**:通过 `@mpxjs/api-proxy` 提供的统一 `mpx.xxx` API 调用环境能力,避免直接使用 `wx.xxx` / `my.xxx`;具体支持范围见 [环境 API 参考](./references/rn-api-reference.md);如用户通过 `custom` 配置扩充拓展了环境 API 能力,以用户说明为准。 -3. **selector 映射**:脚本中的 `selectComponent` / `selectAllComponents` / `createSelectorQuery` / `createIntersectionObserver` 等 selector API 仅支持 `#id` / `.class`,且对应模板节点须声明空 `wx:ref` 以建立编译期 selector 映射。详见 [逻辑能力参考 · 实例方法与属性](./references/rn-script-reference.md#页面--组件实例方法与属性)。 +3. **selector 映射**:脚本中的 `selectComponent` / `selectAllComponents` / `createSelectorQuery` / `createIntersectionObserver` 等 selector API 仅支持 `#id` / `.class`,且对应模板节点或组件须声明空 `wx:ref` 以建立编译期 selector 映射。详见 [逻辑能力参考 · 实例方法与属性](./references/rn-script-reference.md#页面--组件实例方法与属性)。 ### 样式(style)约束 @@ -130,14 +130,14 @@ Mpx 是一个以微信小程序语法为基础、进行了类 Vue 语法拓展 - 读取 [模板能力参考](./references/rn-template-reference.md),对 `