!164 optimize(no-vdom): switch condition branches to plain array
* optimize(no-vdom): switch condition branches to plain array * test(no-vdom): switch to jsx * feat(reactive): creatRoot * test(no-vdom): switch render test to jsx
This commit is contained in:
parent
79a34e2849
commit
e463938a9d
|
@ -23,16 +23,17 @@ type CondExpression = boolean | (() => boolean);
|
|||
type Branch = JSXElement | (() => JSXElement);
|
||||
|
||||
export interface CondProps {
|
||||
// Array of tuples, first item is the condition, second is the branch to render
|
||||
branches: [CondExpression, Branch][];
|
||||
// The odd number of branches is the condition expression and the even number of branches is the branch
|
||||
branches: (CondExpression | Branch)[];
|
||||
}
|
||||
|
||||
export function Cond(props: CondProps) {
|
||||
// Find the first branch that matches the condition
|
||||
// Any signal that used in condition expression, will trigger the condition to recompute
|
||||
const currentBranch = computed(() => {
|
||||
for (let i = 0; i < props.branches.length; i++) {
|
||||
const [condition, branch] = props.branches[i];
|
||||
for (let i = 0; i < props.branches.length; i=i+2) {
|
||||
const condition = props.branches[i];
|
||||
const branch = props.branches[i + 1];
|
||||
if (typeof condition === 'function' ? condition() : condition) {
|
||||
return branch;
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
*/
|
||||
|
||||
import { insert } from './dom';
|
||||
import { RNode, untrack, setScheduler } from 'inula-reactive';
|
||||
import { RNode, untrack, setScheduler, createRoot } from 'inula-reactive';
|
||||
import { readContext } from './components/Env';
|
||||
|
||||
// enable the scheduler
|
||||
|
@ -50,11 +50,9 @@ export function render(codeFn: CodeFunction, element: HTMLElement): () => void {
|
|||
throw new Error('Render target is not valid.');
|
||||
}
|
||||
|
||||
const disposer = (): void => {
|
||||
// TODO
|
||||
};
|
||||
|
||||
insert(element, codeFn(), element.firstChild ? null : undefined);
|
||||
const disposer = createRoot(() => {
|
||||
insert(element, codeFn(), element.firstChild ? null : undefined);
|
||||
});
|
||||
|
||||
return () => {
|
||||
disposer();
|
||||
|
|
|
@ -67,33 +67,28 @@ describe('conditions', () => {
|
|||
$$runComponent(Cond, {
|
||||
get branches() {
|
||||
return [
|
||||
[
|
||||
() => x.get() > 10,
|
||||
() => {
|
||||
const _el$3 = _tmpl$(),
|
||||
_el$4 = _el$3.firstChild;
|
||||
$$insert(_el$3, x, _el$4);
|
||||
return _el$3;
|
||||
},
|
||||
],
|
||||
[
|
||||
() => 5 > x.get(),
|
||||
() => {
|
||||
const _el$5 = _tmpl$2(),
|
||||
_el$6 = _el$5.firstChild;
|
||||
$$insert(_el$5, x, _el$6);
|
||||
return _el$5;
|
||||
},
|
||||
],
|
||||
[
|
||||
true,
|
||||
() => {
|
||||
const _el$7 = _tmpl$4(),
|
||||
_el$8 = _el$7.firstChild;
|
||||
$$insert(_el$7, x, _el$8);
|
||||
return _el$7;
|
||||
},
|
||||
],
|
||||
() => x.get() > 10,
|
||||
() => {
|
||||
const _el$3 = _tmpl$(),
|
||||
_el$4 = _el$3.firstChild;
|
||||
$$insert(_el$3, x, _el$4);
|
||||
return _el$3;
|
||||
},
|
||||
() => 5 > x.get(),
|
||||
() => {
|
||||
const _el$5 = _tmpl$2(),
|
||||
_el$6 = _el$5.firstChild;
|
||||
$$insert(_el$5, x, _el$6);
|
||||
return _el$5;
|
||||
},
|
||||
|
||||
true,
|
||||
() => {
|
||||
const _el$7 = _tmpl$4(),
|
||||
_el$8 = _el$7.firstChild;
|
||||
$$insert(_el$7, x, _el$8);
|
||||
return _el$7;
|
||||
},
|
||||
];
|
||||
},
|
||||
}),
|
||||
|
@ -150,15 +145,13 @@ describe('conditions', () => {
|
|||
$$runComponent(Cond, {
|
||||
get branches() {
|
||||
return [
|
||||
[
|
||||
() => x.get() > 10,
|
||||
() => {
|
||||
const _el$3 = _tmpl$(),
|
||||
_el$4 = _el$3.firstChild;
|
||||
$$insert(_el$3, x, _el$4);
|
||||
return _el$3;
|
||||
},
|
||||
],
|
||||
() => x.get() > 10,
|
||||
() => {
|
||||
const _el$3 = _tmpl$(),
|
||||
_el$4 = _el$3.firstChild;
|
||||
$$insert(_el$3, x, _el$4);
|
||||
return _el$3;
|
||||
},
|
||||
];
|
||||
},
|
||||
}),
|
||||
|
@ -225,53 +218,43 @@ describe('conditions', () => {
|
|||
$$runComponent(Cond, {
|
||||
get branches() {
|
||||
return [
|
||||
[
|
||||
() => x.get() > 10,
|
||||
() => {
|
||||
const _el$3 = _tmpl$(),
|
||||
_el$4 = _el$3.firstChild;
|
||||
$$insert(_el$3, x, _el$4);
|
||||
return _el$3;
|
||||
},
|
||||
],
|
||||
[
|
||||
() => 5 > x.get(),
|
||||
() => {
|
||||
const _el$5 = _tmpl$2(),
|
||||
_el$6 = _el$5.firstChild;
|
||||
$$insert(_el$5, x, _el$6);
|
||||
return _el$5;
|
||||
},
|
||||
],
|
||||
[
|
||||
true,
|
||||
() => {
|
||||
return $$runComponent(Cond, {
|
||||
get branches() {
|
||||
return [
|
||||
[
|
||||
() => x.get() > 7,
|
||||
() => {
|
||||
const _el$8 = _tmpl$3(),
|
||||
_el$9 = _el$8.firstChild;
|
||||
$$insert(_el$8, x, _el$9);
|
||||
return _el$8;
|
||||
},
|
||||
],
|
||||
[
|
||||
true,
|
||||
() => {
|
||||
const _el$10 = _tmpl$4(),
|
||||
_el$11 = _el$10.firstChild;
|
||||
$$insert(_el$10, x, _el$11);
|
||||
return _el$10;
|
||||
},
|
||||
],
|
||||
];
|
||||
},
|
||||
});
|
||||
},
|
||||
],
|
||||
() => x.get() > 10,
|
||||
() => {
|
||||
const _el$3 = _tmpl$(),
|
||||
_el$4 = _el$3.firstChild;
|
||||
$$insert(_el$3, x, _el$4);
|
||||
return _el$3;
|
||||
},
|
||||
() => 5 > x.get(),
|
||||
() => {
|
||||
const _el$5 = _tmpl$2(),
|
||||
_el$6 = _el$5.firstChild;
|
||||
$$insert(_el$5, x, _el$6);
|
||||
return _el$5;
|
||||
},
|
||||
true,
|
||||
() => {
|
||||
return $$runComponent(Cond, {
|
||||
get branches() {
|
||||
return [
|
||||
() => x.get() > 7,
|
||||
() => {
|
||||
const _el$8 = _tmpl$3(),
|
||||
_el$9 = _el$8.firstChild;
|
||||
$$insert(_el$8, x, _el$9);
|
||||
return _el$8;
|
||||
},
|
||||
true,
|
||||
() => {
|
||||
const _el$10 = _tmpl$4(),
|
||||
_el$11 = _el$10.firstChild;
|
||||
$$insert(_el$10, x, _el$11);
|
||||
return _el$10;
|
||||
},
|
||||
];
|
||||
},
|
||||
});
|
||||
},
|
||||
];
|
||||
},
|
||||
}),
|
||||
|
@ -339,12 +322,10 @@ describe('conditions', () => {
|
|||
$$runComponent(Cond, {
|
||||
get branches() {
|
||||
return [
|
||||
[
|
||||
() => showX.get(),
|
||||
() => {
|
||||
return _tmpl$();
|
||||
},
|
||||
],
|
||||
() => showX.get(),
|
||||
() => {
|
||||
return _tmpl$();
|
||||
},
|
||||
];
|
||||
},
|
||||
}),
|
||||
|
@ -355,12 +336,10 @@ describe('conditions', () => {
|
|||
$$runComponent(Cond, {
|
||||
get branches() {
|
||||
return [
|
||||
[
|
||||
() => showY.get(),
|
||||
() => {
|
||||
return _tmpl2$();
|
||||
},
|
||||
],
|
||||
() => showY.get(),
|
||||
() => {
|
||||
return _tmpl2$();
|
||||
},
|
||||
];
|
||||
},
|
||||
}),
|
||||
|
@ -371,12 +350,10 @@ describe('conditions', () => {
|
|||
$$runComponent(Cond, {
|
||||
get branches() {
|
||||
return [
|
||||
[
|
||||
() => showZ.get(),
|
||||
() => {
|
||||
return _tmpl3$();
|
||||
},
|
||||
],
|
||||
() => showZ.get(),
|
||||
() => {
|
||||
return _tmpl3$();
|
||||
},
|
||||
];
|
||||
},
|
||||
}),
|
||||
|
|
|
@ -17,9 +17,7 @@
|
|||
|
||||
import { describe, expect, vi } from 'vitest';
|
||||
import { domTest as it } from './utils';
|
||||
import { template as $$template } from '../src/dom';
|
||||
import { runComponent as $$runComponent, render} from '../src/core';
|
||||
import { delegateEvents as $$delegateEvents, addEventListener as $$on } from '../src/event';
|
||||
import { render } from '@inula/no-vdom';
|
||||
|
||||
function dispatchMouseEvent(element: HTMLElement, eventType = 'click') {
|
||||
element.dispatchEvent(new MouseEvent(eventType, { bubbles: true }));
|
||||
|
@ -35,70 +33,20 @@ function dispatchChangeEvent(input: HTMLElement, value: string) {
|
|||
|
||||
describe('event', () => {
|
||||
it('should trigger delegated and bound event', ({ container }) => {
|
||||
/**
|
||||
* 源码:
|
||||
* const fn = vi.fn();
|
||||
* const Comp = () => {
|
||||
* const handler = () => fn();
|
||||
* return <>
|
||||
* <input id="inline-fn-change" onChange={() =>fn("bound")}/>
|
||||
* <input id="var-change" onChange={handler}/>
|
||||
* <input id="hoisted-var-change" onChange={fn}/>
|
||||
* <button id="inline-fn-click" onClick={() =>fn("delegated")}>Click Delegated</button>
|
||||
* <button id="var-click" onClick={handler}>Click Delegated</button>
|
||||
* <button id="hoisted-var-click" onClick={fn}>Click Delegated</button>
|
||||
* </>;
|
||||
* };
|
||||
*
|
||||
* render(() => <CountingComponent />, container);
|
||||
*/
|
||||
|
||||
// 编译后:
|
||||
const $tmpl = /*#__PURE__*/ $$template(`<input id="inline-fn-change">`),
|
||||
$tmpl_2 = /*#__PURE__*/ $$template(`<input id="var-change">`),
|
||||
$tmpl_3 = /*#__PURE__*/ $$template(`<input id="hoisted-var-change">`),
|
||||
$tmpl_4 = /*#__PURE__*/ $$template(`<button id="inline-fn-click">Click Delegated`),
|
||||
$tmpl_5 = /*#__PURE__*/ $$template(`<button id="var-click">Click Delegated`),
|
||||
$tmpl_6 = /*#__PURE__*/ $$template(`<button id="hoisted-var-click">Click Delegated`);
|
||||
|
||||
const fn = vi.fn();
|
||||
const Comp = () => {
|
||||
const handler = () => fn();
|
||||
return [
|
||||
(() => {
|
||||
const _el$ = $tmpl();
|
||||
$$on(_el$, 'change', () => fn('bound'));
|
||||
return _el$;
|
||||
})(),
|
||||
(() => {
|
||||
const _el$2 = $tmpl_2();
|
||||
$$on(_el$2, 'change', handler);
|
||||
return _el$2;
|
||||
})(),
|
||||
(() => {
|
||||
const _el$3 = $tmpl_3();
|
||||
$$on(_el$3, 'change', fn);
|
||||
return _el$3;
|
||||
})(),
|
||||
(() => {
|
||||
const _el$4 = $tmpl_4();
|
||||
$$on(_el$4, 'click', () => fn('delegated'));
|
||||
return _el$4;
|
||||
})(),
|
||||
(() => {
|
||||
const _el$5 = $tmpl_5();
|
||||
$$on(_el$5, 'click', handler);
|
||||
return _el$5;
|
||||
})(),
|
||||
(() => {
|
||||
const _el$6 = $tmpl_6();
|
||||
$$on(_el$6, 'click', fn);
|
||||
return _el$6;
|
||||
})(),
|
||||
];
|
||||
return <>
|
||||
<input id="inline-fn-change" onChange={() =>fn("bound")}/>
|
||||
<input id="var-change" onChange={handler}/>
|
||||
<input id="hoisted-var-change" onChange={fn}/>
|
||||
<button id="inline-fn-click" onClick={() =>fn("delegated")}>Click Delegated</button>
|
||||
<button id="var-click" onClick={handler}>Click Delegated</button>
|
||||
<button id="hoisted-var-click" onClick={fn}>Click Delegated</button>
|
||||
</>;
|
||||
};
|
||||
render(() => $$runComponent(Comp), container);
|
||||
$$delegateEvents(['click']);
|
||||
|
||||
render(() => <Comp />, container);
|
||||
|
||||
dispatchChangeEvent(document.getElementById('inline-fn-change'), 'change');
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
|
|
|
@ -17,47 +17,23 @@
|
|||
|
||||
import { describe, expect } from 'vitest';
|
||||
import { domTest as it, nextTick } from './utils';
|
||||
import { template as $$template, insert as $$insert } from '../src/dom';
|
||||
import { runComponent as $$runComponent, render, onMount } from '../src/core';
|
||||
import { reactive } from 'inula-reactive';
|
||||
import { render, onMount } from '@inula/no-vdom';
|
||||
|
||||
describe('onMount', () => {
|
||||
it('should work.', async ({ container, log }) => {
|
||||
/**
|
||||
* 源码:
|
||||
* function App() {
|
||||
* onMount(() => {
|
||||
* log.add('App');
|
||||
* });
|
||||
* return <Child />;
|
||||
* }
|
||||
* function Child() {
|
||||
* onMount(() => {
|
||||
* log.add('Child');
|
||||
* });
|
||||
* return <GrandChild />;
|
||||
* }
|
||||
* function GrandChild() {
|
||||
* onMount(() => {
|
||||
* log.add('GrandChild');
|
||||
* });
|
||||
* }
|
||||
* render(() => <App />, container);
|
||||
*/
|
||||
|
||||
// 编译后:
|
||||
function App() {
|
||||
onMount(() => {
|
||||
log.add('App');
|
||||
});
|
||||
return $$runComponent(Child);
|
||||
return <Child />;
|
||||
}
|
||||
|
||||
function Child() {
|
||||
onMount(() => {
|
||||
log.add('Child');
|
||||
});
|
||||
return $$runComponent(GrandChild);
|
||||
return <GrandChild />;
|
||||
}
|
||||
|
||||
function GrandChild() {
|
||||
|
@ -66,27 +42,14 @@ describe('onMount', () => {
|
|||
});
|
||||
}
|
||||
|
||||
render(() => $$runComponent(App, {}), container);
|
||||
render(() => <App />, container);
|
||||
|
||||
await nextTick();
|
||||
expect(log.get()).toEqual(['App', 'Child', 'GrandChild']);
|
||||
});
|
||||
|
||||
it('should support multiple onMounts.', async ({ container, log }) => {
|
||||
/**
|
||||
* 源码:
|
||||
* function App() {
|
||||
* onMount(() => {
|
||||
* log.add('App1');
|
||||
* });
|
||||
* onMount(() => {
|
||||
* log.add('App2');
|
||||
* });
|
||||
* return <div />;
|
||||
* }
|
||||
*/
|
||||
// 编译后:
|
||||
const _tmpl$ = /*#__PURE__*/ $$template(`<div>`);
|
||||
|
||||
function App() {
|
||||
onMount(() => {
|
||||
log.add('App1');
|
||||
|
@ -94,9 +57,10 @@ describe('onMount', () => {
|
|||
onMount(() => {
|
||||
log.add('App2');
|
||||
});
|
||||
return _tmpl$();
|
||||
return <div />;
|
||||
}
|
||||
render(() => $$runComponent(App, {}), container);
|
||||
|
||||
render(() => <App />, container);
|
||||
|
||||
await nextTick();
|
||||
expect(log.get()).toEqual(['App1', 'App2']);
|
||||
|
@ -117,26 +81,12 @@ describe('onMount', () => {
|
|||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 源码:
|
||||
* function App() {
|
||||
* const data = useFetch();
|
||||
* return <div>{data.get()}</div>;
|
||||
* }
|
||||
*/
|
||||
// 编译后:
|
||||
const _tmpl$ = /*#__PURE__*/ $$template(`<div>`);
|
||||
|
||||
function App() {
|
||||
const data = useFetch();
|
||||
return (() => {
|
||||
const _el$ = _tmpl$();
|
||||
$$insert(_el$, () => data.get());
|
||||
return _el$;
|
||||
})();
|
||||
return <div>{data.get()}</div>;
|
||||
}
|
||||
|
||||
render(() => $$runComponent(App, {}), container);
|
||||
render(() => <App />, container);
|
||||
await nextTick();
|
||||
expect(container.innerHTML).toBe('<div>fake data</div>');
|
||||
});
|
||||
|
|
|
@ -15,9 +15,7 @@
|
|||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-nocheck For the compiled code.
|
||||
import { reactive } from 'inula-reactive';
|
||||
import {
|
||||
render
|
||||
} from '@inula/no-vdom';
|
||||
import { render } from '@inula/no-vdom';
|
||||
import { describe, expect } from 'vitest';
|
||||
import { domTest as it } from './utils';
|
||||
|
||||
|
@ -26,7 +24,7 @@ describe('render', () => {
|
|||
const CountingComponent = () => {
|
||||
return <div id="count">Count value is 0.</div>;
|
||||
};
|
||||
render(() => $$runComponent(CountingComponent, {}), container);
|
||||
render(() => <CountingComponent />, container);
|
||||
|
||||
expect(container.querySelector('#count').innerHTML).toEqual('Count value is 0.');
|
||||
});
|
||||
|
@ -35,7 +33,7 @@ describe('render', () => {
|
|||
const CountingComponent = () => {
|
||||
return <div id="count">Count value is {0}.</div>;
|
||||
};
|
||||
render(() => $$runComponent(CountingComponent, {}), container);
|
||||
render(() => <CountingComponent />, container);
|
||||
|
||||
expect(container.querySelector('#count').innerHTML).toEqual('Count value is 0.');
|
||||
});
|
||||
|
@ -50,12 +48,14 @@ describe('render', () => {
|
|||
<>
|
||||
<div id="count">Count value is {count.get()}.</div>
|
||||
<div>
|
||||
<button id="btn" onClick={add}>add</button>
|
||||
<button id="btn" onClick={add}>
|
||||
add
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
render(() => $$runComponent(CountingComponent, {}), container);
|
||||
render(() => <CountingComponent />, container);
|
||||
|
||||
container.querySelector('#btn').click();
|
||||
|
||||
|
@ -75,12 +75,14 @@ describe('render', () => {
|
|||
<div>
|
||||
<CountValue count={count} />
|
||||
<div>
|
||||
<button id="btn" onClick={add}>add</button>
|
||||
<button id="btn" onClick={add}>
|
||||
add
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
render(() => $$runComponent(CountingComponent, {}), container);
|
||||
render(() => <CountingComponent />, container);
|
||||
|
||||
container.querySelector('#btn').click();
|
||||
|
||||
|
@ -88,7 +90,7 @@ describe('render', () => {
|
|||
});
|
||||
|
||||
it('should render components with slot', ({ container }) => {
|
||||
const CountValue = (props) => {
|
||||
const CountValue = props => {
|
||||
return <div>Title: {props.children}</div>;
|
||||
};
|
||||
|
||||
|
@ -98,10 +100,16 @@ describe('render', () => {
|
|||
count.set(c => c + 1);
|
||||
};
|
||||
|
||||
return <div>
|
||||
<CountValue><h1>Your count is {count.get()}.</h1></CountValue>
|
||||
<div><button onClick={add}>add</button></div>
|
||||
</div>;
|
||||
return (
|
||||
<div>
|
||||
<CountValue>
|
||||
<h1>Your count is {count.get()}.</h1>
|
||||
</CountValue>
|
||||
<div>
|
||||
<button onClick={add}>add</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
render(() => <CountingComponent />, container);
|
||||
|
@ -118,7 +126,7 @@ describe('render', () => {
|
|||
});
|
||||
|
||||
it('should render sub components', ({ container }) => {
|
||||
const CountValue = (props) => {
|
||||
const CountValue = props => {
|
||||
return <div>Count value is {props.count} .</div>;
|
||||
};
|
||||
|
||||
|
@ -140,7 +148,7 @@ describe('render', () => {
|
|||
</div>
|
||||
);
|
||||
};
|
||||
render(() => $$runComponent(CountingComponent, {}), container);
|
||||
render(() => <CountingComponent />, container);
|
||||
|
||||
expect(container.querySelector('h1').innerHTML).toMatchInlineSnapshot('"0"');
|
||||
container.querySelector('button').click();
|
||||
|
@ -159,17 +167,16 @@ describe('render', () => {
|
|||
const CountingComponent = () => {
|
||||
return <div style="color: red;">Count value is 0.</div>;
|
||||
};
|
||||
render(() => $$runComponent(CountingComponent, {}), container);
|
||||
render(() => <CountingComponent />, container);
|
||||
});
|
||||
|
||||
|
||||
it('should render string of style with expression', ({ container }) => {
|
||||
const Comp = () => {
|
||||
const color = 'red';
|
||||
return <div style={`color: ${color};`}>Count value is 0.</div>;
|
||||
};
|
||||
|
||||
render(() => $$runComponent(Comp, {}), container);
|
||||
render(() => <Comp />, container);
|
||||
expect(container.querySelector('div').style.color).toEqual('red');
|
||||
});
|
||||
|
||||
|
@ -177,18 +184,17 @@ describe('render', () => {
|
|||
const Comp = () => {
|
||||
return <div style={{ color: 'red', display: 'flex' }}>Count value is 0.</div>;
|
||||
};
|
||||
render(() => $$runComponent(Comp, {}), container);
|
||||
render(() => <Comp />, container);
|
||||
expect(container.querySelector('div').style.color).toEqual('red');
|
||||
});
|
||||
|
||||
|
||||
it('should render object of style with expression', ({ container }) => {
|
||||
const Comp = () => {
|
||||
const color = 'red';
|
||||
return <div style={{ color }}>Count value is 0.</div>;
|
||||
};
|
||||
|
||||
render(() => $$runComponent(Comp, {}), container);
|
||||
render(() => <Comp />, container);
|
||||
expect(container.querySelector('div').style.color).toEqual('red');
|
||||
});
|
||||
|
||||
|
@ -198,7 +204,7 @@ describe('render', () => {
|
|||
return <div style={{ color: color.get() }}>Count value is 0.</div>;
|
||||
};
|
||||
|
||||
render(() => $$runComponent(Comp, {}), container);
|
||||
render(() => <Comp />, container);
|
||||
expect(container.querySelector('div').style.color).toEqual('red');
|
||||
container.querySelector('div').style.color = 'green';
|
||||
expect(container.querySelector('div').style.color).toEqual('green');
|
||||
|
@ -210,7 +216,7 @@ describe('render', () => {
|
|||
return <div style={style.get()}>Count value is 0.</div>;
|
||||
};
|
||||
|
||||
render(() => $$runComponent(Comp, {}), container);
|
||||
render(() => <Comp />, container);
|
||||
expect(container.querySelector('div').style.color).toEqual('red');
|
||||
container.querySelector('div').style.color = 'green';
|
||||
expect(container.querySelector('div').style.color).toEqual('green');
|
||||
|
@ -221,7 +227,7 @@ describe('render', () => {
|
|||
return <div className="red">Count value is 0.</div>;
|
||||
};
|
||||
|
||||
render(() => $$runComponent(Comp, {}), container);
|
||||
render(() => <Comp />, container);
|
||||
expect(container.querySelector('div').className).toEqual('red');
|
||||
});
|
||||
|
||||
|
@ -230,7 +236,7 @@ describe('render', () => {
|
|||
const color = 'red';
|
||||
return <div className={color}>Count value is 0.</div>;
|
||||
};
|
||||
render(() => $$runComponent(Comp, {}), container);
|
||||
render(() => <Comp />, container);
|
||||
expect(container.querySelector('div').className).toEqual('red');
|
||||
});
|
||||
|
||||
|
@ -238,7 +244,7 @@ describe('render', () => {
|
|||
const Comp = () => {
|
||||
return <div className={{ red: true }}>Count value is 0.</div>;
|
||||
};
|
||||
render(() => $$runComponent(Comp, {}), container);
|
||||
render(() => <Comp />, container);
|
||||
expect(container.querySelector('div').className).toEqual('red');
|
||||
});
|
||||
|
||||
|
@ -246,7 +252,7 @@ describe('render', () => {
|
|||
const Comp = () => {
|
||||
return <div className={['red', 'green']}>Count value is 0.</div>;
|
||||
};
|
||||
render(() => $$runComponent(Comp, {}), container);
|
||||
render(() => <Comp />, container);
|
||||
expect(container.querySelector('div').className).toEqual('red green');
|
||||
});
|
||||
|
||||
|
@ -255,7 +261,7 @@ describe('render', () => {
|
|||
const color = reactive('red');
|
||||
return <div className={color.get()}>Count value is 0.</div>;
|
||||
};
|
||||
render(() => $$runComponent(Comp, {}), container);
|
||||
render(() => <Comp />, container);
|
||||
expect(container.querySelector('div').className).toEqual('red');
|
||||
container.querySelector('div').className = 'green';
|
||||
expect(container.querySelector('div').className).toEqual('green');
|
||||
|
@ -267,39 +273,36 @@ describe('render', () => {
|
|||
return <div className={{ [color.get()]: true }}>Count value is 0.</div>;
|
||||
};
|
||||
|
||||
render(() => $$runComponent(Comp, {}), container);
|
||||
render(() => <Comp />, container);
|
||||
expect(container.querySelector('div').className).toEqual('red');
|
||||
});
|
||||
|
||||
it('should update class with array', ({ container }) => {
|
||||
const Comp = () => {
|
||||
const color = reactive('red');
|
||||
return <div className={[color.get(), 'green']}
|
||||
>Count value is 0.</div>;
|
||||
return <div className={[color.get(), 'green']}>Count value is 0.</div>;
|
||||
};
|
||||
render(() => $$runComponent(Comp, {}), container);
|
||||
render(() => <Comp />, container);
|
||||
expect(container.querySelector('div').className).toEqual('red green');
|
||||
});
|
||||
|
||||
it('should render attribute', ({ container }) => {
|
||||
function App() {
|
||||
return (
|
||||
<div id="test">parallel</div>
|
||||
);
|
||||
return <div id="test">parallel</div>;
|
||||
}
|
||||
render(() => $$runComponent(App, {}), container);
|
||||
|
||||
render(() => <App />, container);
|
||||
expect(container.querySelector('div').id).toEqual('test');
|
||||
});
|
||||
|
||||
it('should update attribute', ({ container }) => {
|
||||
const id = reactive('el');
|
||||
function App() {
|
||||
|
||||
return (
|
||||
<div id={id.get()}>parallel</div>
|
||||
);
|
||||
function App() {
|
||||
return <div id={id.get()}>parallel</div>;
|
||||
}
|
||||
render(() => $$runComponent(App, {}), container);
|
||||
|
||||
render(() => <App />, container);
|
||||
expect(container.querySelector('div').id).toEqual('el');
|
||||
id.set('test');
|
||||
expect(container.querySelector('div').id).toEqual('test');
|
||||
|
|
|
@ -18,7 +18,7 @@ import { Signal } from './Types';
|
|||
import { isFunction } from './Utils';
|
||||
import { schedule } from './SetScheduler';
|
||||
|
||||
let runningRNode: RNode<any> | undefined = undefined; // 当前正执行的RNode
|
||||
let runningRNode: RNode<any> | Root | undefined = undefined; // 当前正执行的RNode
|
||||
let calledGets: RNode<any>[] | null = null;
|
||||
let sameGetsIndex = 0; // 记录前后两次运行RNode时,调用get顺序没有变化的节点
|
||||
|
||||
|
@ -50,9 +50,9 @@ export class RNode<T = any> implements Signal<T> {
|
|||
_value: T;
|
||||
fn?: () => T;
|
||||
|
||||
private observers: RNode[] | null = null; // 被谁用
|
||||
private sources: RNode[] | null = null; // 使用谁
|
||||
|
||||
observers: RNode[] | null = null; // 被谁用
|
||||
sources: RNode[] | null = null; // 使用谁
|
||||
subNodes: RNode[] | null = null; // he RNode that are running within the current RNode.
|
||||
protected state: State;
|
||||
isEffect = false;
|
||||
|
||||
|
@ -93,7 +93,7 @@ export class RNode<T = any> implements Signal<T> {
|
|||
}
|
||||
|
||||
track() {
|
||||
if (runningRNode) {
|
||||
if (runningRNode && !isRoot(runningRNode)) {
|
||||
// 前后两次运行RNode,从左到右对比,如果调用get的RNode相同就calledGetsIndex加1
|
||||
if (!calledGets && runningRNode.sources && runningRNode.sources[sameGetsIndex] == this) {
|
||||
sameGetsIndex++;
|
||||
|
@ -170,6 +170,14 @@ export class RNode<T = any> implements Signal<T> {
|
|||
const prevGets = calledGets;
|
||||
const prevGetsIndex = sameGetsIndex;
|
||||
|
||||
if (runningRNode) {
|
||||
if (runningRNode.subNodes) {
|
||||
runningRNode.subNodes.push(this);
|
||||
} else {
|
||||
runningRNode.subNodes = [this];
|
||||
}
|
||||
}
|
||||
|
||||
runningRNode = this;
|
||||
calledGets = null as any;
|
||||
sameGetsIndex = 0;
|
||||
|
@ -263,7 +271,7 @@ export class RNode<T = any> implements Signal<T> {
|
|||
this.state = Fresh;
|
||||
}
|
||||
|
||||
private removeParentObservers(index: number): void {
|
||||
removeParentObservers(index: number): void {
|
||||
if (!this.sources) return;
|
||||
for (let i = index; i < this.sources.length; i++) {
|
||||
const source: RNode<any> = this.sources[i];
|
||||
|
@ -297,6 +305,50 @@ export function onCleanup<T = any>(fn: (oldValue: T) => void): void {
|
|||
}
|
||||
}
|
||||
|
||||
export function isRoot(node: RNode | Root): node is Root {
|
||||
return (node as Root).isRoot;
|
||||
}
|
||||
|
||||
export function createRoot(fn: () => void): () => void {
|
||||
const root: Root = {
|
||||
cleanups: [],
|
||||
subNodes: null,
|
||||
_value: null,
|
||||
isRoot: true,
|
||||
};
|
||||
const prevNode = runningRNode;
|
||||
runningRNode = root;
|
||||
try {
|
||||
fn();
|
||||
// TODO: handle error
|
||||
} finally {
|
||||
runningRNode = prevNode;
|
||||
}
|
||||
return () => cleanRNode(root);
|
||||
}
|
||||
|
||||
type Root = Cleanable & { isRoot: true };
|
||||
type Cleanable = {
|
||||
cleanups: ((oldValue: any) => void)[];
|
||||
subNodes: RNode[] | null;
|
||||
_value: any;
|
||||
}
|
||||
|
||||
export function cleanRNode(node: Cleanable) {
|
||||
if ((node as RNode).sources) {
|
||||
(node as RNode).removeParentObservers(0);
|
||||
}
|
||||
|
||||
// subNodes cleanup should be done first
|
||||
if (node.subNodes) {
|
||||
for (let i = 0; i < node.subNodes.length; i++) {
|
||||
cleanRNode(node.subNodes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
node.cleanups.forEach(cleanup => cleanup(node._value));
|
||||
}
|
||||
|
||||
/** run all non-clean effect nodes */
|
||||
export function runEffects(): void {
|
||||
for (let i = 0; i < Effects.length; i++) {
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
import { createComputed as computed, createReactive as reactive, createWatch as watch } from './RNodeCreator';
|
||||
import { isReactiveObj } from './Utils';
|
||||
import { RNode, untrack, runEffects } from './RNode';
|
||||
import { RNode, untrack, runEffects, onCleanup, createRoot } from './RNode';
|
||||
import { setScheduler } from './SetScheduler';
|
||||
|
||||
export interface Index {
|
||||
|
@ -38,5 +38,7 @@ export {
|
|||
untrack,
|
||||
unwrap,
|
||||
setScheduler,
|
||||
runEffects
|
||||
runEffects,
|
||||
createRoot,
|
||||
onCleanup
|
||||
};
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
import { computed, createRoot, onCleanup, watch } from '../src';
|
||||
|
||||
describe('onCleanup', () => {
|
||||
it('should work', () => {
|
||||
const cleanup = jest.fn();
|
||||
const unmount = createRoot(() => {
|
||||
onCleanup(cleanup);
|
||||
});
|
||||
unmount();
|
||||
expect(cleanup).toBeCalled();
|
||||
});
|
||||
|
||||
it('should work in the nested effect', () => {
|
||||
const cleanup = jest.fn();
|
||||
const unmount = createRoot(() => {
|
||||
onCleanup(() => cleanup(1));
|
||||
watch(() => {
|
||||
onCleanup(() => cleanup(2));
|
||||
computed(() => {
|
||||
onCleanup(() => cleanup(3));
|
||||
}).get();
|
||||
});
|
||||
});
|
||||
unmount();
|
||||
expect(cleanup).toHaveBeenNthCalledWith(1, 3);
|
||||
expect(cleanup).toHaveBeenNthCalledWith(2, 2);
|
||||
expect(cleanup).toHaveBeenNthCalledWith(3, 1);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue