parent
65a4d6273c
commit
07e2ce09b4
|
@ -0,0 +1,562 @@
|
||||||
|
# **Templating**
|
||||||
|
|
||||||
|
## JSX
|
||||||
|
|
||||||
|
### template
|
||||||
|
|
||||||
|
JSX整体编译为template模板,动态部分通过insert插入。
|
||||||
|
template字符串不进行省略(end tag)。<font color="#c00000">待讨论</font>
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
<div id="count">Count value is <!>.</div>
|
||||||
|
|
||||||
|
// solid
|
||||||
|
const $tmpl = /*#__PURE__*/ $$template(`<div id="count">Count value is <!>.`);
|
||||||
|
|
||||||
|
// inula
|
||||||
|
const $tmpl = /*#__PURE__*/ $$template(`<div id="count">Count value is <!>.</div>`);
|
||||||
|
```
|
||||||
|
|
||||||
|
命名规则
|
||||||
|
对于dom的变量,前缀用`$`,后缀用`_number`,用dom类型作为变量名。<font color="#c00000">待讨论</font>
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
const $tmpl = /*#__PURE__*/ $$template(`<div id="count">Count value is <!>.`);
|
||||||
|
const $div = $tmpl(), // 使用$div
|
||||||
|
$text = $div.firstChild, // 使用$text
|
||||||
|
```
|
||||||
|
|
||||||
|
**内部函数用`$$`作为前缀**
|
||||||
|
|
||||||
|
### insert
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
const $tmpl = /*#__PURE__*/ $$template(`<div id="count">Count value is <!>.`);
|
||||||
|
const $div = $tmpl(),
|
||||||
|
$text = $div.firstChild,
|
||||||
|
$text_1 = $text.nextSibling,
|
||||||
|
$$insert
|
||||||
|
($div, count, $text_1);
|
||||||
|
return $div;
|
||||||
|
```
|
||||||
|
## Class
|
||||||
|
### inline class
|
||||||
|
直接修改dom的`className`
|
||||||
|
```jsx
|
||||||
|
/**
|
||||||
|
* 源码:
|
||||||
|
* const Comp = () => {
|
||||||
|
* const color = 'red';
|
||||||
|
* return <div class={color}>Count value is 0.</div>;
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
// 编译后:
|
||||||
|
const $tmpl = /*#__PURE__*/ $$template('<div>Count value is 0.');
|
||||||
|
const Comp = () => {
|
||||||
|
const color = 'red';
|
||||||
|
return (() => {
|
||||||
|
const _el$ = $tmpl();
|
||||||
|
$$effect(() => (_el$.className = color));
|
||||||
|
return _el$;
|
||||||
|
})();
|
||||||
|
};
|
||||||
|
render(() => $$runComponent(Comp, {}), container);
|
||||||
|
````
|
||||||
|
|
||||||
|
### Object class
|
||||||
|
通过`$$className`设置class
|
||||||
|
```jsx
|
||||||
|
/**
|
||||||
|
* 源码:
|
||||||
|
* const Comp = () => {
|
||||||
|
* return <div class={{ red: true }}>Count value is 0.</div>;
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
// 编译后:
|
||||||
|
const $tmpl = /*#__PURE__*/ $$template('<div>Count value is 0.');
|
||||||
|
const Comp = () => {
|
||||||
|
return (() => {
|
||||||
|
const _el$ = $tmpl();
|
||||||
|
$$className(_el$, {
|
||||||
|
red: true,
|
||||||
|
});
|
||||||
|
return _el$;
|
||||||
|
})();
|
||||||
|
};
|
||||||
|
render(() => $$runComponent(Comp, {}), container);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Array class
|
||||||
|
通过`$$className`设置class
|
||||||
|
```jsx
|
||||||
|
/**
|
||||||
|
* 源码:
|
||||||
|
* const Comp = () => {
|
||||||
|
* return <div class={['red', 'green']}>Count value is 0.</div>;
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
// 编译后:
|
||||||
|
const $tmpl = /*#__PURE__*/ $$template('<div>Count value is 0.');
|
||||||
|
const Comp = () => {
|
||||||
|
return (() => {
|
||||||
|
const _el$ = $tmpl();
|
||||||
|
$$className(_el$, ['red', 'green']);
|
||||||
|
return _el$;
|
||||||
|
})();
|
||||||
|
};
|
||||||
|
render(() => $$runComponent(Comp, {}), container);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Attribute
|
||||||
|
使用`$$setAttribute`设置属性
|
||||||
|
```jsx
|
||||||
|
function App() {
|
||||||
|
return (
|
||||||
|
<h1 id="test">parallel</h1>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编译后:
|
||||||
|
const $tmpl = /*#__PURE__*/ $$template('<div id="test">Count value is 0.');
|
||||||
|
const Comp = () => {
|
||||||
|
return (() => {
|
||||||
|
const $div = $tmpl();
|
||||||
|
$$setAttribute($div, 'id', 'test');
|
||||||
|
return $div;
|
||||||
|
})();
|
||||||
|
};
|
||||||
|
```
|
||||||
|
使用响应式时,包裹在`$$effect`中
|
||||||
|
```jsx
|
||||||
|
//源码:
|
||||||
|
function App() {
|
||||||
|
const id = reactive('el');
|
||||||
|
return (
|
||||||
|
<h1 id={id.get()}>parallel</h1>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编译后:
|
||||||
|
const $tmpl = /*#__PURE__*/ $$template('<div id="test">Count value is 0.');
|
||||||
|
const id = reactive('el');
|
||||||
|
const Comp = () => {
|
||||||
|
return (() => {
|
||||||
|
const _el$ = $tmpl();
|
||||||
|
$$effect(() => $$setAttribute(_el$, 'id', id.get()));
|
||||||
|
return _el$;
|
||||||
|
})();
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fragment
|
||||||
|
Fragment编译为数组,并且每个元素都是一个IIFE,返回对应的dom。
|
||||||
|
```jsx
|
||||||
|
/**
|
||||||
|
* 源码:
|
||||||
|
* const CountingComponent = () => {
|
||||||
|
* const [count, setCount] = createSignal(0);
|
||||||
|
* const add = () => {
|
||||||
|
* setCount((c) => c + 1);
|
||||||
|
* }
|
||||||
|
* return <>
|
||||||
|
* <div id="count">Count value is {count()}.</div>
|
||||||
|
* <div><button onClick={add}>add</button></div>
|
||||||
|
* </>;
|
||||||
|
* };
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 编译后:
|
||||||
|
const $tmpl = /*#__PURE__*/ $$template('<div id="count">Count value is <!>.'),
|
||||||
|
$tmpl_2 = /*#__PURE__*/ $$template('<div><button id="btn">add');
|
||||||
|
const CountingComponent = () => {
|
||||||
|
const count = reactive(0);
|
||||||
|
const add = () => {
|
||||||
|
count.set(c => c + 1);
|
||||||
|
};
|
||||||
|
return [
|
||||||
|
(() => {
|
||||||
|
const _el$ = $tmpl(),
|
||||||
|
_el$2 = _el$.firstChild,
|
||||||
|
_el$4 = _el$2.nextSibling;
|
||||||
|
$$insert(_el$, count, _el$4);
|
||||||
|
return _el$;
|
||||||
|
})(),
|
||||||
|
(() => {
|
||||||
|
const _el$5 = $tmpl_2(),
|
||||||
|
_el$6 = _el$5.firstChild;
|
||||||
|
$$on(_el$6, 'click', add, true);
|
||||||
|
return _el$5;
|
||||||
|
})(),
|
||||||
|
];
|
||||||
|
};
|
||||||
|
```
|
||||||
|
## Event
|
||||||
|
|
||||||
|
### 事件代理
|
||||||
|
收集每个文件中使用过的事件,通过`$$delegateEvents`挂载
|
||||||
|
`$$delegateEvents`传入render中的dom,让代理事件挂载在dom上,而不是document。<font color="#c00000">待讨论</font>
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
$$render(() => $$createComponent(Main, {}), container);
|
||||||
|
$$delegateEvents(['click'], container);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 事件绑定
|
||||||
|
统一使用`$$on`,传入参数为:
|
||||||
|
1. dom
|
||||||
|
2. 事件类型
|
||||||
|
3. 事件处理函数
|
||||||
|
4. 是否冒泡,默认为false
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
<button type="button" onClick={handleDeleteClick}>x</button>
|
||||||
|
// to
|
||||||
|
$$on(_el$6, 'click', handleDeleteClick, true);
|
||||||
|
|
||||||
|
<input id="var-change" onChange={handler}/>
|
||||||
|
// to
|
||||||
|
$$on(_el$6, 'change', handleDeleteClick, true);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dom Ref
|
||||||
|
```jsx
|
||||||
|
function App() {
|
||||||
|
let myDiv;
|
||||||
|
return <div ref={myDiv}>My Element</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// to
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
let myDiv;
|
||||||
|
return (() => {
|
||||||
|
const $div = _tmpl$();
|
||||||
|
const $ref = myDiv;
|
||||||
|
typeof $ref === "function" ? $$bindRef($ref, _el$) : myDiv = $div;
|
||||||
|
return _el$;
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
## Styling
|
||||||
|
### Inline Style
|
||||||
|
```jsx
|
||||||
|
/**
|
||||||
|
* 源码:
|
||||||
|
* const CountingComponent = () => {
|
||||||
|
* return <div style="color: red;">Count value is 0.</div>;
|
||||||
|
* };
|
||||||
|
*
|
||||||
|
* render(() => <CountingComponent />, container);
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 编译后:
|
||||||
|
const $tmpl = /*#__PURE__*/ $$template('<div style="color: red;">Count value is 0.');
|
||||||
|
const Comp = () => {
|
||||||
|
return $tmpl();
|
||||||
|
};
|
||||||
|
render(() => $$runComponent(Comp, {}), container);
|
||||||
|
```
|
||||||
|
### Object Style
|
||||||
|
每个style属性通过`$$setProperty`设置。
|
||||||
|
动态style通过`$$effect`包裹。
|
||||||
|
```jsx
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 源码:
|
||||||
|
* const Comp = () => {
|
||||||
|
* const color = reactive('red');
|
||||||
|
* return <div style={{ color: color.get(), display: 'flex' }}>Count value is 0.</div>;
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 编译后:
|
||||||
|
const $tmpl = /*#__PURE__*/ $$template('<div>Count value is 0.');
|
||||||
|
const Comp = () => {
|
||||||
|
return (() => {
|
||||||
|
const _el$ = $tmpl();
|
||||||
|
$$effect(() =>
|
||||||
|
color.get() != null ? _el$.style.setProperty('color', color.get()) : _el$.style.removeProperty('color')
|
||||||
|
);
|
||||||
|
_el$.style.setProperty('display', 'flex');
|
||||||
|
return _el$;
|
||||||
|
})();
|
||||||
|
};
|
||||||
|
|
||||||
|
```
|
||||||
|
## Loop
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
<For each={state.todoList}>
|
||||||
|
{todo => <><Todo todo={todo}/><Todo todo={todo}/></>}
|
||||||
|
</For>
|
||||||
|
|
||||||
|
// to
|
||||||
|
$$runComponent(For, {
|
||||||
|
get each() {
|
||||||
|
return state.todoList;
|
||||||
|
},
|
||||||
|
children: todo => [
|
||||||
|
$$runComponent(Todo, {
|
||||||
|
todo: todo,
|
||||||
|
}),
|
||||||
|
$$runComponent(Todo, {
|
||||||
|
todo: todo,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Conditional
|
||||||
|
编译为`Cond`组件,`Cond`组件接收`branches`属性。
|
||||||
|
`branches`属性为一个数组,数组中的每个元素都是一个数组,数组的第一个元素是条件,第二个元素是返回的dom。
|
||||||
|
```ts
|
||||||
|
// It's boolean when the condition is static of default branch
|
||||||
|
// Otherwise it's a function that return boolean
|
||||||
|
type CondExpression = boolean | (() => boolean);
|
||||||
|
// When branch only include static JSXElement the branch can be a JSXElement
|
||||||
|
// Otherwise, the branch should be a function that return JSXElement
|
||||||
|
type Branch = JSXElement | (() => JSXElement);
|
||||||
|
|
||||||
|
export interface CondProps {
|
||||||
|
// Array of tuples, first item is the condition, second is the branch to render
|
||||||
|
branches: [CondExpression, Branch][];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
```jsx
|
||||||
|
/**
|
||||||
|
* 源码:
|
||||||
|
* const fn = vi.fn();
|
||||||
|
* function App() {
|
||||||
|
* const x = reactive(7);
|
||||||
|
*
|
||||||
|
* return (
|
||||||
|
* <div>
|
||||||
|
* <h1>if</h1>
|
||||||
|
* <if cond={x.get() > 10}>
|
||||||
|
* <p>{x.get()} is greater than 10</p>
|
||||||
|
* </if>
|
||||||
|
* <else-if cond={5 > x.get()}>
|
||||||
|
* <p>{x.get()} is less than 5</p>
|
||||||
|
* </else-if>
|
||||||
|
* <else>
|
||||||
|
* <p>{x.get()} is between 5 and 10</p>
|
||||||
|
* </else>
|
||||||
|
* </div>
|
||||||
|
* );
|
||||||
|
* }
|
||||||
|
* render(() => <App />, container);
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 编译后:
|
||||||
|
|
||||||
|
const _tmpl$ = /*#__PURE__*/ _$template(`<p> is greater than 10`),
|
||||||
|
_tmpl$2 = /*#__PURE__*/ _$template(`<p> is less than 5`),
|
||||||
|
_tmpl$3 = /*#__PURE__*/ _$template(`<div><h1>xxx`),
|
||||||
|
_tmpl$4 = /*#__PURE__*/ _$template(`<p> is between 5 and 10`);
|
||||||
|
let change;
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
const x = reactive(7);
|
||||||
|
change = v => x.set(v);
|
||||||
|
return (() => {
|
||||||
|
const _el$ = _tmpl$3(),
|
||||||
|
_el$2 = _el$.firstChild;
|
||||||
|
_$insert(
|
||||||
|
_el$,
|
||||||
|
_$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;
|
||||||
|
},
|
||||||
|
],
|
||||||
|
];
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
return _el$;
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
render(() => _$runComponent(App, {}), container);
|
||||||
|
```
|
||||||
|
## Early Return
|
||||||
|
|
||||||
|
# **Component composition**
|
||||||
|
|
||||||
|
## Props
|
||||||
|
|
||||||
|
使用`.get()`的表达式作为props时,转为对象时通过getter包装。
|
||||||
|
使用props时,通过props.(参数名)访问来保证在组件时读取props时能够保持响应。
|
||||||
|
|
||||||
|
> 好处: 1. 使用props时无需感知是否为响应式,无需额外判断 2. 使用统一,JSX和函数体JS都通过.get() 读值
|
||||||
|
> 问题: 1. 不能解构 -> 通过2.0 API编译语法糖解决
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
function App() {
|
||||||
|
const name = reactive("init")
|
||||||
|
return <Button name={signal()}/>
|
||||||
|
}
|
||||||
|
|
||||||
|
function Button(props) {
|
||||||
|
const greeting = createMemo(() => props.name + "!")
|
||||||
|
return <h1>{greeting()}</h1>
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编译后
|
||||||
|
function App() {
|
||||||
|
const name = reactive("init")
|
||||||
|
return $$runComponent(Button, {
|
||||||
|
get name() {
|
||||||
|
return name.get()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function Button(props) {
|
||||||
|
const greeting = computed(() => props.name + "!")
|
||||||
|
return <h1>{greeting.get()}</h1>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
预想2.0语法编译解决解构问题:
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
// 2.0 语法编译前
|
||||||
|
function App() {
|
||||||
|
let name = 'init'
|
||||||
|
return <Button name={name}>x</button>
|
||||||
|
}
|
||||||
|
|
||||||
|
function Button({name}) {
|
||||||
|
cosnt
|
||||||
|
greeting = name + '!'
|
||||||
|
return <h1>{greeting}</h1>
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2.0 语法编译后
|
||||||
|
function App() {
|
||||||
|
const name = reactive('init');
|
||||||
|
return <Button name={name.get()}>x</button>
|
||||||
|
}
|
||||||
|
|
||||||
|
function Button(props) {
|
||||||
|
cosnt
|
||||||
|
greeting = computed(() => props.name + '!')
|
||||||
|
return <h1>{greeting.get()}</h1>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
备选方案:
|
||||||
|
JSX中传递响应式时直接传递响应式变量,无需`.get()`。
|
||||||
|
使用时从props取对应的响应式值进行使用。
|
||||||
|
|
||||||
|
> 好处: 可以解构,组件显式处理props中的响应式
|
||||||
|
> 问题: 1. 使用props时需额外判断props是否为响应式,2.0API 无法向下编译
|
||||||
|
|
||||||
|
2. JSX使用响应式不要get,JS使用要get
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
function App() {
|
||||||
|
const name = reactive('init');
|
||||||
|
return <Button class={name}>x</button>
|
||||||
|
}
|
||||||
|
|
||||||
|
$$runComponent(Button, {
|
||||||
|
class: name
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function Button({name}) {
|
||||||
|
cosnt
|
||||||
|
greeting = computed(() => (isReactiveObj(props.name) ? props.name.get() : props.name) + '!')
|
||||||
|
return <h1>{reeting.get()}</h1>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Slot
|
||||||
|
通过children属性传递slot。IIFE包裹slot,返回对应的dom。
|
||||||
|
```jsx
|
||||||
|
/**
|
||||||
|
* 源码:
|
||||||
|
* const CountValue = (props) => {
|
||||||
|
* return <div>Title: {props.children}</div>;
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* const CountingComponent = () => {
|
||||||
|
* const [count, setCount] = createSignal(0);
|
||||||
|
*
|
||||||
|
* return <div>
|
||||||
|
* <CountValue><h1>FOO</h1></CountValue>
|
||||||
|
* </div>;
|
||||||
|
* };
|
||||||
|
*
|
||||||
|
* render(() => <CountingComponent />, document.getElementById("app"));
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 编译后:
|
||||||
|
const _tmpl$ = /*#__PURE__*/ $$template(`<div>Title: `),
|
||||||
|
_tmpl$2 = /*#__PURE__*/ $$template(`<h1>Your count is <!>.`),
|
||||||
|
_tmpl$3 = /*#__PURE__*/ $$template(`<div>`);
|
||||||
|
const CountValue = props => {
|
||||||
|
return (() => {
|
||||||
|
const _el$ = _tmpl$(),
|
||||||
|
_el$2 = _el$.firstChild;
|
||||||
|
$$insert(_el$, () => props.children, null);
|
||||||
|
return _el$;
|
||||||
|
})();
|
||||||
|
};
|
||||||
|
const CountingComponent = () => {
|
||||||
|
const count = reactive(0);
|
||||||
|
const add = () => {
|
||||||
|
count.set(c => c + 1);
|
||||||
|
};
|
||||||
|
return (() => {
|
||||||
|
const _el$3 = _tmpl$3(),
|
||||||
|
_el$8 = _el$3.firstChild,
|
||||||
|
_el$9 = _el$8.firstChild;
|
||||||
|
$$insert(
|
||||||
|
_el$3,
|
||||||
|
$$runComponent(CountValue, {
|
||||||
|
get children() {
|
||||||
|
const _el$4 = _tmpl$2(),
|
||||||
|
_el$5 = _el$4.firstChild,
|
||||||
|
_el$7 = _el$5.nextSibling,
|
||||||
|
_el$6 = _el$7.nextSibling;
|
||||||
|
$$insert(_el$4, count, _el$7);
|
||||||
|
return _el$4;
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
_el$8,
|
||||||
|
);
|
||||||
|
return _el$3;
|
||||||
|
})();
|
||||||
|
};
|
||||||
|
```
|
||||||
|
## Context
|
|
@ -14,9 +14,12 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { insert } from './dom';
|
import { insert } from './dom';
|
||||||
import { untrack } from 'inula-reactive';
|
import { RNode, untrack, setScheduler } from 'inula-reactive';
|
||||||
import { readContext } from './components/Env';
|
import { readContext } from './components/Env';
|
||||||
|
|
||||||
|
// enable the scheduler
|
||||||
|
setScheduler();
|
||||||
|
|
||||||
type ComponentConstructor<T> = (props: T, context: any) => any;
|
type ComponentConstructor<T> = (props: T, context: any) => any;
|
||||||
type CodeFunction = () => any;
|
type CodeFunction = () => any;
|
||||||
|
|
||||||
|
@ -58,3 +61,11 @@ export function render(codeFn: CodeFunction, element: HTMLElement): () => void {
|
||||||
element.textContent = '';
|
element.textContent = '';
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* onMount is a lifecycle hook that runs after the component is mounted
|
||||||
|
* @param fn mount function
|
||||||
|
*/
|
||||||
|
export function onMount(fn: () => void): void {
|
||||||
|
new RNode(() => untrack(fn), { isEffect: true, lazy: true });
|
||||||
|
}
|
||||||
|
|
|
@ -27,7 +27,6 @@ describe('env', () => {
|
||||||
it('should work.', ({container}) => {
|
it('should work.', ({container}) => {
|
||||||
/**
|
/**
|
||||||
* 源码:
|
* 源码:
|
||||||
* const fn = vi.fn();
|
|
||||||
* function App() {
|
* function App() {
|
||||||
* const x = reactive(7);
|
* const x = reactive(7);
|
||||||
* const theme = reactive('dark');
|
* const theme = reactive('dark');
|
||||||
|
@ -77,7 +76,6 @@ describe('env', () => {
|
||||||
it('should work with nested env.', ({container}) => {
|
it('should work with nested env.', ({container}) => {
|
||||||
/**
|
/**
|
||||||
* 源码:
|
* 源码:
|
||||||
* const fn = vi.fn();
|
|
||||||
* function App() {
|
* function App() {
|
||||||
* const theme = reactive('dark');
|
* const theme = reactive('dark');
|
||||||
* return (
|
* return (
|
||||||
|
@ -145,7 +143,6 @@ describe('env', () => {
|
||||||
it('should merged the parent env.', ({container}) => {
|
it('should merged the parent env.', ({container}) => {
|
||||||
/**
|
/**
|
||||||
* 源码:
|
* 源码:
|
||||||
* const fn = vi.fn();
|
|
||||||
* function App() {
|
* function App() {
|
||||||
* const theme = reactive('dark');
|
* const theme = reactive('dark');
|
||||||
* return (
|
* return (
|
||||||
|
@ -272,7 +269,6 @@ describe('env', () => {
|
||||||
it('should work with recursive env, like menu.', ({container}) => {
|
it('should work with recursive env, like menu.', ({container}) => {
|
||||||
/**
|
/**
|
||||||
* 源码:
|
* 源码:
|
||||||
* const fn = vi.fn();
|
|
||||||
* function App() {
|
* function App() {
|
||||||
* return (
|
* return (
|
||||||
* <Menu key="root">
|
* <Menu key="root">
|
||||||
|
|
|
@ -0,0 +1,143 @@
|
||||||
|
/*
|
||||||
|
* 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, 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';
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Child() {
|
||||||
|
onMount(() => {
|
||||||
|
log.add('Child');
|
||||||
|
});
|
||||||
|
return $$runComponent(GrandChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
function GrandChild() {
|
||||||
|
onMount(() => {
|
||||||
|
log.add('GrandChild');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render(() => $$runComponent(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');
|
||||||
|
});
|
||||||
|
onMount(() => {
|
||||||
|
log.add('App2');
|
||||||
|
});
|
||||||
|
return _tmpl$();
|
||||||
|
}
|
||||||
|
render(() => $$runComponent(App, {}), container);
|
||||||
|
|
||||||
|
await nextTick();
|
||||||
|
expect(log.get()).toEqual(['App1', 'App2']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work in hooks.', async ({ container, log }) => {
|
||||||
|
const fakeFetch = () =>
|
||||||
|
new Promise(resolve => {
|
||||||
|
resolve('fake data');
|
||||||
|
});
|
||||||
|
|
||||||
|
function useFetch() {
|
||||||
|
const data = reactive(null);
|
||||||
|
onMount(async () => {
|
||||||
|
const resp = await fakeFetch();
|
||||||
|
data.set(resp);
|
||||||
|
});
|
||||||
|
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$;
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
render(() => $$runComponent(App, {}), container);
|
||||||
|
await nextTick();
|
||||||
|
expect(container.innerHTML).toBe('<div>fake data</div>');
|
||||||
|
});
|
||||||
|
});
|
|
@ -15,10 +15,10 @@
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-nocheck For the compiled code.
|
// @ts-nocheck For the compiled code.
|
||||||
|
|
||||||
import { describe, expect } from 'vitest';
|
import { describe, expect, vi } from 'vitest';
|
||||||
import { domTest as it } from './utils';
|
import { domTest as it, nextTick } from './utils';
|
||||||
import { template as $$template, bindRef as $$bindRef } from '../src/dom';
|
import { template as $$template, bindRef as $$bindRef } from '../src/dom';
|
||||||
import { runComponent as $$runComponent, render } from '../src/core';
|
import { runComponent as $$runComponent, render, onMount } from '../src/core';
|
||||||
|
|
||||||
describe('ref', () => {
|
describe('ref', () => {
|
||||||
it('should reference to dom.', ({container}) => {
|
it('should reference to dom.', ({container}) => {
|
||||||
|
@ -88,4 +88,37 @@ describe('ref', () => {
|
||||||
render(() => $$runComponent(App, {}), container);
|
render(() => $$runComponent(App, {}), container);
|
||||||
expect(canvas).toBe(container.firstChild);
|
expect(canvas).toBe(container.firstChild);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should be accessible in onMount.', async ({container}) => {
|
||||||
|
/**
|
||||||
|
* 源码:
|
||||||
|
* function App() {
|
||||||
|
* let ref;
|
||||||
|
* onMount(() => {
|
||||||
|
* console.log(ref);
|
||||||
|
* });
|
||||||
|
* return <canvas ref={ref} width="256" height="256" />;
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
const $tmpl$ = $$template('<canvas width="256" height="256"></canvas>');
|
||||||
|
const fn = vi.fn();
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
let ref: Node;
|
||||||
|
onMount(() => {
|
||||||
|
fn(ref);
|
||||||
|
});
|
||||||
|
return (() => {
|
||||||
|
const $div = $tmpl$();
|
||||||
|
const $ref = ref;
|
||||||
|
typeof $ref === 'function' ? $$bindRef($ref, $div) : (ref = $div);
|
||||||
|
return $div;
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
render(() => $$runComponent(App, {}), container);
|
||||||
|
|
||||||
|
await nextTick();
|
||||||
|
expect(fn).toBeCalledWith(container.firstChild);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -16,7 +16,26 @@ import { test } from 'vitest';
|
||||||
|
|
||||||
interface DomTestContext {
|
interface DomTestContext {
|
||||||
container: HTMLDivElement;
|
container: HTMLDivElement;
|
||||||
|
log: Log;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Log {
|
||||||
|
private messages: string[] = [];
|
||||||
|
|
||||||
|
add(message: string) {
|
||||||
|
this.messages.push(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
get() {
|
||||||
|
return this.messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
empty() {
|
||||||
|
this.messages = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const log = new Log();
|
||||||
// Define a new test type that extends the default test type and adds the container fixture.
|
// Define a new test type that extends the default test type and adds the container fixture.
|
||||||
export const domTest = test.extend<DomTestContext>({
|
export const domTest = test.extend<DomTestContext>({
|
||||||
container: async ({ task }, use) => {
|
container: async ({ task }, use) => {
|
||||||
|
@ -25,4 +44,14 @@ export const domTest = test.extend<DomTestContext>({
|
||||||
await use(container);
|
await use(container);
|
||||||
container.remove();
|
container.remove();
|
||||||
},
|
},
|
||||||
|
log: async ({ task }, use) => {
|
||||||
|
await use(log);
|
||||||
|
log.empty();
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export function nextTick(): Promise<void> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(resolve, 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
|
|
||||||
import { isPrimitive } from './Utils';
|
import { isPrimitive } from './Utils';
|
||||||
import { RNode } from './RNode';
|
import { RNode } from './RNode';
|
||||||
import { Fn, NoArgFn, NonFunctionType, Signal } from './Types';
|
import { NoArgFn, NonFunctionType, Signal } from './Types';
|
||||||
import { DeepReactive, RProxyNode } from './RProxyNode';
|
import { DeepReactive, RProxyNode } from './RProxyNode';
|
||||||
import { getRNodeVal } from './RNodeAccessor';
|
import { getRNodeVal } from './RNodeAccessor';
|
||||||
|
|
||||||
|
@ -44,12 +44,10 @@ export function createComputed<T extends NoArgFn>(fn: T, deep = true) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createWatch<T>(fn: T) {
|
export function createWatch<T>(fn: T) {
|
||||||
const rNode = new RNode(fn, {
|
return new RNode(fn, {
|
||||||
isEffect: true,
|
isEffect: true,
|
||||||
lazy: false,
|
lazy: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
return rNode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getOrCreateChildProxy(value: unknown, parent: RProxyNode<any>, key: string | symbol) {
|
export function getOrCreateChildProxy(value: unknown, parent: RProxyNode<any>, key: string | symbol) {
|
||||||
|
|
Loading…
Reference in New Issue