!146 feat(no-vdom): Ref

* feat(no-vdom): Ref
This commit is contained in:
Hoikan 2024-02-18 07:33:41 +00:00 committed by 陈超涛
parent 843a64ebd9
commit 727db59da0
4 changed files with 118 additions and 5 deletions

View File

@ -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<T>(node: T, ref: RefObject<T> | RefCallback<T>): void {
if (typeof ref === 'function') {
ref(node);
} else if (ref && 'current' in ref) {
(ref as RefObject<T>).current = node;
} else {
throw new Error('Invalid ref');
}
}

View File

@ -20,3 +20,7 @@ export type AppDisposer = () => void;
export type Props = Record<string, unknown>;
export type Context = Props;
export type WithChildren<T = Props> = T & { children?: JSXElement };
// --- ref ---
export type RefObject<T> = { current: T | null };
export type RefCallback<T> = (instance: T | null) => void;
export type Ref<T> = RefObject<T> | RefCallback<T> | T;

View File

@ -406,7 +406,7 @@ describe('env', () => {
render(() => $$runComponent(App, {}), container);
expect(container.innerHTML).toMatchInlineSnapshot(
'<ul><li>root</li><ul><li>sub1</li><li>root-sub1-1</li><li>root-sub1-2</li></ul><ul><li>sub2</li><li>root-sub2-3</li><ul><li>sub3</li><li>root-sub2-sub3-4</li></ul><li>root-sub2-5</li></ul></ul>',
'"<ul><li>root</li><ul><li>sub1</li><li>root-sub1-1</li><li>root-sub1-2</li></ul><ul><li>sub2</li><li>root-sub2-3</li><ul><li>sub3</li><li>root-sub2-sub3-4</li></ul><li>root-sub2-5</li></ul></ul>"',
);
});
});

View File

@ -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 <canvas ref={ref} width="256" height="256" />;
* }
*/
const $tmpl$ = $$template(`<canvas width="256" height="256"></canvas>`);
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 <Canvas ref={canvas} />;
* }
*
* render(() => <App />, document.getElementById("app"));
*
* // canvas.tsx
* export default function Canvas(props) {
* return <canvas ref={props.ref} width="256" height="256" />;
* }
*/
const $tmpl$ = $$template('<canvas width="256" height="256"></canvas>');
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);
});
});