diff --git a/packages/inula-novdom/src/dom.ts b/packages/inula-novdom/src/dom.ts index 5a955cae..930b5d0e 100644 --- a/packages/inula-novdom/src/dom.ts +++ b/packages/inula-novdom/src/dom.ts @@ -15,6 +15,7 @@ import { watch } from 'inula-reactive'; import { isReactiveObj } from 'inula-reactive'; +import { RefCallback, RefObject } from './type'; /** * Creates a function that returns a Node created from the provided HTML string. @@ -93,7 +94,7 @@ function insertExpression(parent: Node, value: any, prevValue: any, marker?: Nod result = cleanChildren(parent, prevValue, marker); } else if (t === 'function') { // 在watch里面执行 - watchRender((prev) => { + watchRender(prev => { let v = value(); while (isReactiveObj(v)) { v = v.get(); @@ -108,7 +109,7 @@ function insertExpression(parent: Node, value: any, prevValue: any, marker?: Nod const array: any[] = []; const isPrevArray: boolean = prevValue && Array.isArray(prevValue); if (flattenArray(array, value)) { - watchRender((prev) => { + watchRender(prev => { result = insertExpression(parent, array, prev, marker); return result; }, prevValue); @@ -116,7 +117,8 @@ function insertExpression(parent: Node, value: any, prevValue: any, marker?: Nod return () => result; } - if (array.length === 0) { // 当前没有节点 + if (array.length === 0) { + // 当前没有节点 result = cleanChildren(parent, prevValue, marker); if (multi) { return result; @@ -135,7 +137,8 @@ function insertExpression(parent: Node, value: any, prevValue: any, marker?: Nod appendNodes(parent, array); } result = array; - } else if (value.nodeType) { // 是Node节点 + } else if (value.nodeType) { + // 是Node节点 if (Array.isArray(prevValue)) { if (multi) { return cleanChildren(parent, prevValue, marker, value); @@ -367,3 +370,18 @@ export function style( } } } + +/** + * Binds a ref to a node. + * @param node + * @param ref + */ +export function bindRef(node: T, ref: RefObject | RefCallback): void { + if (typeof ref === 'function') { + ref(node); + } else if (ref && 'current' in ref) { + (ref as RefObject).current = node; + } else { + throw new Error('Invalid ref'); + } +} diff --git a/packages/inula-novdom/src/type.ts b/packages/inula-novdom/src/type.ts index da3ec6ad..26441c00 100644 --- a/packages/inula-novdom/src/type.ts +++ b/packages/inula-novdom/src/type.ts @@ -20,3 +20,7 @@ export type AppDisposer = () => void; export type Props = Record; export type Context = Props; export type WithChildren = T & { children?: JSXElement }; +// --- ref --- +export type RefObject = { current: T | null }; +export type RefCallback = (instance: T | null) => void; +export type Ref = RefObject | RefCallback | T; diff --git a/packages/inula-novdom/tests/env.test.tsx b/packages/inula-novdom/tests/env.test.tsx index eda3ba23..c9f20e18 100644 --- a/packages/inula-novdom/tests/env.test.tsx +++ b/packages/inula-novdom/tests/env.test.tsx @@ -406,7 +406,7 @@ describe('env', () => { render(() => $$runComponent(App, {}), container); expect(container.innerHTML).toMatchInlineSnapshot( - '
  • root
    • sub1
    • root-sub1-1
    • root-sub1-2
    • sub2
    • root-sub2-3
      • sub3
      • root-sub2-sub3-4
    • root-sub2-5
', + '"
  • root
    • sub1
    • root-sub1-1
    • root-sub1-2
    • sub2
    • root-sub2-3
      • sub3
      • root-sub2-sub3-4
    • root-sub2-5
"', ); }); }); diff --git a/packages/inula-novdom/tests/ref.test.ts b/packages/inula-novdom/tests/ref.test.ts new file mode 100644 index 00000000..1605c4ce --- /dev/null +++ b/packages/inula-novdom/tests/ref.test.ts @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2024 Huawei Technologies Co.,Ltd. + * + * openInula is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-nocheck For the compiled code. + +import { describe, expect } from 'vitest'; +import { domTest as it } from './utils'; +import { template as $$template, bindRef as $$bindRef } from '../src/dom'; +import { runComponent as $$runComponent, render } from '../src/core'; + +describe('ref', () => { + it('should reference to dom.', ({ container }) => { + /* + * 源码: + * function App(props) { + * let ref; + * return ; + * } + */ + const $tmpl$ = $$template(``); + let ref: Node; + + function App() { + return (() => { + const $div = $tmpl$(); + const $ref = ref; + typeof $ref === 'function' ? $$bindRef($ref, $div) : (ref = $div); + return $div; + })(); + } + + render(() => $$runComponent(App, {}), container); + expect(ref).toBe(container.firstChild); + }); + + it('should reference to component.', ({ container }) => { + /** + * 源码: + * // App.tsx + * import Canvas from "./canvas"; + * + * function App() { + * let canvas; + * return ; + * } + * + * render(() => , document.getElementById("app")); + * + * // canvas.tsx + * export default function Canvas(props) { + * return ; + * } + */ + const $tmpl$ = $$template(''); + + function Canvas() { + return (() => { + const $div = $tmpl$(); + const $ref = canvas; + typeof $ref === 'function' ? $$bindRef($ref, $div) : (canvas = $div); + return $div; + })(); + } + + let canvas: Node; + + function App() { + return $$runComponent(Canvas, { + ref($r: Node) { + const $ref = canvas; + typeof $ref === 'function' ? $ref($r) : (canvas = $r); + }, + }); + } + + render(() => $$runComponent(App, {}), container); + expect(canvas).toBe(container.firstChild); + }); +});