Match-id-4cc3574b83fb955dd2c7b7f3a5eb4beab4f22c94
This commit is contained in:
parent
ead091a670
commit
dfffef41e2
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "inulajs",
|
||||
"name": "inulajs-reactive",
|
||||
"description": "Inulajs is a JavaScript framework library.",
|
||||
"keywords": [
|
||||
"inulajs"
|
||||
|
|
|
@ -0,0 +1,162 @@
|
|||
/*
|
||||
* Copyright (c) 2023 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.
|
||||
*/
|
||||
|
||||
import Inula, { render, createRef, useReactive, For } from '../../../../../src/index';
|
||||
import { beforeEach } from '@jest/globals';
|
||||
|
||||
const Row = ({ item }) => {
|
||||
return <li id={item.id} key={item.id}>{item.name}</li>;
|
||||
};
|
||||
|
||||
let rObj;
|
||||
let ref;
|
||||
let appFn;
|
||||
let App;
|
||||
let itemFn;
|
||||
|
||||
describe('测试 For 组件的新增', () => {
|
||||
beforeEach(() => {
|
||||
ref = createRef();
|
||||
appFn = jest.fn();
|
||||
itemFn = jest.fn();
|
||||
|
||||
App = () => {
|
||||
const _rObj = useReactive({
|
||||
items: [
|
||||
{ id: 'id-1', name: 'p1' },
|
||||
{ id: 'id-2', name: 'p2' },
|
||||
],
|
||||
});
|
||||
rObj = _rObj;
|
||||
|
||||
appFn();
|
||||
|
||||
return (
|
||||
<div ref={ref}>
|
||||
<For each={_rObj.items}>
|
||||
{item => {
|
||||
itemFn();
|
||||
return <Row item={item} />;
|
||||
}}
|
||||
</For>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
it('通过 push 在后面添加1行', () => {
|
||||
render(<App />, container);
|
||||
let items = container.querySelectorAll('li');
|
||||
expect(items.length).toEqual(2);
|
||||
|
||||
// 在后面添加一行
|
||||
rObj.items.push({ id: 'id-3', name: 'p3' });
|
||||
|
||||
items = container.querySelectorAll('li');
|
||||
expect(items.length).toEqual(3);
|
||||
expect(appFn).toHaveBeenCalledTimes(1);
|
||||
|
||||
// 第一次渲染执行2次,push更新执行1次
|
||||
expect(itemFn).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
it('通过 unshift 在前面添加2行', () => {
|
||||
render(<App />, container);
|
||||
let items = container.querySelectorAll('li');
|
||||
expect(items.length).toEqual(2);
|
||||
|
||||
// 在前面添加2行
|
||||
rObj.items.unshift({ id: 'id-3', name: 'p3' }, { id: 'id-4', name: 'p4' });
|
||||
|
||||
items = container.querySelectorAll('li');
|
||||
expect(items.length).toEqual(4);
|
||||
expect(appFn).toHaveBeenCalledTimes(1);
|
||||
|
||||
// 第一次渲染执行2次,unshift更新执行2次
|
||||
expect(itemFn).toHaveBeenCalledTimes(4);
|
||||
});
|
||||
|
||||
it('通过 set 在后面添加1行', () => {
|
||||
render(<App />, container);
|
||||
let items = container.querySelectorAll('li');
|
||||
expect(items.length).toEqual(2);
|
||||
|
||||
// 在后面添加一行
|
||||
rObj.items.set([
|
||||
{ id: 'id-1', name: 'p1' },
|
||||
{ id: 'id-2', name: 'p2' },
|
||||
{ id: 'id-3', name: 'p3' },
|
||||
]);
|
||||
|
||||
items = container.querySelectorAll('li');
|
||||
expect(items.length).toEqual(3);
|
||||
expect(appFn).toHaveBeenCalledTimes(1);
|
||||
|
||||
// 第一次渲染执行2次,push更新执行1次
|
||||
expect(itemFn).toHaveBeenCalledTimes(3);
|
||||
|
||||
let li = container.querySelector('#id-3');
|
||||
expect(li.innerHTML).toEqual('p3');
|
||||
});
|
||||
|
||||
it('For标签使用,使用push创建3000行表格数据', () => {
|
||||
let reactiveObj;
|
||||
const App = () => {
|
||||
const sourceData = useReactive([]);
|
||||
reactiveObj = sourceData;
|
||||
|
||||
return (
|
||||
<div style={{ width: '100%', height: '100%', overflowY: 'auto' }}>
|
||||
<table border='1' width='100%'>
|
||||
<tr>
|
||||
<th>序号</th>
|
||||
<th>名称</th>
|
||||
<th>年龄</th>
|
||||
<th>性别</th>
|
||||
<th>名族</th>
|
||||
<th>其他</th>
|
||||
</tr>
|
||||
<For each={sourceData}>
|
||||
{
|
||||
eachItem => {
|
||||
return (
|
||||
<tr>
|
||||
<th style={{ color: eachItem.color }}>{eachItem.value}</th>
|
||||
<th style={{ color: eachItem.color }}>{eachItem.value}</th>
|
||||
<th style={{ color: eachItem.color }}>{eachItem.value}</th>
|
||||
<th style={{ color: eachItem.color }}>{eachItem.value}</th>
|
||||
<th style={{ color: eachItem.color }}>{eachItem.value}</th>
|
||||
<th style={{ color: eachItem.color }}>{eachItem.value}</th>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
}
|
||||
</For>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
render(<App />, container);
|
||||
|
||||
// 不推荐:循环push
|
||||
for (let i = 0; i < 2; i++) {
|
||||
reactiveObj.push({ value: i, color: null });
|
||||
}
|
||||
expect(reactiveObj.get().length).toEqual(2);
|
||||
|
||||
let items = container.querySelectorAll('tr');
|
||||
expect(items.length).toEqual(3);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* Copyright (c) 2023 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.
|
||||
*/
|
||||
|
||||
import Inula, { render, createRef, useReactive, For } from '../../../../../src/index';
|
||||
import { beforeEach } from '@jest/globals';
|
||||
|
||||
const Row = ({ item }) => {
|
||||
return <li id={item.id} key={item.id}>{item.name}</li>;
|
||||
};
|
||||
|
||||
let rObj;
|
||||
let ref;
|
||||
let appFn;
|
||||
let App;
|
||||
let itemFn;
|
||||
|
||||
describe('测试 For 组件的删除', () => {
|
||||
beforeEach(() => {
|
||||
ref = createRef();
|
||||
appFn = jest.fn();
|
||||
itemFn = jest.fn();
|
||||
|
||||
App = () => {
|
||||
const _rObj = useReactive({
|
||||
items: [
|
||||
{ id: 'id-1', name: 'p1' },
|
||||
{ id: 'id-2', name: 'p2' },
|
||||
{ id: 'id-3', name: 'p3' },
|
||||
{ id: 'id-4', name: 'p4' },
|
||||
{ id: 'id-5', name: 'p5' },
|
||||
],
|
||||
});
|
||||
rObj = _rObj;
|
||||
|
||||
appFn();
|
||||
|
||||
return (
|
||||
<div ref={ref}>
|
||||
<For each={_rObj.items}>
|
||||
{item => {
|
||||
itemFn();
|
||||
return <Row item={item} />;
|
||||
}}
|
||||
</For>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
it('通过 pop 删除最后1行', () => {
|
||||
render(<App />, container);
|
||||
let items = container.querySelectorAll('li');
|
||||
expect(items.length).toEqual(5);
|
||||
|
||||
// 删除最后一行
|
||||
rObj.items.pop();
|
||||
|
||||
items = container.querySelectorAll('li');
|
||||
expect(items.length).toEqual(4);
|
||||
expect(appFn).toHaveBeenCalledTimes(1);
|
||||
|
||||
// 第一次渲染执行5次,pop无需更新
|
||||
expect(itemFn).toHaveBeenCalledTimes(5);
|
||||
});
|
||||
|
||||
it('通过 splice 删除中间2行', () => {
|
||||
render(<App />, container);
|
||||
let items = container.querySelectorAll('li');
|
||||
expect(items.length).toEqual(5);
|
||||
|
||||
// 删除中间一行
|
||||
rObj.items.splice(2, 2);
|
||||
|
||||
items = container.querySelectorAll('li');
|
||||
expect(items.length).toEqual(3);
|
||||
expect(appFn).toHaveBeenCalledTimes(1);
|
||||
|
||||
// 第一次渲染执行5次,splice无需更新
|
||||
expect(itemFn).toHaveBeenCalledTimes(5);
|
||||
});
|
||||
|
||||
it('通过 splice 删除中间2行,增加1行', () => {
|
||||
render(<App />, container);
|
||||
let items = container.querySelectorAll('li');
|
||||
expect(items.length).toEqual(5);
|
||||
|
||||
// 删除中间2行,增加1行
|
||||
rObj.items.splice(2, 2, ...[{ id: 6, name: 'p6' }]);
|
||||
|
||||
items = container.querySelectorAll('li');
|
||||
expect(items.length).toEqual(4);
|
||||
expect(appFn).toHaveBeenCalledTimes(1);
|
||||
|
||||
// 第一次渲染执行5次,splice新增1行会执行1次
|
||||
expect(itemFn).toHaveBeenCalledTimes(6);
|
||||
});
|
||||
|
||||
it('通过 set 删除中间2行', () => {
|
||||
render(<App />, container);
|
||||
let items = container.querySelectorAll('li');
|
||||
expect(items.length).toEqual(5);
|
||||
|
||||
// 删除中间2行
|
||||
rObj.items.set([
|
||||
{ id: 'id-1', name: 'p1' },
|
||||
{ id: 'id-2', name: 'p2' },
|
||||
{ id: 'id-5', name: 'p5' },
|
||||
]);
|
||||
|
||||
items = container.querySelectorAll('li');
|
||||
expect(items.length).toEqual(3);
|
||||
expect(appFn).toHaveBeenCalledTimes(1);
|
||||
|
||||
// 第一次渲染执行5次,splice无需更新
|
||||
expect(itemFn).toHaveBeenCalledTimes(5);
|
||||
});
|
||||
});
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,251 @@
|
|||
/*
|
||||
* Copyright (c) 2023 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.
|
||||
*/
|
||||
|
||||
import Inula, { render, createRef, useReactive, reactive, memo, For } from '../../../../../src/index';
|
||||
|
||||
const Item = ({ item }) => {
|
||||
return <li key={item.id}>{item.name}</li>;
|
||||
};
|
||||
|
||||
describe('测试 For 组件', () => {
|
||||
it('使用For组件遍历reactive“数组”', () => {
|
||||
let rObj;
|
||||
const ref = createRef();
|
||||
const fn = jest.fn();
|
||||
const Item = ({ item }) => {
|
||||
return <li key={item.id}>{item.name}</li>;
|
||||
};
|
||||
|
||||
const App = () => {
|
||||
const _rObj = useReactive({
|
||||
items: [
|
||||
{ name: 'p1', id: 1 },
|
||||
{ name: 'p2', id: 2 },
|
||||
],
|
||||
});
|
||||
rObj = _rObj;
|
||||
|
||||
fn();
|
||||
|
||||
return (
|
||||
<div ref={ref}>
|
||||
<For each={_rObj.items}>
|
||||
{item => {
|
||||
return <Item item={item} />;
|
||||
}}
|
||||
</For>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
render(<App />, container);
|
||||
let items = container.querySelectorAll('li');
|
||||
expect(items.length).toEqual(2);
|
||||
|
||||
// 每次修改items都会触发整个组件刷新
|
||||
rObj.items.set([{ name: 'p11', id: 1 }]);
|
||||
|
||||
items = container.querySelectorAll('li');
|
||||
expect(items.length).toEqual(1);
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
|
||||
// 每次修改items都会触发整个组件刷新
|
||||
rObj.items.push({ name: 'p22', id: 2 });
|
||||
|
||||
items = container.querySelectorAll('li');
|
||||
expect(items.length).toEqual(2);
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('reactive“数组”从[]变成有值', () => {
|
||||
let rObj;
|
||||
const ref = createRef();
|
||||
const fn = jest.fn();
|
||||
const Item = ({ item }) => {
|
||||
return <li key={item.id}>{item.name}</li>;
|
||||
};
|
||||
|
||||
const App = () => {
|
||||
const _rObj = useReactive({
|
||||
items: [],
|
||||
});
|
||||
rObj = _rObj;
|
||||
|
||||
fn();
|
||||
|
||||
return (
|
||||
<div ref={ref}>
|
||||
<For each={_rObj.items}>
|
||||
{item => {
|
||||
return <Item item={item} />;
|
||||
}}
|
||||
</For>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
render(<App />, container);
|
||||
let items = container.querySelectorAll('li');
|
||||
expect(items.length).toEqual(0);
|
||||
|
||||
// 每次修改items都会触发整个组件刷新
|
||||
rObj.items.set([{ name: 'p11', id: 1 }]);
|
||||
|
||||
items = container.querySelectorAll('li');
|
||||
expect(items.length).toEqual(1);
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
|
||||
// 每次修改items都会触发整个组件刷新
|
||||
rObj.items.push({ name: 'p22', id: 2 });
|
||||
|
||||
items = container.querySelectorAll('li');
|
||||
expect(items.length).toEqual(2);
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('数组3行变到4行', () => {
|
||||
const state = reactive({
|
||||
data: {
|
||||
lines: [
|
||||
{ id: 'id-1', label: '1' },
|
||||
{ id: 'id-2', label: '2' },
|
||||
{ id: 'id-3', label: '3' },
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const Row = memo(({ item }) => {
|
||||
return (
|
||||
<tr>
|
||||
<td>{item.id}</td>
|
||||
<td>
|
||||
<a id={item.id}>{item.label}</a>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
});
|
||||
|
||||
const RowList = () => {
|
||||
return <For each={state.data.lines}>{item => <Row item={item} />}</For>;
|
||||
};
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<div>
|
||||
<table>
|
||||
<tbody>
|
||||
<RowList />
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
render(<App />, container);
|
||||
|
||||
let a = container.querySelector('#id-1');
|
||||
|
||||
expect(a.innerHTML).toEqual('1');
|
||||
expect(state.data.lines.length).toEqual(3);
|
||||
state.data.set({
|
||||
lines: [
|
||||
{ id: 'id-4', label: '4' },
|
||||
{ id: 'id-5', label: '5' },
|
||||
{ id: 'id-6', label: '6' },
|
||||
{ id: 'id-7', label: '7' },
|
||||
],
|
||||
});
|
||||
expect(state.data.lines.length).toEqual(4);
|
||||
a = container.querySelector('#id-4');
|
||||
|
||||
expect(a.innerHTML).toEqual('4');
|
||||
const b = container.querySelector('#id-6');
|
||||
expect(b.innerHTML).toEqual('6');
|
||||
});
|
||||
|
||||
it('使用基本数据数组的loop方法', () => {
|
||||
let rObj;
|
||||
const fn = jest.fn();
|
||||
|
||||
const App = () => {
|
||||
const _rObj = useReactive({
|
||||
items: [1, 2, 3, 4],
|
||||
});
|
||||
rObj = _rObj;
|
||||
|
||||
fn();
|
||||
|
||||
return (
|
||||
<div>
|
||||
{_rObj.items.map(rItem => {
|
||||
return <li>{rItem}</li>;
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
render(<App />, container);
|
||||
let items = container.querySelectorAll('li');
|
||||
expect(items.length).toEqual(4);
|
||||
|
||||
// 每次修改items都会触发整个组件刷新
|
||||
rObj.items.set([1, 2, 3]);
|
||||
|
||||
items = container.querySelectorAll('li');
|
||||
expect(items.length).toEqual(3);
|
||||
expect(fn).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('数组reverse', () => {
|
||||
it('调用数组的reverse方法', () => {
|
||||
let rObj;
|
||||
const fn = jest.fn();
|
||||
|
||||
const App = () => {
|
||||
const _rObj = useReactive({
|
||||
items: [
|
||||
{ id: 1, name: 'p1' },
|
||||
{ id: 2, name: 'p2' },
|
||||
{ id: 3, name: 'p3' },
|
||||
],
|
||||
});
|
||||
rObj = _rObj;
|
||||
|
||||
fn();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<For each={_rObj.items}>
|
||||
{item => {
|
||||
return <Item item={item} />;
|
||||
}}
|
||||
</For>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
render(<App />, container);
|
||||
let items = container.querySelectorAll('li');
|
||||
expect(items.length).toEqual(3);
|
||||
|
||||
// 反转
|
||||
rObj.items.reverse();
|
||||
|
||||
items = container.querySelectorAll('li');
|
||||
expect(items.length).toEqual(3);
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* Copyright (c) 2023 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.
|
||||
*/
|
||||
|
||||
import Inula, { render, createRef, act, useReactive } from '../../../../src/index';
|
||||
import { Block } from '../../../../src/reactive/components/Block';
|
||||
|
||||
describe('测试 Block 组件', () => {
|
||||
it('使用 Block 控制更新范围', () => {
|
||||
let rObj, rColor;
|
||||
const ref = createRef();
|
||||
const fn = jest.fn();
|
||||
const fn1 = jest.fn();
|
||||
const App = () => {
|
||||
const _rObj = useReactive({ count: 0 });
|
||||
const _rColor = useReactive('blue');
|
||||
rObj = _rObj;
|
||||
rColor = _rColor;
|
||||
|
||||
fn();
|
||||
|
||||
return (
|
||||
<div ref={ref}>
|
||||
111 222
|
||||
<Block>
|
||||
{() => {
|
||||
fn1();
|
||||
const count = _rObj.count.get();
|
||||
return (
|
||||
<>
|
||||
<div>Count: {count}</div>
|
||||
<div>{_rColor}</div>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</Block>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
render(<App />, container);
|
||||
|
||||
expect(ref.current.innerHTML).toEqual('111 222<div>Count: 0</div><div>blue</div>');
|
||||
|
||||
// 会触发View刷新
|
||||
rObj.count.set(1);
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
expect(fn1).toHaveBeenCalledTimes(2);
|
||||
expect(ref.current.innerHTML).toEqual('111 222<div>Count: 1</div><div>blue</div>');
|
||||
|
||||
// 不会触发View刷新
|
||||
rColor.set('red');
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
expect(fn1).toHaveBeenCalledTimes(2);
|
||||
expect(ref.current.innerHTML).toEqual('111 222<div>Count: 1</div><div>red</div>');
|
||||
});
|
||||
|
||||
it('使用 Block 包裹一个Atom', () => {
|
||||
let rObj;
|
||||
const ref1 = createRef();
|
||||
const fn = jest.fn();
|
||||
const App = () => {
|
||||
const _rObj = useReactive('blue');
|
||||
rObj = _rObj;
|
||||
|
||||
fn();
|
||||
|
||||
return (
|
||||
// div下面有多个元素,_rObj就需要用RText包裹
|
||||
<div ref={ref1}>
|
||||
111 222
|
||||
<Block>{_rObj}</Block>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
render(<App />, container);
|
||||
expect(ref1.current.innerHTML).toEqual('111 222blue');
|
||||
rObj.set('red');
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
expect(ref1.current.innerHTML).toEqual('111 222red');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* Copyright (c) 2023 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.
|
||||
*/
|
||||
|
||||
import Inula, { render, createRef, useReactive, useComputed, For, Show, Switch } from '../../../../src/index';
|
||||
|
||||
describe('测试Switch、Show、For标签的组合使用时的组件渲染', () => {
|
||||
it('Show、For标签的组合使用', () => {
|
||||
const Item = ({ item }) => {
|
||||
return <li key={item.id}>{item.name}</li>;
|
||||
};
|
||||
|
||||
let reactiveObj;
|
||||
const ref = createRef();
|
||||
const ref1 = createRef();
|
||||
const fn = jest.fn();
|
||||
|
||||
const App = () => {
|
||||
const dataList = useReactive([]);
|
||||
reactiveObj = dataList;
|
||||
|
||||
const listLen = useComputed(() => {
|
||||
return dataList.get().length;
|
||||
});
|
||||
|
||||
fn();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Show if={() => dataList.get().length > 0} else={() => <div />}>
|
||||
<div ref={ref} style={{ display: 'flex' }}>
|
||||
<For each={dataList}>{item => <Item item={item} />}</For>
|
||||
</div>
|
||||
</Show>
|
||||
<div ref={ref1}>{listLen}</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
render(<App />, container);
|
||||
|
||||
let liItems = container.querySelectorAll('li');
|
||||
expect(liItems.length).toEqual(0);
|
||||
|
||||
reactiveObj.push({ id: 1, name: '1' });
|
||||
expect(reactiveObj.get().length).toEqual(1);
|
||||
liItems = container.querySelectorAll('li');
|
||||
expect(liItems.length).toEqual(1);
|
||||
|
||||
reactiveObj.push({ id: 2, name: '2' });
|
||||
expect(reactiveObj.get().length).toEqual(2);
|
||||
liItems = container.querySelectorAll('li');
|
||||
expect(liItems.length).toEqual(2);
|
||||
|
||||
expect(ref1.current.innerHTML).toEqual('2');
|
||||
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('Switch、Show和For标签的组合使用', () => {
|
||||
const Item = ({ item }) => {
|
||||
return <li key={item.id}>{item.name}</li>;
|
||||
};
|
||||
|
||||
let reactiveObj;
|
||||
const ref = createRef();
|
||||
const App = () => {
|
||||
const dataList = useReactive([]);
|
||||
reactiveObj = dataList;
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
<Show if={() => dataList.get().length === 0}>
|
||||
<div />
|
||||
</Show>
|
||||
<Show if={() => dataList.get().length > 0}>
|
||||
<div ref={ref} style={{ display: 'flex' }}>
|
||||
<For each={dataList}>{item => <Item item={item} />}</For>
|
||||
</div>
|
||||
</Show>
|
||||
</Switch>
|
||||
);
|
||||
};
|
||||
render(<App />, container);
|
||||
|
||||
let liItems = container.querySelectorAll('li');
|
||||
expect(liItems.length).toEqual(0);
|
||||
|
||||
reactiveObj.push({ id: 1, name: '1' });
|
||||
expect(reactiveObj.get().length).toEqual(1);
|
||||
|
||||
liItems = container.querySelectorAll('li');
|
||||
expect(liItems.length).toEqual(1);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright (c) 2023 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.
|
||||
*/
|
||||
|
||||
import Inula, { render, createRef, act, useReactive, useCompute, reactive, RText } from '../../../../src/index';
|
||||
|
||||
describe('测试 RText 组件', () => {
|
||||
it('使用RText精准更新', () => {
|
||||
let rObj;
|
||||
const ref1 = createRef();
|
||||
const fn = jest.fn();
|
||||
const App = () => {
|
||||
const _rObj = useReactive('blue');
|
||||
rObj = _rObj;
|
||||
|
||||
fn();
|
||||
|
||||
return (
|
||||
// div下面有多个元素,_rObj就需要用RText包裹
|
||||
<div ref={ref1}>
|
||||
111 222
|
||||
<RText>{_rObj}</RText>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
render(<App />, container);
|
||||
expect(ref1.current.innerHTML).toEqual('111 222blue');
|
||||
rObj.set('red');
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
expect(ref1.current.innerHTML).toEqual('111 222red');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,200 @@
|
|||
/*
|
||||
* Copyright (c) 2023 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.
|
||||
*/
|
||||
|
||||
import Inula, { render, createRef, act, useReactive, useCompute, reactive, Show } from '../../../../src/index';
|
||||
|
||||
describe('测试 Show 组件', () => {
|
||||
it('if为primitive值', () => {
|
||||
let rObj;
|
||||
const ref1 = createRef();
|
||||
const ref2 = createRef();
|
||||
const fn = jest.fn();
|
||||
const App = () => {
|
||||
const _rObj = useReactive('blue');
|
||||
rObj = _rObj;
|
||||
|
||||
fn();
|
||||
|
||||
return (
|
||||
// 如果else中的dom和children一个类型,需要增加key,否则会被框架当作同一个dom
|
||||
<Show
|
||||
if={_rObj}
|
||||
else={
|
||||
<div key="else" ref={ref2}>
|
||||
Loading...
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div key="if" ref={ref1}>
|
||||
{_rObj}
|
||||
</div>
|
||||
</Show>
|
||||
);
|
||||
};
|
||||
|
||||
render(<App />, container);
|
||||
expect(ref1.current.innerHTML).toEqual('blue');
|
||||
rObj.set('');
|
||||
expect(ref2.current.innerHTML).toEqual('Loading...');
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('if为primitive值,没有else', () => {
|
||||
let rObj;
|
||||
const ref1 = createRef();
|
||||
const ref2 = createRef();
|
||||
const fn = jest.fn();
|
||||
const App = () => {
|
||||
const _rObj = useReactive('blue');
|
||||
rObj = _rObj;
|
||||
|
||||
fn();
|
||||
|
||||
return (
|
||||
// 如果else中的dom和children一个类型,需要增加key,否则会被框架当作同一个dom
|
||||
<Show if={_rObj}>
|
||||
<div ref={ref1}>{_rObj}</div>
|
||||
</Show>
|
||||
);
|
||||
};
|
||||
|
||||
render(<App />, container);
|
||||
expect(ref1.current.innerHTML).toEqual('blue');
|
||||
rObj.set('');
|
||||
expect(ref2.current).toEqual(null);
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('if为reactive object值', () => {
|
||||
let rObj;
|
||||
const ref1 = createRef();
|
||||
const ref2 = createRef();
|
||||
const fn = jest.fn();
|
||||
const App = () => {
|
||||
const _rObj = useReactive({
|
||||
color: 'blue',
|
||||
});
|
||||
rObj = _rObj;
|
||||
|
||||
fn();
|
||||
|
||||
return (
|
||||
// 如果else中的dom和children一个类型,需要增加key,否则会被框架当作同一个dom
|
||||
<Show
|
||||
if={_rObj.color}
|
||||
else={
|
||||
<div key="else" ref={ref2}>
|
||||
Loading...
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div key="if" ref={ref1}>
|
||||
{_rObj.color}
|
||||
</div>
|
||||
</Show>
|
||||
);
|
||||
};
|
||||
|
||||
render(<App />, container);
|
||||
expect(ref1.current.innerHTML).toEqual('blue');
|
||||
rObj.color.set('');
|
||||
expect(ref2.current.innerHTML).toEqual('Loading...');
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('if为函数', () => {
|
||||
let rObj;
|
||||
const ref1 = createRef();
|
||||
const ref2 = createRef();
|
||||
const fn = jest.fn();
|
||||
const App = () => {
|
||||
const _rObj = useReactive({
|
||||
color: 'blue',
|
||||
});
|
||||
rObj = _rObj;
|
||||
|
||||
fn();
|
||||
|
||||
return (
|
||||
// 如果else中的dom和children一个类型,需要增加key,否则会被框架当作同一个dom
|
||||
<Show
|
||||
if={() => _rObj.color}
|
||||
else={
|
||||
<div key="else" ref={ref2}>
|
||||
Loading...
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div key="if" ref={ref1}>
|
||||
{_rObj.color}
|
||||
</div>
|
||||
</Show>
|
||||
);
|
||||
};
|
||||
|
||||
render(<App />, container);
|
||||
expect(ref1.current.innerHTML).toEqual('blue');
|
||||
rObj.color.set('');
|
||||
expect(ref2.current.innerHTML).toEqual('Loading...');
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('if的children、else是函数', () => {
|
||||
const ref1 = createRef();
|
||||
const ref2 = createRef();
|
||||
const fn = jest.fn();
|
||||
const _count = reactive(0);
|
||||
const _rObj = reactive({
|
||||
color: 'blue',
|
||||
});
|
||||
|
||||
const App = () => {
|
||||
fn();
|
||||
|
||||
return (
|
||||
// 如果else中的dom和children一个类型,需要增加key,否则会被框架当作同一个dom
|
||||
<Show
|
||||
if={() => _rObj.color}
|
||||
else={() => (
|
||||
<div key="else" ref={ref2}>
|
||||
Loading...
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
{() => {
|
||||
const text = useCompute(() => {
|
||||
return _rObj.color.get() + _count.get();
|
||||
});
|
||||
|
||||
return (
|
||||
<div key="if" ref={ref1}>
|
||||
{text}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</Show>
|
||||
);
|
||||
};
|
||||
|
||||
render(<App />, container);
|
||||
expect(ref1.current.innerHTML).toEqual('blue0');
|
||||
// 修改children函数中使用到的响应式变量,也会触发Show组件更新
|
||||
_count.set(1);
|
||||
expect(ref1.current.innerHTML).toEqual('blue1');
|
||||
_rObj.color.set('');
|
||||
expect(ref2.current.innerHTML).toEqual('Loading...');
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* Copyright (c) 2023 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.
|
||||
*/
|
||||
|
||||
import Inula, { render, createRef, act, useReactive, Show, Switch } from '../../../../src/index';
|
||||
|
||||
describe('测试 Switch 组件', () => {
|
||||
it('Switch 配合 Show 使用', () => {
|
||||
let rObj;
|
||||
const refBlue = createRef();
|
||||
const refRed = createRef();
|
||||
const refYellow = createRef();
|
||||
const refNothing = createRef();
|
||||
const fn = jest.fn();
|
||||
const App = () => {
|
||||
const _rObj = useReactive('blue');
|
||||
rObj = _rObj;
|
||||
|
||||
fn();
|
||||
|
||||
return (
|
||||
<Switch default={<div ref={refNothing}>nothing</div>}>
|
||||
{/*if不能写成 _rObj === 'red' 或者 _rObj.get() === 'red' */}
|
||||
<Show if={() => _rObj.get() === 'blue'}>
|
||||
<div id="1" ref={refBlue}>
|
||||
{_rObj}
|
||||
</div>
|
||||
</Show>
|
||||
<Show if={() => _rObj.get() === 'red'}>
|
||||
<div id="2" ref={refRed}>
|
||||
{_rObj}
|
||||
</div>
|
||||
</Show>
|
||||
<Show if={() => _rObj.get() === 'yellow'}>
|
||||
<div id="3" ref={refYellow}>
|
||||
{_rObj}
|
||||
</div>
|
||||
</Show>
|
||||
</Switch>
|
||||
);
|
||||
};
|
||||
|
||||
render(<App />, container);
|
||||
expect(refBlue.current.innerHTML).toEqual('blue');
|
||||
// rObj被3个RContext依赖,分别是Switch组件、Show组件、div[id=1]的Children
|
||||
expect(rObj.usedRContexts.size).toEqual(3);
|
||||
|
||||
act(() => {
|
||||
rObj.set('red');
|
||||
});
|
||||
expect(refRed.current.innerHTML).toEqual('red');
|
||||
// rObj被3个Effect依赖,分别是Switch组件、Show组件、div[id=2]的Children
|
||||
expect(rObj.usedRContexts.size).toEqual(3);
|
||||
|
||||
act(() => {
|
||||
rObj.set('black');
|
||||
});
|
||||
expect(refNothing.current.innerHTML).toEqual('nothing');
|
||||
// rObj被1个RContext依赖,分别是Switch组件
|
||||
expect(rObj.usedRContexts.size).toEqual(1);
|
||||
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,51 @@
|
|||
import Inula, { computed, createRef, reactive, render } from '../../../src/index';
|
||||
|
||||
describe('测试 computed', () => {
|
||||
it('在class组件render中使用computed', () => {
|
||||
let rObj;
|
||||
let appInst;
|
||||
const ref = createRef();
|
||||
const fn = jest.fn();
|
||||
|
||||
class App extends Inula.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
appInst = this;
|
||||
|
||||
this.state = {
|
||||
name: 1,
|
||||
};
|
||||
|
||||
this._rObj = reactive(1);
|
||||
rObj = this._rObj;
|
||||
}
|
||||
|
||||
render() {
|
||||
const computedVal = computed(() => {
|
||||
fn();
|
||||
return this._rObj.get() + '!!!';
|
||||
});
|
||||
|
||||
return <div ref={ref}>{computedVal}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
render(<App />, container);
|
||||
expect(ref.current.innerHTML).toEqual('1!!!'); // computed执行2次
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
rObj.set('2');
|
||||
expect(ref.current.innerHTML).toEqual('2!!!');
|
||||
expect(fn).toHaveBeenCalledTimes(2); // computed执行2次
|
||||
|
||||
// 触发组件重新渲染
|
||||
appInst.setState({ name: 2 });
|
||||
|
||||
expect(fn).toHaveBeenCalledTimes(3); // 生成新的一个computation,再执行了1次,computed总共执行3次
|
||||
|
||||
rObj.set('3');
|
||||
expect(ref.current.innerHTML).toEqual('3!!!');
|
||||
|
||||
expect(fn).toHaveBeenCalledTimes(5); // 两个computation各执行了一次,computed总共执行5次
|
||||
});
|
||||
});
|
|
@ -0,0 +1,377 @@
|
|||
/*
|
||||
* Copyright (c) 2023 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.
|
||||
*/
|
||||
|
||||
import Inula, {
|
||||
createRef,
|
||||
For,
|
||||
reactive,
|
||||
render,
|
||||
useCompute,
|
||||
useReactive,
|
||||
computed,
|
||||
} from '../../../src/index';
|
||||
|
||||
describe('computed 基本使用', () => {
|
||||
it('computed 返回的是一个响应式对象,用到的响应式对象是原始类型', () => {
|
||||
const rObj = reactive('123');
|
||||
const comp = computed(() => {
|
||||
return rObj.get() + '!!!';
|
||||
});
|
||||
expect(comp.get()).toEqual('123!!!');
|
||||
|
||||
rObj.set('456');
|
||||
expect(comp.get()).toEqual('456!!!');
|
||||
});
|
||||
|
||||
it('computed 返回的是一个响应式对象,用到两个响应式对象', () => {
|
||||
const rObj1 = reactive({ name: 'xiaoming' });
|
||||
const rObj2 = reactive({ age: 18 });
|
||||
const comp = computed(() => {
|
||||
return rObj1.name.get() + ' is ' + rObj2.age.get();
|
||||
});
|
||||
expect(comp.get()).toEqual('xiaoming is 18');
|
||||
rObj1.name.set('xiaowang');
|
||||
rObj2.set(prev => ({ age: prev.age + 2 }));
|
||||
expect(comp.get()).toEqual('xiaowang is 20');
|
||||
});
|
||||
|
||||
it('computed 返回的是一个复杂响应式对象', () => {
|
||||
const rObj = reactive({ array: [1, 2, 3, 4, 5, 6] });
|
||||
const comp = computed(() => {
|
||||
return { newArray: rObj.array.get().filter(x => x > 4) };
|
||||
});
|
||||
expect(comp.get()).toEqual({ newArray: [5, 6] });
|
||||
expect(comp.newArray.get()).toEqual([5, 6]);
|
||||
rObj.array.push(...[100]);
|
||||
expect(comp.get()).toEqual({ newArray: [5, 6, 100] });
|
||||
});
|
||||
|
||||
it('computed 返回的是一个响应式对象,用到的响应式对象是对象类型', () => {
|
||||
const rObj = reactive({ array: [1, 2, 3, 4, 5, 6] });
|
||||
const comp = computed(() => {
|
||||
return rObj.array.get().filter(x => x > 4);
|
||||
});
|
||||
expect(comp.get()).toEqual([5, 6]);
|
||||
|
||||
rObj.array.set([1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
||||
expect(comp.get()).toEqual([5, 6, 7, 8, 9]);
|
||||
|
||||
rObj.array.push(...[10, 11]);
|
||||
expect(comp.get()).toEqual([5, 6, 7, 8, 9, 10, 11]);
|
||||
|
||||
rObj.set({ array: [100, 101, 102] });
|
||||
expect(comp.get()).toEqual([100, 101, 102]);
|
||||
});
|
||||
|
||||
it('computed 返回的是一个复杂响应式对象2', () => {
|
||||
const rObj = reactive({ array: [1, 2, 3, 4, 5, 6] });
|
||||
const comp = computed(() => {
|
||||
return { newArray: rObj.array.get().filter(x => x > 4) };
|
||||
});
|
||||
expect(comp.newArray.get()).toEqual([5, 6]);
|
||||
rObj.array.push(...[7, 8]);
|
||||
expect(comp.newArray.get()).toEqual([5, 6, 7, 8]);
|
||||
rObj.array.set([1, 100, 101, 102]);
|
||||
expect(comp.newArray.get()).toEqual([100, 101, 102]);
|
||||
expect(comp.get()).toEqual({ newArray: [100, 101, 102] });
|
||||
});
|
||||
});
|
||||
|
||||
describe('测试 useCompute', () => {
|
||||
it('useComputed基本使用 使用get方法(组件式更新)', () => {
|
||||
let rObj;
|
||||
const ref = createRef();
|
||||
const fn = jest.fn();
|
||||
const App = () => {
|
||||
const _rObj = useReactive('123');
|
||||
rObj = _rObj;
|
||||
|
||||
const _cObj = useCompute(() => {
|
||||
return _rObj.get() + '!!!';
|
||||
});
|
||||
fn();
|
||||
|
||||
return <div ref={ref}>{_cObj.get()}</div>;
|
||||
};
|
||||
|
||||
render(<App />, container);
|
||||
expect(ref.current.innerHTML).toEqual('123!!!');
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
rObj.set('456');
|
||||
expect(ref.current.innerHTML).toEqual('456!!!');
|
||||
expect(fn).toHaveBeenCalledTimes(2);
|
||||
rObj.set('789');
|
||||
expect(ref.current.innerHTML).toEqual('789!!!');
|
||||
expect(fn).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
it('useComputed基本使用 直接使用对象(Dom级更新)', () => {
|
||||
let rObj;
|
||||
const ref = createRef();
|
||||
const fn = jest.fn();
|
||||
const App = () => {
|
||||
const _rObj = useReactive('123');
|
||||
rObj = _rObj;
|
||||
|
||||
const _cObj = useCompute(() => {
|
||||
return _rObj.get() + '!!!';
|
||||
});
|
||||
fn();
|
||||
|
||||
return <div ref={ref}>{_cObj}</div>;
|
||||
};
|
||||
|
||||
render(<App />, container);
|
||||
expect(ref.current.innerHTML).toEqual('123!!!');
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
rObj.set('456');
|
||||
expect(ref.current.innerHTML).toEqual('456!!!');
|
||||
rObj.set('789');
|
||||
expect(ref.current.innerHTML).toEqual('789!!!');
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('useComputed 基本使用2', () => {
|
||||
let rObj;
|
||||
const ref = createRef();
|
||||
const fn = jest.fn();
|
||||
const compFn = jest.fn();
|
||||
const App = () => {
|
||||
const _rObj = useReactive({ array: [1, 2, 3, 4, 5, 6] });
|
||||
rObj = _rObj;
|
||||
|
||||
const cObj = useCompute(() => {
|
||||
compFn();
|
||||
return { len: _rObj.array.get().filter(x => x >= 4).length };
|
||||
});
|
||||
|
||||
fn();
|
||||
|
||||
return <div ref={ref}>{cObj.len}</div>;
|
||||
};
|
||||
|
||||
render(<App />, container);
|
||||
expect(ref.current.innerHTML).toEqual('3');
|
||||
rObj.array.push(...[7, 8]);
|
||||
expect(ref.current.innerHTML).toEqual('5');
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
rObj.array.unshift(...[0, 100]);
|
||||
expect(ref.current.innerHTML).toEqual('6');
|
||||
rObj.set({ array: [1, 100, 101, 102, 103] });
|
||||
expect(ref.current.innerHTML).toEqual('4');
|
||||
expect(compFn).toHaveBeenCalledTimes(4);
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('连锁useComputed使用', () => {
|
||||
let rObj;
|
||||
const ref = createRef();
|
||||
const fn = jest.fn();
|
||||
const compFn = jest.fn();
|
||||
const App = () => {
|
||||
const _rObj = useReactive(1);
|
||||
rObj = _rObj;
|
||||
|
||||
const double = useCompute(() => _rObj.get() * 2);
|
||||
const dd = useCompute(() => {
|
||||
compFn();
|
||||
return double.get() * 2;
|
||||
});
|
||||
|
||||
fn();
|
||||
|
||||
return <div ref={ref}>{dd}</div>;
|
||||
};
|
||||
|
||||
render(<App />, container);
|
||||
expect(ref.current.innerHTML).toEqual('4');
|
||||
expect(compFn).toHaveBeenCalledTimes(1);
|
||||
rObj.set('2');
|
||||
expect(ref.current.innerHTML).toEqual('8');
|
||||
expect(compFn).toHaveBeenCalledTimes(2);
|
||||
rObj.set('4');
|
||||
expect(ref.current.innerHTML).toEqual('16');
|
||||
expect(compFn).toHaveBeenCalledTimes(3);
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('useComputed中使用到了两个响应式对象', () => {
|
||||
let _rObj1;
|
||||
let _rObj2;
|
||||
const ref = createRef();
|
||||
const fn = jest.fn();
|
||||
const compFn = jest.fn();
|
||||
const App = () => {
|
||||
const rObj1 = useReactive({ name: 'xiaoming' });
|
||||
const rObj2 = useReactive({ age: 18 });
|
||||
_rObj1 = rObj1;
|
||||
_rObj2 = rObj2;
|
||||
|
||||
const words = useCompute(() => {
|
||||
compFn();
|
||||
return `${rObj1.name.get()} is ${rObj2.age.get()}`;
|
||||
});
|
||||
|
||||
fn();
|
||||
|
||||
return <div ref={ref}>{words}</div>;
|
||||
};
|
||||
|
||||
render(<App />, container);
|
||||
expect(ref.current.innerHTML).toEqual('xiaoming is 18');
|
||||
expect(compFn).toHaveBeenCalledTimes(1);
|
||||
_rObj1.name.set('xiaowang');
|
||||
expect(ref.current.innerHTML).toEqual('xiaowang is 18');
|
||||
expect(compFn).toHaveBeenCalledTimes(2);
|
||||
_rObj2.set({ age: 20 });
|
||||
expect(ref.current.innerHTML).toEqual('xiaowang is 20');
|
||||
expect(compFn).toHaveBeenCalledTimes(3);
|
||||
_rObj1.name.set('laowang');
|
||||
_rObj2.set({ age: 30 });
|
||||
expect(ref.current.innerHTML).toEqual('laowang is 30');
|
||||
expect(compFn).toHaveBeenCalledTimes(5);
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('多个reactive的compute', () => {
|
||||
let a;
|
||||
const ref = createRef();
|
||||
const compFn = jest.fn();
|
||||
const computeFn = jest.fn();
|
||||
const App = () => {
|
||||
const _a = useReactive('a');
|
||||
const b = useReactive('b');
|
||||
const cond = useReactive(true);
|
||||
a = _a;
|
||||
const compute = useCompute(() => {
|
||||
computeFn();
|
||||
return cond.get() ? _a.get() : b.get();
|
||||
});
|
||||
|
||||
compFn();
|
||||
|
||||
return (
|
||||
<button
|
||||
ref={ref}
|
||||
className={compute}
|
||||
onClick={() => {
|
||||
cond.set(false);
|
||||
}}
|
||||
>
|
||||
{compute}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
render(<App />, container);
|
||||
expect(ref.current.innerHTML).toEqual('a');
|
||||
ref.current.click();
|
||||
expect(ref.current.innerHTML).toEqual('b');
|
||||
a.set('aa');
|
||||
|
||||
expect(computeFn).toHaveBeenCalledTimes(3);
|
||||
|
||||
expect(ref.current.innerHTML).toEqual('b');
|
||||
});
|
||||
|
||||
it('useCompute返回一个数组对象', () => {
|
||||
let rObj;
|
||||
let cObj;
|
||||
let ref = createRef();
|
||||
let appFn = jest.fn();
|
||||
let itemFn = jest.fn();
|
||||
|
||||
const App = () => {
|
||||
const _rObj = useReactive([
|
||||
{ id: 'id-1', name: 'p1' },
|
||||
{ id: 'id-2', name: 'p2' },
|
||||
{ id: 'id-3', name: 'p3' },
|
||||
]);
|
||||
rObj = _rObj;
|
||||
|
||||
const _cObj = useCompute(() => {
|
||||
return _rObj.get().slice();
|
||||
});
|
||||
cObj = _cObj;
|
||||
|
||||
appFn();
|
||||
|
||||
return (
|
||||
<div ref={ref}>
|
||||
<For each={_cObj}>
|
||||
{item => {
|
||||
itemFn();
|
||||
return (
|
||||
<li id={item.id} key={item.id}>
|
||||
{item.name}
|
||||
</li>
|
||||
);
|
||||
}}
|
||||
</For>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
render(<App />, container);
|
||||
let items = container.querySelectorAll('li');
|
||||
expect(items.length).toEqual(3);
|
||||
|
||||
rObj.push({ id: 'id-4', name: 'p4' });
|
||||
|
||||
items = container.querySelectorAll('li');
|
||||
expect(items.length).toEqual(4);
|
||||
|
||||
// rObj[1].name.get();
|
||||
rObj[1].set({ id: 'id-2', name: 'p222' });
|
||||
let li = container.querySelector('#id-2');
|
||||
|
||||
expect(li.innerHTML).toEqual('p222');
|
||||
|
||||
// // 更新
|
||||
// cObj.set([true]);
|
||||
//
|
||||
// items = container.querySelectorAll('li');
|
||||
// expect(items.length).toEqual(1);
|
||||
// expect(appFn).toHaveBeenCalledTimes(1);
|
||||
//
|
||||
// // 第一次渲染执行3次,更新也触发了1次
|
||||
// expect(itemFn).toHaveBeenCalledTimes(4);
|
||||
});
|
||||
|
||||
xit('测试compute在checkbox中的使用', () => {
|
||||
let a;
|
||||
const ref = createRef();
|
||||
const compFn = jest.fn();
|
||||
const computeFn = jest.fn();
|
||||
const App = () => {
|
||||
const rObj = useReactive({ checked: true });
|
||||
const checked = useCompute(() => {
|
||||
return rObj.checked.get();
|
||||
});
|
||||
|
||||
compFn();
|
||||
|
||||
return <Checkbox checked={checked} />;
|
||||
};
|
||||
render(<App />, container);
|
||||
expect(ref.current.innerHTML).toEqual('a');
|
||||
ref.current.click();
|
||||
expect(ref.current.innerHTML).toEqual('b');
|
||||
a.set('aa');
|
||||
|
||||
expect(computeFn).toHaveBeenCalledTimes(3);
|
||||
|
||||
expect(ref.current.innerHTML).toEqual('b');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* Copyright (c) 2023 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.
|
||||
*/
|
||||
|
||||
import Inula, { render, createRef, act, useReactive } from '../../../src/index';
|
||||
import { Show } from '../../../src/reactive/components/Show';
|
||||
import { Switch } from '../../../src/reactive/components/Switch';
|
||||
|
||||
describe('响应式数据usedRContexts', () => {
|
||||
it('测试响应式数据的usedRContexts会随着VNode的删除而清除', () => {
|
||||
let rObj;
|
||||
const refBlue = createRef();
|
||||
const refRed = createRef();
|
||||
const refYellow = createRef();
|
||||
const refNothing = createRef();
|
||||
const fn = jest.fn();
|
||||
const App = () => {
|
||||
const _rObj = useReactive('blue');
|
||||
rObj = _rObj;
|
||||
|
||||
fn();
|
||||
|
||||
return (
|
||||
<Switch default={<div ref={refNothing}>nothing</div>}>
|
||||
{/*if不能写成 _rObj === 'red' 或者 _rObj.get() === 'red' */}
|
||||
<Show if={() => _rObj.get() === 'blue'}>
|
||||
<div id="1" ref={refBlue}>
|
||||
{_rObj}
|
||||
</div>
|
||||
</Show>
|
||||
<Show if={() => _rObj.get() === 'red'}>
|
||||
<div id="2" ref={refRed}>
|
||||
{_rObj}
|
||||
</div>
|
||||
</Show>
|
||||
<Show if={() => _rObj.get() === 'yellow'}>
|
||||
<div id="3" ref={refYellow}>
|
||||
{_rObj}
|
||||
</div>
|
||||
</Show>
|
||||
</Switch>
|
||||
);
|
||||
};
|
||||
|
||||
render(<App />, container);
|
||||
expect(refBlue.current.innerHTML).toEqual('blue');
|
||||
// rObj被3个RContext依赖,分别是Switch组件、Show组件、div[id=1]的Children
|
||||
expect(rObj.usedRContexts.size).toEqual(3);
|
||||
|
||||
act(() => {
|
||||
rObj.set('red');
|
||||
});
|
||||
expect(refRed.current.innerHTML).toEqual('red');
|
||||
// rObj被3个Effect依赖,分别是Switch组件、Show组件、div[id=2]的Children
|
||||
expect(rObj.usedRContexts.size).toEqual(3);
|
||||
|
||||
act(() => {
|
||||
rObj.set('black');
|
||||
});
|
||||
expect(refNothing.current.innerHTML).toEqual('nothing');
|
||||
// rObj被1个RContext依赖,分别是Switch组件
|
||||
expect(rObj.usedRContexts.size).toEqual(1);
|
||||
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright (c) 2023 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.
|
||||
*/
|
||||
|
||||
import Inula, {createRef, render, useReactive, useState, Show} from '../../../src/index';
|
||||
|
||||
describe('传统API和响应式API混合使用', () => {
|
||||
it('混合使用1', () => {
|
||||
let rObj, isShow, update;
|
||||
const ref = createRef();
|
||||
const fn = jest.fn();
|
||||
|
||||
const App = () => {
|
||||
const _isShow = useReactive(true);
|
||||
isShow = _isShow;
|
||||
|
||||
const [_, setState] = useState({});
|
||||
|
||||
update = () => setState({});
|
||||
return (
|
||||
<Show if={isShow}>
|
||||
<Child />
|
||||
</Show>
|
||||
);
|
||||
};
|
||||
|
||||
const Child = () => {
|
||||
const _rObj = useReactive('blue');
|
||||
rObj = _rObj;
|
||||
|
||||
fn();
|
||||
|
||||
return <div ref={ref} className={_rObj}></div>;
|
||||
};
|
||||
|
||||
render(<App />, container);
|
||||
expect(ref.current.className).toEqual('blue');
|
||||
|
||||
// 改变了DOM结构
|
||||
isShow.set(false);
|
||||
expect(ref.current).toEqual(null);
|
||||
|
||||
update();
|
||||
|
||||
expect(ref.current).toEqual(null);
|
||||
});
|
||||
|
||||
|
||||
});
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright (c) 2023 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.
|
||||
*/
|
||||
|
||||
import Inula, { render, createRef, act, useReactive } from '../../../src/index';
|
||||
|
||||
describe('测试混合型的 children', () => {
|
||||
it('children是 字符串+Atom 场景', () => {
|
||||
let rObj;
|
||||
const ref1 = createRef();
|
||||
const fn = jest.fn();
|
||||
const App = () => {
|
||||
const _rObj = useReactive(0);
|
||||
rObj = _rObj;
|
||||
|
||||
fn();
|
||||
|
||||
return (
|
||||
// div下面有多个元素
|
||||
<div ref={ref1}>Count: {_rObj}</div>
|
||||
);
|
||||
};
|
||||
|
||||
render(<App />, container);
|
||||
expect(ref1.current.innerHTML).toEqual('Count: 0');
|
||||
rObj.set(1);
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
expect(ref1.current.innerHTML).toEqual('Count: 1');
|
||||
});
|
||||
|
||||
it('children是 字符串+Atom 场景2', () => {
|
||||
let rObj;
|
||||
const ref1 = createRef();
|
||||
const fn = jest.fn();
|
||||
const App = () => {
|
||||
const _rObj = useReactive({ count: 0 });
|
||||
rObj = _rObj;
|
||||
|
||||
fn();
|
||||
|
||||
return (
|
||||
// div下面有多个元素
|
||||
<div ref={ref1}>Count: {_rObj.count}</div>
|
||||
);
|
||||
};
|
||||
|
||||
render(<App />, container);
|
||||
expect(ref1.current.innerHTML).toEqual('Count: 0');
|
||||
rObj.count.set(1);
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
expect(ref1.current.innerHTML).toEqual('Count: 1');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,973 @@
|
|||
/*
|
||||
* Copyright (c) 2023 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.
|
||||
*/
|
||||
|
||||
import Inula, {
|
||||
render,
|
||||
createRef,
|
||||
useReactive,
|
||||
useCompute,
|
||||
reactive,
|
||||
computed,
|
||||
watchReactive,
|
||||
} from '../../../src/index';
|
||||
import { GET_R_NODE } from '../../../src/reactive/proxy/RProxyHandler';
|
||||
import { isAtom, isReactiveProxy, isRNode } from '../../../src/reactive/Utils';
|
||||
|
||||
describe('测试 useReactive(对象)', () => {
|
||||
it('reactive基本使用', () => {
|
||||
let rObj;
|
||||
const ref = createRef();
|
||||
const fn = jest.fn();
|
||||
const App = () => {
|
||||
const _rObj = useReactive({
|
||||
color: 'blue',
|
||||
});
|
||||
rObj = _rObj;
|
||||
|
||||
fn();
|
||||
|
||||
return <div ref={ref}>{_rObj.color}</div>;
|
||||
};
|
||||
|
||||
render(<App />, container);
|
||||
expect(ref.current.innerHTML).toEqual('blue');
|
||||
rObj.color.set('red');
|
||||
expect(rObj.color.get()).toEqual('red');
|
||||
expect(ref.current.innerHTML).toEqual('red');
|
||||
rObj.color.set(prev => prev + '!!');
|
||||
expect(rObj.color.get()).toEqual('red!!');
|
||||
expect(ref.current.innerHTML).toEqual('red!!');
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('响应式对象赋值修改为一个对象', () => {
|
||||
let rObj;
|
||||
const ref = createRef();
|
||||
const fn = jest.fn();
|
||||
const App = () => {
|
||||
const _rObj = useReactive({
|
||||
data: { framework: 'Vue' },
|
||||
});
|
||||
rObj = _rObj;
|
||||
|
||||
fn();
|
||||
|
||||
return <div ref={ref}>{_rObj.data.framework}</div>;
|
||||
};
|
||||
|
||||
render(<App />, container);
|
||||
expect(ref.current.innerHTML).toEqual('Vue');
|
||||
rObj.data.set({ framework: 'React' });
|
||||
expect(rObj.data.framework.get()).toEqual('React');
|
||||
expect(ref.current.innerHTML).toEqual('React');
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('赋值修改复杂响应式对象', () => {
|
||||
let rObj;
|
||||
const ref = createRef();
|
||||
const fn = jest.fn();
|
||||
const App = () => {
|
||||
const _rObj = useReactive({
|
||||
data: { framework: { js: 'Vue' } },
|
||||
});
|
||||
rObj = _rObj;
|
||||
|
||||
fn();
|
||||
|
||||
return <div ref={ref}>{_rObj.data.framework.js}</div>;
|
||||
};
|
||||
|
||||
render(<App />, container);
|
||||
expect(ref.current.innerHTML).toEqual('Vue');
|
||||
rObj.data.set({ framework: { js: 'React' } });
|
||||
expect(rObj.data.framework.get()).toEqual({ js: 'React' });
|
||||
expect(ref.current.innerHTML).toEqual('React');
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('赋值修改响应式对象中Atom的值', () => {
|
||||
let rObj;
|
||||
const ref = createRef();
|
||||
const fn = jest.fn();
|
||||
const App = () => {
|
||||
const _rObj = useReactive({
|
||||
rdata: { framework: 'Vue' },
|
||||
});
|
||||
rObj = _rObj;
|
||||
|
||||
fn();
|
||||
|
||||
return <div ref={ref}>{_rObj.rdata.framework}</div>;
|
||||
};
|
||||
|
||||
render(<App />, container);
|
||||
expect(ref.current.innerHTML).toEqual('Vue');
|
||||
rObj.rdata.framework.set('React');
|
||||
expect(rObj.rdata.get()).toEqual({ framework: 'React' });
|
||||
expect(ref.current.innerHTML).toEqual('React');
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('把响应式属性传递到子组件', () => {
|
||||
let rObj;
|
||||
const ref = createRef();
|
||||
const fn1 = jest.fn();
|
||||
const fn2 = jest.fn();
|
||||
|
||||
const App = () => {
|
||||
const _rObj = useReactive({
|
||||
data: {
|
||||
color: 'blue',
|
||||
},
|
||||
});
|
||||
rObj = _rObj;
|
||||
|
||||
fn1();
|
||||
return <Child color={_rObj.data.color} />;
|
||||
};
|
||||
|
||||
const Child = ({ color }) => {
|
||||
fn2();
|
||||
|
||||
const cl = useCompute(() => {
|
||||
return 'cl-' + color.get();
|
||||
});
|
||||
|
||||
return <div ref={ref} className={cl}></div>;
|
||||
};
|
||||
|
||||
render(<App />, container);
|
||||
expect(ref.current.className).toEqual('cl-blue');
|
||||
expect(fn1).toHaveBeenCalledTimes(1);
|
||||
expect(fn2).toHaveBeenCalledTimes(1);
|
||||
rObj.data.color.set('red');
|
||||
expect(fn1).toHaveBeenCalledTimes(1);
|
||||
expect(fn2).toHaveBeenCalledTimes(1);
|
||||
expect(ref.current.className).toEqual('cl-red');
|
||||
});
|
||||
|
||||
it('reactive对象中“原始数据”被赋值为“对象”', () => {
|
||||
let rObj;
|
||||
const ref = createRef();
|
||||
const fn = jest.fn();
|
||||
const App = () => {
|
||||
const _rObj = useReactive({
|
||||
data: 'blue',
|
||||
});
|
||||
rObj = _rObj;
|
||||
|
||||
_rObj.data.set({ color: 'red' });
|
||||
|
||||
fn();
|
||||
|
||||
return <div ref={ref}>{_rObj.data.color}</div>;
|
||||
};
|
||||
|
||||
render(<App />, container);
|
||||
expect(ref.current.innerHTML).toEqual('red');
|
||||
rObj.data.color.set('blue');
|
||||
expect(rObj.data.color.get()).toEqual('blue');
|
||||
expect(ref.current.innerHTML).toEqual('blue');
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('reactive对象中“对象”被赋值为“新对象”', () => {
|
||||
let rObj;
|
||||
const ref = createRef();
|
||||
const fn = jest.fn();
|
||||
const App = () => {
|
||||
const _rObj = useReactive({
|
||||
data: {
|
||||
cl: 'blue',
|
||||
},
|
||||
});
|
||||
rObj = _rObj;
|
||||
|
||||
_rObj.data.set({ color: 'red' });
|
||||
|
||||
fn();
|
||||
|
||||
return <div ref={ref}>{_rObj.data.color}</div>;
|
||||
};
|
||||
|
||||
render(<App />, container);
|
||||
expect(ref.current.innerHTML).toEqual('red');
|
||||
rObj.data.color.set('blue');
|
||||
expect(rObj.data.color.get()).toEqual('blue');
|
||||
expect(ref.current.innerHTML).toEqual('blue');
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('测试reactive数组', () => {
|
||||
it('reactive“数组”length的使用', () => {
|
||||
let rObj;
|
||||
const ref = createRef();
|
||||
const fn = jest.fn();
|
||||
const App = () => {
|
||||
const _rObj = useReactive({
|
||||
data: [
|
||||
{ name: 'p1', age: 1 },
|
||||
{ name: 'p2', age: 2 },
|
||||
],
|
||||
});
|
||||
rObj = _rObj;
|
||||
|
||||
fn();
|
||||
|
||||
// 在DOM中使用length无法精细响应式
|
||||
return <div ref={ref}>{_rObj.data.length}</div>;
|
||||
};
|
||||
|
||||
render(<App />, container);
|
||||
expect(ref.current.innerHTML).toEqual('2');
|
||||
rObj.data.set([{ name: 'p1', age: 1 }]);
|
||||
expect(ref.current.innerHTML).toEqual('1');
|
||||
expect(fn).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('reactive“数组”的使用', () => {
|
||||
let rObj;
|
||||
const ref = createRef();
|
||||
const fn = jest.fn();
|
||||
const App = () => {
|
||||
const _rObj = useReactive({
|
||||
data: [
|
||||
{ name: 'p1', age: 1 },
|
||||
{ name: 'p2', age: 2 },
|
||||
],
|
||||
});
|
||||
rObj = _rObj;
|
||||
|
||||
fn();
|
||||
|
||||
return <div ref={ref}>{_rObj.data[0].name}</div>;
|
||||
};
|
||||
|
||||
render(<App />, container);
|
||||
expect(ref.current.innerHTML).toEqual('p1');
|
||||
// 这种修改无法响应!
|
||||
// rObj.data.set([
|
||||
// { name: 'p11', age: 1 },
|
||||
// ]);
|
||||
|
||||
// 直接修改数组中被使用属性
|
||||
rObj.data[0].name.set('p11');
|
||||
expect(ref.current.innerHTML).toEqual('p11');
|
||||
// 在DOM中使用length无法精细响应式
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('jsx中通过items.get().map遍历reactive“数组”', () => {
|
||||
let rObj;
|
||||
const ref = createRef();
|
||||
const fn = jest.fn();
|
||||
const App = () => {
|
||||
const _rObj = useReactive({
|
||||
items: [
|
||||
{ name: 'p1', id: 1 },
|
||||
{ name: 'p2', id: 2 },
|
||||
],
|
||||
});
|
||||
rObj = _rObj;
|
||||
|
||||
fn();
|
||||
|
||||
return (
|
||||
<div ref={ref}>
|
||||
{_rObj.items.get().map(item => {
|
||||
return <li key={item.id}>{item.name}</li>;
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
render(<App />, container);
|
||||
let items = container.querySelectorAll('li');
|
||||
expect(items.length).toEqual(2);
|
||||
|
||||
// 每次修改items都会触发整个组件刷新
|
||||
rObj.items.set([{ name: 'p11', age: 1 }]);
|
||||
|
||||
items = container.querySelectorAll('li');
|
||||
expect(items.length).toEqual(1);
|
||||
expect(fn).toHaveBeenCalledTimes(2);
|
||||
|
||||
// 每次修改items都会触发整个组件刷新
|
||||
rObj.items.push({ name: 'p22', id: 2 });
|
||||
|
||||
items = container.querySelectorAll('li');
|
||||
expect(items.length).toEqual(2);
|
||||
expect(fn).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
it('jsx中通过items.get().map遍历reactive“数组”,孩子是Item', () => {
|
||||
let rObj;
|
||||
const ref = createRef();
|
||||
const fn = jest.fn();
|
||||
const Item = ({ item }) => {
|
||||
return <li key={item.id}>{item.name}</li>;
|
||||
};
|
||||
|
||||
const App = () => {
|
||||
const _rObj = useReactive({
|
||||
items: [
|
||||
{ name: 'p1', id: 1 },
|
||||
{ name: 'p2', id: 2 },
|
||||
],
|
||||
});
|
||||
rObj = _rObj;
|
||||
|
||||
fn();
|
||||
|
||||
return (
|
||||
<div ref={ref}>
|
||||
{/*items必须要调用get()才能map*/}
|
||||
{_rObj.items.get().map(item => {
|
||||
return <Item item={item} />;
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
render(<App />, container);
|
||||
let items = container.querySelectorAll('li');
|
||||
expect(items.length).toEqual(2);
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
|
||||
// 每次修改items都会触发整个组件刷新
|
||||
rObj.items.set([{ name: 'p11', age: 1 }]);
|
||||
|
||||
items = container.querySelectorAll('li');
|
||||
expect(items.length).toEqual(1);
|
||||
expect(fn).toHaveBeenCalledTimes(2);
|
||||
|
||||
// 每次修改items都会触发整个组件刷新
|
||||
rObj.items.push({ name: 'p22', id: 2 });
|
||||
|
||||
items = container.querySelectorAll('li');
|
||||
expect(items.length).toEqual(2);
|
||||
expect(fn).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
it('jsx中通过items.map遍历reactive“数组”,具有响应式', () => {
|
||||
let rObj;
|
||||
const ref = createRef();
|
||||
const fn = jest.fn();
|
||||
const Item = ({ item }) => {
|
||||
return <li key={item.id}>{item.name}</li>;
|
||||
};
|
||||
|
||||
const App = () => {
|
||||
const _rObj = useReactive({
|
||||
items: [
|
||||
{ name: 'p1', id: 1 },
|
||||
{ name: 'p2', id: 2 },
|
||||
{ name: 'p3', id: 3 },
|
||||
],
|
||||
});
|
||||
rObj = _rObj;
|
||||
|
||||
fn();
|
||||
|
||||
return (
|
||||
<div ref={ref}>
|
||||
{_rObj.items.map(item => {
|
||||
return <Item item={item} />;
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
render(<App />, container);
|
||||
let items = container.querySelectorAll('li');
|
||||
expect(items.length).toEqual(3);
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
|
||||
rObj.items.set([
|
||||
{ name: 'p11', age: 1 },
|
||||
{ name: 'p22', age: 2 },
|
||||
]);
|
||||
|
||||
items = container.querySelectorAll('li');
|
||||
// 子元素不会响应式变化
|
||||
expect(items.length).toEqual(2);
|
||||
expect(fn).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('jsx中通过items.map遍历reactive“数组”,孩子是Item,Item对象具有响应式', () => {
|
||||
let rObj;
|
||||
const ref = createRef();
|
||||
const fn = jest.fn();
|
||||
const Item = ({ item }) => {
|
||||
const id = useCompute(() => {
|
||||
return `id-${item.id.get()}`;
|
||||
});
|
||||
|
||||
return (
|
||||
<li key={item.id} id={id}>
|
||||
{item.name}
|
||||
</li>
|
||||
);
|
||||
};
|
||||
|
||||
const App = () => {
|
||||
const _rObj = useReactive({
|
||||
items: [
|
||||
{ name: 'p1', id: 1 },
|
||||
{ name: 'p2', id: 2 },
|
||||
],
|
||||
});
|
||||
rObj = _rObj;
|
||||
|
||||
fn();
|
||||
|
||||
return (
|
||||
<div ref={ref}>
|
||||
{_rObj.items.map(item => {
|
||||
return <Item item={item} />;
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
render(<App />, container);
|
||||
let item = container.querySelector('#id-1');
|
||||
expect(item.innerHTML).toEqual('p1');
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
|
||||
rObj.items[0].name.set('p111');
|
||||
item = container.querySelector('#id-1');
|
||||
// 子元素会响应式变化
|
||||
expect(item.innerHTML).toEqual('p111');
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('测试响应式数据', () => {
|
||||
const obj = reactive({
|
||||
data: [
|
||||
{
|
||||
id: '1',
|
||||
value: 'val-1',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
value: 'val-2',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// 使用,让创建children
|
||||
obj.data[1].value.read();
|
||||
|
||||
obj.set({
|
||||
data: [
|
||||
{
|
||||
id: '11',
|
||||
value: 'val-11',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
obj.set({
|
||||
data: [
|
||||
{
|
||||
id: '111',
|
||||
value: 'val-111',
|
||||
},
|
||||
{
|
||||
id: '222',
|
||||
value: 'val-222',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(obj.data[1].value.get()).toEqual('val-222');
|
||||
});
|
||||
|
||||
it('响应式对象为复杂对象时,使用set重新设置', () => {
|
||||
const rObj = reactive({ data: [1, 2, 3, 4, 5, 6] });
|
||||
rObj.data.push(...[7, 8]);
|
||||
expect(rObj.data.get()).toEqual([1, 2, 3, 4, 5, 6, 7, 8]);
|
||||
rObj.data.set([100, 101]);
|
||||
expect(rObj.get()).toEqual({ data: [100, 101] });
|
||||
});
|
||||
|
||||
it('使用set直接修改响应式对象数组中某个元素的值', () => {
|
||||
const rObj = reactive({ data: [1, 2, 3] });
|
||||
rObj.data.push(...[4, 5, 6]);
|
||||
expect(rObj.data.get()).toEqual([1, 2, 3, 4, 5, 6]);
|
||||
|
||||
// 修改数组第4个元素
|
||||
rObj.data[1].set({ val: 2 });
|
||||
expect(rObj.get()).toEqual({ data: [1, { val: 2 }, 3, 4, 5, 6] });
|
||||
});
|
||||
|
||||
it('使用set直接修改响应式对象数组中某个元素的值2', () => {
|
||||
const rObj = reactive({ data: [1, 2, 3] });
|
||||
rObj.data.push(...[4, 5, 6]);
|
||||
expect(rObj.data.get()).toEqual([1, 2, 3, 4, 5, 6]);
|
||||
|
||||
// 修改数组第4个元素
|
||||
rObj.data[4].set({ val: 2 });
|
||||
expect(rObj.get()).toEqual({ data: [1, 2, 3, 4, { val: 2 }, 6] });
|
||||
});
|
||||
|
||||
it('在删除数组中一个数字,再加一个对象,类型是RNode', () => {
|
||||
const rObj = reactive({ data: [1, 2, 3, 4] });
|
||||
// 使用最后一个数据,在children中创建出child
|
||||
rObj.data[3].get();
|
||||
// 删除最后一个数据
|
||||
rObj.data.set([1, 2, 3]);
|
||||
// 重新增加一个obj类型的数据
|
||||
rObj.data.set([1, 2, 3, { val: 4 }]);
|
||||
|
||||
// rObj.data[3]是RNode
|
||||
expect(isRNode(rObj.data[3][GET_R_NODE])).toBeTruthy();
|
||||
|
||||
expect(rObj.data[3].val.get()).toEqual(4);
|
||||
});
|
||||
|
||||
xit('钻石问题', () => {
|
||||
const fn = jest.fn();
|
||||
const rObj = reactive(0);
|
||||
const evenOrOdd = computed(() => (rObj.get() % 2 === 0 ? 'even' : 'odd'));
|
||||
|
||||
watchReactive(() => {
|
||||
fn();
|
||||
rObj.get();
|
||||
evenOrOdd.get();
|
||||
});
|
||||
|
||||
rObj.set(1);
|
||||
|
||||
// TODO
|
||||
expect(fn).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
it('数组中的数据由“对象”变成“字符串”', () => {
|
||||
let fn = jest.fn();
|
||||
let fn1 = jest.fn();
|
||||
const rObj = reactive({
|
||||
items: [
|
||||
{ name: 'p1', id: 1 },
|
||||
{ name: { n: 'p22' }, id: 2 },
|
||||
],
|
||||
});
|
||||
|
||||
watchReactive(rObj.items[1].name, () => {
|
||||
fn();
|
||||
});
|
||||
watchReactive(rObj.items[1].name.n, () => {
|
||||
fn1();
|
||||
});
|
||||
|
||||
rObj.items.set([
|
||||
{ name: 'p1', id: 1 },
|
||||
{ name: 'p2', id: 2 }, // name 改为 基本数据类型
|
||||
]);
|
||||
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
|
||||
// 无法触发fn1
|
||||
expect(fn1).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('数组中的数据由“字符串”变成“对象”', () => {
|
||||
let fn = jest.fn();
|
||||
let fn1 = jest.fn();
|
||||
const rObj = reactive({
|
||||
items: [
|
||||
{ name: 'p1', id: 1 },
|
||||
{ name: 'p2', id: 2 },
|
||||
],
|
||||
});
|
||||
|
||||
watchReactive(rObj.items[1].name, () => {
|
||||
fn();
|
||||
});
|
||||
// 允许使用或监听没有定义的属性
|
||||
watchReactive(rObj.items[1].name.n, () => {
|
||||
fn1();
|
||||
});
|
||||
|
||||
rObj.items.set([
|
||||
{ name: 'p1', id: 1 },
|
||||
{ name: { n: 'p22' }, id: 2 },
|
||||
]);
|
||||
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
|
||||
// 可以触发fn1
|
||||
expect(fn1).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('访问一个不存在的属性,会抛出异常', () => {
|
||||
let fn = jest.fn();
|
||||
let fn1 = jest.fn();
|
||||
const rObj = reactive({
|
||||
items: [{ name: 'p1' }, { name: 'p2' }],
|
||||
});
|
||||
|
||||
watchReactive(() => {
|
||||
rObj.items[1].get();
|
||||
fn();
|
||||
});
|
||||
watchReactive(() => {
|
||||
// 会抛异常
|
||||
rObj.items[1].name.n.get();
|
||||
fn1();
|
||||
});
|
||||
|
||||
rObj.items.set([{ name: 'p1' }, { name: { n: 'p22' } }]);
|
||||
|
||||
expect(fn).toHaveBeenCalledTimes(2);
|
||||
|
||||
// 无法触发fn1
|
||||
expect(fn1).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('数组中的数据由“字符串”变成“对象”3', () => {
|
||||
let fn = jest.fn();
|
||||
let fn1 = jest.fn();
|
||||
const rObj = reactive({
|
||||
items: [{ a: 1 }, 2, 3],
|
||||
});
|
||||
|
||||
watchReactive(() => {
|
||||
rObj.items[1].get();
|
||||
fn();
|
||||
});
|
||||
|
||||
rObj.items.set([2, 3, 4]);
|
||||
|
||||
expect(fn).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('数组中的数据由“数字”变成“对象”', () => {
|
||||
let fn = jest.fn();
|
||||
let fn1 = jest.fn();
|
||||
const rObj = reactive({
|
||||
items: [1, 2, 3],
|
||||
});
|
||||
|
||||
watchReactive(() => {
|
||||
rObj.items[0].get();
|
||||
fn();
|
||||
});
|
||||
|
||||
watchReactive(() => {
|
||||
rObj.get();
|
||||
fn1();
|
||||
});
|
||||
|
||||
rObj.items.set([{ a: 1 }, 3, 4]);
|
||||
|
||||
expect(fn).toHaveBeenCalledTimes(2);
|
||||
|
||||
// 父数据也会触发
|
||||
expect(fn1).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('数组中的数据由“对象”变成“数组”', () => {
|
||||
let fn = jest.fn();
|
||||
let fn1 = jest.fn();
|
||||
const rObj = reactive({
|
||||
items: [{ a: 1 }, 2, 3],
|
||||
});
|
||||
|
||||
watchReactive(() => {
|
||||
rObj.items[0].get();
|
||||
fn();
|
||||
});
|
||||
|
||||
watchReactive(() => {
|
||||
rObj.get();
|
||||
fn1();
|
||||
});
|
||||
|
||||
rObj.items.set([[1], 3, 4]);
|
||||
|
||||
expect(fn).toHaveBeenCalledTimes(2);
|
||||
|
||||
// 父数据也会触发
|
||||
expect(fn1).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('数组中的数据由“空数组”变成“空数组”', () => {
|
||||
let fn = jest.fn();
|
||||
let fn1 = jest.fn();
|
||||
const rObj = reactive({
|
||||
items: [[], 2, 3],
|
||||
});
|
||||
|
||||
watchReactive(() => {
|
||||
rObj.items[0].get();
|
||||
fn();
|
||||
});
|
||||
|
||||
watchReactive(() => {
|
||||
rObj.get();
|
||||
fn1();
|
||||
});
|
||||
|
||||
rObj.items.set([[], 3, 4]);
|
||||
|
||||
expect(fn).toHaveBeenCalledTimes(2);
|
||||
|
||||
// 父数据也会触发
|
||||
expect(fn1).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('数组中的2个数据由“对象”变成“对象”', () => {
|
||||
let fn = jest.fn();
|
||||
let fn1 = jest.fn();
|
||||
const rObj = reactive({
|
||||
items: [{ a: { b: 1 }, b: { c: 2 } }, { a: 2 }, 3],
|
||||
});
|
||||
|
||||
watchReactive(() => {
|
||||
rObj.items[0].a.get();
|
||||
rObj.items[0].b.get();
|
||||
fn();
|
||||
});
|
||||
|
||||
watchReactive(() => {
|
||||
rObj.get();
|
||||
fn1();
|
||||
});
|
||||
|
||||
// 第一个a 由{b: 1} -> {b: 2}能够精准更新
|
||||
rObj.items.set([{ a: { b: 2 }, b: { c: 3 } }, { a: 3 }, 4]);
|
||||
|
||||
expect(fn).toHaveBeenCalledTimes(2);
|
||||
|
||||
// 父数据也会触发
|
||||
expect(fn1).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('数组中的2个数据由“对象”变成“对象”,前一个属性能精准更新,会触发后面那个', () => {
|
||||
let fn = jest.fn();
|
||||
let fn1 = jest.fn();
|
||||
let fn2 = jest.fn();
|
||||
let fn3 = jest.fn();
|
||||
let fn4 = jest.fn();
|
||||
const rObj = reactive({
|
||||
items: [{ a: { b: 1 }, b: { c: 2 } }, { a: 2 }, 3],
|
||||
});
|
||||
|
||||
watchReactive(() => {
|
||||
rObj.items[0].a.get();
|
||||
fn();
|
||||
});
|
||||
watchReactive(() => {
|
||||
// b由 { c: 2 } -> { c: 3 } 可以触发
|
||||
rObj.items[0].b.get();
|
||||
fn1();
|
||||
});
|
||||
watchReactive(() => {
|
||||
// b由 1 -> undefined 可以触发
|
||||
rObj.items[0].a.b.get();
|
||||
fn2();
|
||||
});
|
||||
watchReactive(() => {
|
||||
// c由 2 -> 3 可以触发
|
||||
rObj.items[0].b.c.get();
|
||||
fn3();
|
||||
});
|
||||
watchReactive(() => {
|
||||
rObj.get();
|
||||
fn4();
|
||||
});
|
||||
|
||||
// 第一个a 由{b: 1} -> {d: 2}能够精准更新
|
||||
rObj.items.set([{ a: { d: 2 }, b: { c: 3 } }, { a: 3 }, 4]);
|
||||
|
||||
expect(fn).toHaveBeenCalledTimes(2);
|
||||
expect(fn1).toHaveBeenCalledTimes(2);
|
||||
expect(fn2).toHaveBeenCalledTimes(2);
|
||||
expect(fn3).toHaveBeenCalledTimes(2);
|
||||
|
||||
// 父数据也会触发
|
||||
expect(fn4).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('数组中的2个数据由“对象”变成“对象”,前一个属性不能精准更新,也不再触发后面那个', () => {
|
||||
let fn = jest.fn();
|
||||
let fn1 = jest.fn();
|
||||
let fn2 = jest.fn();
|
||||
let fn3 = jest.fn();
|
||||
let fn4 = jest.fn();
|
||||
const rObj = reactive({
|
||||
items: [{ a: { b: 1 }, b: { c: 2 } }, { a: 2 }, 3],
|
||||
});
|
||||
|
||||
watchReactive(() => {
|
||||
rObj.items[0].a.get();
|
||||
fn();
|
||||
});
|
||||
watchReactive(() => {
|
||||
// b由 { c: 2 } -> { c: 3 } 可以触发
|
||||
rObj.items[0].b.get();
|
||||
fn1();
|
||||
});
|
||||
watchReactive(() => {
|
||||
rObj.items[0].a.b.get();
|
||||
fn2();
|
||||
});
|
||||
watchReactive(() => {
|
||||
rObj.items[0].b.c.get();
|
||||
fn3();
|
||||
});
|
||||
watchReactive(() => {
|
||||
rObj.get();
|
||||
fn4();
|
||||
});
|
||||
|
||||
// 第一个 a 由{b: 1} -> 1 不能够精准更新
|
||||
rObj.items.set([{ a: 1, b: { c: 3 } }, { a: 3 }, 4]);
|
||||
|
||||
expect(fn).toHaveBeenCalledTimes(2);
|
||||
expect(fn1).toHaveBeenCalledTimes(2);
|
||||
// 由 { b: 1 } -> 1 是不会触发 b 精准更新
|
||||
expect(fn2).toHaveBeenCalledTimes(1);
|
||||
// 前一个属性不能精准更新,也不触发后面那个的精准更新
|
||||
expect(fn3).toHaveBeenCalledTimes(1);
|
||||
|
||||
// 父数据也会触发
|
||||
expect(fn4).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('数组中的2个数据由“对象”变成“[]”,前一个属性不能精准更新,也不再触发后面那个', () => {
|
||||
let fn = jest.fn();
|
||||
let fn1 = jest.fn();
|
||||
let fn2 = jest.fn();
|
||||
let fn3 = jest.fn();
|
||||
let fn4 = jest.fn();
|
||||
const rObj = reactive({
|
||||
items: [{ a: { b: 1 }, b: { c: 2 } }, { a: 2 }, 3],
|
||||
});
|
||||
|
||||
watchReactive(() => {
|
||||
rObj.items[0].a.get();
|
||||
fn();
|
||||
});
|
||||
watchReactive(() => {
|
||||
// b由 { c: 2 } -> { c: 3 } 可以触发
|
||||
rObj.items[0].b.get();
|
||||
fn1();
|
||||
});
|
||||
watchReactive(() => {
|
||||
rObj.items[0].a.b.get();
|
||||
fn2();
|
||||
});
|
||||
watchReactive(() => {
|
||||
rObj.items[0].b.c.get();
|
||||
fn3();
|
||||
});
|
||||
watchReactive(() => {
|
||||
rObj.get();
|
||||
fn4();
|
||||
});
|
||||
|
||||
// 第一个 a 由{b: 1} -> [] 不能够精准更新
|
||||
rObj.items.set([{ a: [], b: { c: 3 } }, { a: 3 }, 4]);
|
||||
|
||||
expect(fn).toHaveBeenCalledTimes(2);
|
||||
expect(fn1).toHaveBeenCalledTimes(2);
|
||||
// 由 { b: 1 } -> 1 是不会触发 b 精准更新
|
||||
expect(fn2).toHaveBeenCalledTimes(1);
|
||||
// 前一个属性不能精准更新,也不触发后面那个的精准更新
|
||||
expect(fn3).toHaveBeenCalledTimes(1);
|
||||
|
||||
// 父数据也会触发
|
||||
expect(fn4).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('数组中的2个数据由“对象”变成“null”,前一个属性不能精准更新,也不再触发后面那个', () => {
|
||||
let fn = jest.fn();
|
||||
let fn1 = jest.fn();
|
||||
let fn2 = jest.fn();
|
||||
let fn3 = jest.fn();
|
||||
let fn4 = jest.fn();
|
||||
const rObj = reactive({
|
||||
items: [{ a: { b: 1 }, b: { c: 2 } }, { a: 2 }, 3],
|
||||
});
|
||||
|
||||
watchReactive(() => {
|
||||
rObj.items[0].a.get();
|
||||
fn();
|
||||
});
|
||||
watchReactive(() => {
|
||||
// b由 { c: 2 } -> { c: 3 } 可以触发
|
||||
rObj.items[0].b.get();
|
||||
fn1();
|
||||
});
|
||||
watchReactive(() => {
|
||||
rObj.items[0].a.b.get();
|
||||
fn2();
|
||||
});
|
||||
watchReactive(() => {
|
||||
rObj.items[0].b.c.get();
|
||||
fn3();
|
||||
});
|
||||
watchReactive(() => {
|
||||
rObj.get();
|
||||
fn4();
|
||||
});
|
||||
|
||||
// 第一个 a 由{b: 1} -> [] 不能够精准更新
|
||||
rObj.items.set([{ a: null, b: { c: 3 } }, { a: 3 }, 4]);
|
||||
|
||||
expect(fn).toHaveBeenCalledTimes(2);
|
||||
expect(fn1).toHaveBeenCalledTimes(2);
|
||||
// 由 { b: 1 } -> 1 是不会触发 b 精准更新
|
||||
expect(fn2).toHaveBeenCalledTimes(1);
|
||||
// 前一个属性不能精准更新,也不触发后面那个的精准更新
|
||||
expect(fn3).toHaveBeenCalledTimes(1);
|
||||
|
||||
// 父数据也会触发
|
||||
expect(fn4).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('执行基本数据数组的loop方法', () => {
|
||||
let fn = jest.fn();
|
||||
let fn1 = jest.fn();
|
||||
const rObj = reactive({
|
||||
items: [1, 2, 3, 4],
|
||||
});
|
||||
|
||||
rObj.items.forEach(rItem => {
|
||||
expect(isReactiveProxy(rItem)).toBeTruthy();
|
||||
});
|
||||
|
||||
watchReactive(() => {
|
||||
rObj.items.get();
|
||||
fn();
|
||||
});
|
||||
watchReactive(() => {
|
||||
rObj.get();
|
||||
fn1();
|
||||
});
|
||||
|
||||
rObj.items.set([1, 2, 3]);
|
||||
|
||||
expect(fn).toHaveBeenCalledTimes(2);
|
||||
|
||||
// 父数据也会触发
|
||||
expect(fn1).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,266 @@
|
|||
/*
|
||||
* Copyright (c) 2023 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.
|
||||
*/
|
||||
|
||||
import Inula, { render, createRef, useState, useReactive, useCompute } from '../../../src/index';
|
||||
|
||||
describe('测试 useReactive(原生数据)', () => {
|
||||
it('reactive.get()作为children', () => {
|
||||
let rObj;
|
||||
const ref = createRef();
|
||||
const fn = jest.fn();
|
||||
const App = () => {
|
||||
const _rObj = useReactive('1');
|
||||
rObj = _rObj;
|
||||
|
||||
fn();
|
||||
|
||||
return <div ref={ref}>{_rObj.get()}</div>;
|
||||
};
|
||||
render(<App />, container);
|
||||
expect(ref.current.innerHTML).toEqual('1');
|
||||
rObj.set('2');
|
||||
expect(ref.current.innerHTML).toEqual('2');
|
||||
expect(fn).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('reactive作为children', () => {
|
||||
let rObj;
|
||||
const ref = createRef();
|
||||
const fn = jest.fn();
|
||||
const App = () => {
|
||||
const _rObj = useReactive('1');
|
||||
rObj = _rObj;
|
||||
|
||||
fn();
|
||||
|
||||
return <div ref={ref}>{_rObj}</div>;
|
||||
};
|
||||
render(<App />, container);
|
||||
expect(ref.current.innerHTML).toEqual('1');
|
||||
rObj.set('2');
|
||||
expect(ref.current.innerHTML).toEqual('2');
|
||||
rObj.set(prev => prev + '??');
|
||||
expect(ref.current.innerHTML).toEqual('2??');
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('reactive.get()作为prop', () => {
|
||||
let rObj;
|
||||
const ref = createRef();
|
||||
const fn = jest.fn();
|
||||
const App = () => {
|
||||
const _rObj = useReactive(1);
|
||||
rObj = _rObj;
|
||||
|
||||
fn();
|
||||
|
||||
return <div ref={ref} className={_rObj.get()}></div>;
|
||||
};
|
||||
render(<App />, container);
|
||||
expect(ref.current.className).toEqual('1');
|
||||
rObj.set(2);
|
||||
expect(ref.current.className).toEqual('2');
|
||||
expect(fn).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('reactive作为prop', () => {
|
||||
let rObj;
|
||||
const ref = createRef();
|
||||
const fn = jest.fn();
|
||||
const App = () => {
|
||||
const _rObj = useReactive(1);
|
||||
rObj = _rObj;
|
||||
|
||||
fn();
|
||||
|
||||
return <div ref={ref} className={_rObj}></div>;
|
||||
};
|
||||
render(<App />, container);
|
||||
expect(ref.current.className).toEqual('1');
|
||||
rObj.set(2);
|
||||
expect(ref.current.className).toEqual('2');
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('reactive.get()传入style', () => {
|
||||
let rObj;
|
||||
const ref = createRef();
|
||||
const fn = jest.fn();
|
||||
|
||||
const App = () => {
|
||||
const _rObj = useReactive('blue');
|
||||
rObj = _rObj;
|
||||
|
||||
fn();
|
||||
|
||||
return <div ref={ref} style={{ color: _rObj.get() }}></div>;
|
||||
};
|
||||
render(<App />, container);
|
||||
const style = window.getComputedStyle(ref.current);
|
||||
expect(style.color).toEqual('blue');
|
||||
|
||||
rObj.set('red');
|
||||
expect(ref.current.style.color).toEqual('red');
|
||||
|
||||
expect(fn).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('reactive传入style', () => {
|
||||
let rObj;
|
||||
const ref = createRef();
|
||||
const fn = jest.fn();
|
||||
|
||||
const App = () => {
|
||||
const _rObj = useReactive('blue');
|
||||
rObj = _rObj;
|
||||
|
||||
fn();
|
||||
|
||||
return <div ref={ref} style={{ color: _rObj }}></div>;
|
||||
};
|
||||
render(<App />, container);
|
||||
const style = window.getComputedStyle(ref.current);
|
||||
expect(style.color).toEqual('blue');
|
||||
|
||||
rObj.set('red');
|
||||
expect(ref.current.style.color).toEqual('red');
|
||||
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('reactive传入Input value', () => {
|
||||
let rObj;
|
||||
const ref = createRef();
|
||||
const fn = jest.fn();
|
||||
|
||||
const App = () => {
|
||||
const _rObj = useReactive('blue');
|
||||
rObj = _rObj;
|
||||
|
||||
fn();
|
||||
|
||||
return <input ref={ref} value={_rObj}></input>;
|
||||
};
|
||||
render(<App />, container);
|
||||
expect(ref.current.value).toEqual('blue');
|
||||
|
||||
rObj.set('red');
|
||||
expect(ref.current.value).toEqual('red');
|
||||
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('reactive传入Textarea value', () => {
|
||||
let rObj;
|
||||
const ref = createRef();
|
||||
const fn = jest.fn();
|
||||
|
||||
const App = () => {
|
||||
const _rObj = useReactive('blue');
|
||||
rObj = _rObj;
|
||||
|
||||
fn();
|
||||
|
||||
return <textarea ref={ref} value={_rObj}></textarea>;
|
||||
};
|
||||
render(<App />, container);
|
||||
expect(ref.current.value).toEqual('blue');
|
||||
|
||||
rObj.set('red');
|
||||
expect(ref.current.value).toEqual('red');
|
||||
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('reactive父组件刷新, effect不应该重新监听', () => {
|
||||
let rObj, update;
|
||||
const ref = createRef();
|
||||
const fn = jest.fn();
|
||||
|
||||
const App = () => {
|
||||
const [_, setState] = useState({});
|
||||
|
||||
update = () => setState({});
|
||||
return <Child />;
|
||||
};
|
||||
|
||||
const Child = () => {
|
||||
const _rObj = useReactive('blue');
|
||||
rObj = _rObj;
|
||||
|
||||
fn();
|
||||
|
||||
return <div ref={ref} className={_rObj}></div>;
|
||||
};
|
||||
|
||||
render(<App />, container);
|
||||
expect(ref.current.className).toEqual('blue');
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
update();
|
||||
expect(fn).toHaveBeenCalledTimes(2);
|
||||
rObj.set('red');
|
||||
expect(fn).toHaveBeenCalledTimes(2);
|
||||
expect(ref.current.className).toEqual('red');
|
||||
});
|
||||
|
||||
it('不允许:从“原生数据”变成“对象”', () => {
|
||||
let rObj;
|
||||
const ref = createRef();
|
||||
const fn = jest.fn();
|
||||
const App = () => {
|
||||
const _rObj = useReactive('1');
|
||||
rObj = _rObj;
|
||||
|
||||
fn();
|
||||
|
||||
const cp = useCompute(() => {
|
||||
return _rObj.get() === '1' ? '1' : _rObj.data.get();
|
||||
});
|
||||
|
||||
return <div ref={ref}>{cp}</div>;
|
||||
};
|
||||
render(<App />, container);
|
||||
expect(ref.current.innerHTML).toEqual('1');
|
||||
|
||||
// 不允许:从“原生数据”变成“对象”
|
||||
expect(() => rObj.set({ data: '2' })).toThrow(Error('Not allowed Change Primitive to Object'));
|
||||
});
|
||||
|
||||
it('允许:一个reactive属性从“原生数据”变成“对象”', () => {
|
||||
let rObj;
|
||||
const ref = createRef();
|
||||
const fn = jest.fn();
|
||||
const App = () => {
|
||||
const _rObj = useReactive({
|
||||
data: '1',
|
||||
});
|
||||
rObj = _rObj;
|
||||
|
||||
fn();
|
||||
|
||||
const cp = useCompute(() => {
|
||||
return _rObj.data.get() === '1' ? '1' : _rObj.data.num.get();
|
||||
});
|
||||
|
||||
return <div ref={ref}>{cp}</div>;
|
||||
};
|
||||
render(<App />, container);
|
||||
expect(ref.current.innerHTML).toEqual('1');
|
||||
|
||||
rObj.data.set({ num: '2' });
|
||||
expect(ref.current.innerHTML).toEqual('2');
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* Copyright (c) 2023 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.
|
||||
*/
|
||||
|
||||
import Inula, { createRef, render, useReactive } from '../../../src/index';
|
||||
|
||||
describe('测试在DOM的props中使用响应式数据', () => {
|
||||
it('在class props中使用响应式数据', () => {
|
||||
let rObj;
|
||||
const ref = createRef();
|
||||
|
||||
const fn = jest.fn();
|
||||
const App = () => {
|
||||
const _rObj = useReactive({ class: 'c1', color: 'blue' });
|
||||
rObj = _rObj;
|
||||
|
||||
fn();
|
||||
|
||||
return (
|
||||
<div ref={ref} className={_rObj.class}>
|
||||
{_rObj.color}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
render(<App />, container);
|
||||
expect(ref.current.innerHTML).toEqual('blue');
|
||||
expect(ref.current.getAttribute('class')).toEqual('c1');
|
||||
|
||||
rObj.class.set('c2');
|
||||
expect(ref.current.getAttribute('class')).toEqual('c2');
|
||||
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('在style中使用响应式数据', () => {
|
||||
let rObj;
|
||||
const ref = createRef();
|
||||
|
||||
const fn = jest.fn();
|
||||
const App = () => {
|
||||
const _rObj = useReactive({ class: 'c1', color: 'blue' });
|
||||
rObj = _rObj;
|
||||
|
||||
fn();
|
||||
|
||||
return (
|
||||
<div ref={ref} style={{ color: _rObj.color }}>
|
||||
{_rObj.color}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
render(<App />, container);
|
||||
expect(ref.current.innerHTML).toEqual('blue');
|
||||
expect(ref.current.getAttribute('style')).toEqual('color: blue;');
|
||||
|
||||
rObj.color.set('red');
|
||||
expect(ref.current.getAttribute('style')).toEqual('color: red;');
|
||||
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('在input中使用响应式数据', () => {
|
||||
let rObj;
|
||||
const ref = createRef();
|
||||
const fn = jest.fn();
|
||||
|
||||
const App = () => {
|
||||
const _rObj = useReactive({ class: 'c1', color: 'blue' });
|
||||
rObj = _rObj;
|
||||
|
||||
fn();
|
||||
|
||||
return <input ref={ref} value={_rObj.color}></input>;
|
||||
};
|
||||
render(<App />, container);
|
||||
expect(ref.current.value).toEqual('blue');
|
||||
|
||||
rObj.color.set('red');
|
||||
expect(ref.current.value).toEqual('red');
|
||||
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,122 @@
|
|||
import Inula, {render, createRef, useReactive, useWatch, useCompute, For} from '../../../src/index';
|
||||
|
||||
describe('测试 watch', () => {
|
||||
it('watch 一个参数', () => {
|
||||
let rObj;
|
||||
const ref = createRef();
|
||||
const fn = jest.fn();
|
||||
const App = () => {
|
||||
const _rObj = useReactive(1);
|
||||
useWatch(() => {
|
||||
_rObj.get();
|
||||
fn();
|
||||
});
|
||||
|
||||
rObj = _rObj;
|
||||
|
||||
return <div ref={ref}>{_rObj}</div>;
|
||||
};
|
||||
|
||||
render(<App />, container);
|
||||
rObj.set('2');
|
||||
expect(fn).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('watch 2个参数', () => {
|
||||
let rObj;
|
||||
const ref = createRef();
|
||||
const fn = jest.fn();
|
||||
const App = () => {
|
||||
const _rObj = useReactive(1);
|
||||
useWatch(_rObj, () => {
|
||||
fn();
|
||||
});
|
||||
rObj = _rObj;
|
||||
|
||||
return <div ref={ref}>{_rObj}</div>;
|
||||
};
|
||||
|
||||
render(<App />, container);
|
||||
rObj.set('2');
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('watch 2个参数,第一个是函数', () => {
|
||||
let rObj;
|
||||
const ref = createRef();
|
||||
const fn = jest.fn();
|
||||
const App = () => {
|
||||
const _rObj = useReactive(1);
|
||||
useWatch(
|
||||
() => {
|
||||
_rObj.get();
|
||||
},
|
||||
() => {
|
||||
fn();
|
||||
}
|
||||
);
|
||||
rObj = _rObj;
|
||||
|
||||
return <div ref={ref}>{_rObj}</div>;
|
||||
};
|
||||
|
||||
render(<App />, container);
|
||||
rObj.set('2');
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('响应式数据的孩子变更,watch也应该被触发', () => {
|
||||
let rObj;
|
||||
let ref = createRef();
|
||||
let fn = jest.fn();
|
||||
let appFn = jest.fn();
|
||||
let itemFn = jest.fn();
|
||||
|
||||
const App = () => {
|
||||
const _rObj = useReactive([
|
||||
{ id: 'id-1', name: 'p1' },
|
||||
{ id: 'id-2', name: 'p2' },
|
||||
{ id: 'id-3', name: 'p3' },
|
||||
]);
|
||||
rObj = _rObj;
|
||||
|
||||
useWatch(() => {
|
||||
_rObj.get();
|
||||
fn();
|
||||
});
|
||||
|
||||
appFn();
|
||||
|
||||
return (
|
||||
<div ref={ref}>
|
||||
<For each={_rObj}>
|
||||
{item => {
|
||||
itemFn();
|
||||
return (
|
||||
<li id={item.id} key={item.id}>
|
||||
{item.name}
|
||||
</li>
|
||||
);
|
||||
}}
|
||||
</For>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
render(<App />, container);
|
||||
let items = container.querySelectorAll('li');
|
||||
expect(items.length).toEqual(3);
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
|
||||
rObj.push({ id: 'id-4', name: 'p4' });
|
||||
|
||||
items = container.querySelectorAll('li');
|
||||
expect(items.length).toEqual(4);
|
||||
expect(fn).toHaveBeenCalledTimes(2);
|
||||
|
||||
rObj[1].set({ id: 'id-2', name: 'p222' });
|
||||
let li = container.querySelector('#id-2');
|
||||
expect(li.innerHTML).toEqual('p222');
|
||||
expect(fn).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
});
|
|
@ -23,7 +23,7 @@ import execute from 'rollup-plugin-execute';
|
|||
import { terser } from 'rollup-plugin-terser';
|
||||
import { version as inulaVersion } from '../../package.json';
|
||||
|
||||
const extensions = ['.js', '.ts'];
|
||||
const extensions = ['.js', '.ts', '.tsx'];
|
||||
|
||||
const libDir = path.join(__dirname, '../..');
|
||||
const rootDir = path.join(__dirname, '../..');
|
||||
|
|
|
@ -188,7 +188,7 @@ export function submitDomUpdate(tag: string, vNode: VNode) {
|
|||
}
|
||||
|
||||
export function clearText(dom: Element): void {
|
||||
dom.innerHTML = '';
|
||||
dom.textContent = '';
|
||||
}
|
||||
|
||||
// 添加child元素
|
||||
|
|
|
@ -19,6 +19,14 @@ import { setStyles } from './StyleHandler';
|
|||
import { lazyDelegateOnRoot, listenNonDelegatedEvent } from '../../event/EventBinding';
|
||||
import { isEventProp } from '../validators/ValidateProps';
|
||||
import { getCurrentRoot } from '../../renderer/RootStack';
|
||||
import { getValue, isReactiveObj} from '../../reactive/Utils';
|
||||
import { handleReactiveProp } from '../../reactive/RContextCreator';
|
||||
import { ReactiveProxy } from '../../reactive/types';
|
||||
|
||||
export function unwrapVal(propName: string, propVal: any, dom: Element, styleName?: string) {
|
||||
const rawVal: any = handleReactiveProp(dom, propName, propVal, styleName);
|
||||
return rawVal;
|
||||
}
|
||||
|
||||
// 初始化DOM属性和更新 DOM 属性
|
||||
export function setDomProps(dom: Element, props: Record<string, any>, isNativeTag: boolean, isInit: boolean): void {
|
||||
|
@ -43,8 +51,8 @@ export function setDomProps(dom: Element, props: Record<string, any>, isNativeTa
|
|||
} else if (propName === 'children') {
|
||||
// 只处理纯文本子节点,其他children在VNode树中处理
|
||||
const type = typeof propVal;
|
||||
if (type === 'string' || type === 'number') {
|
||||
dom.textContent = propVal;
|
||||
if (type === 'string' || type === 'number' || isReactiveObj(propVal)) {
|
||||
dom.textContent = unwrapVal(propName, propVal, dom);
|
||||
}
|
||||
} else if (propName === 'dangerouslySetInnerHTML') {
|
||||
dom.innerHTML = propVal.__html;
|
||||
|
@ -55,9 +63,9 @@ export function setDomProps(dom: Element, props: Record<string, any>, isNativeTa
|
|||
}
|
||||
|
||||
// 找出两个 DOM 属性的差别,生成需要更新的属性集合
|
||||
export function compareProps(oldProps: Record<string, any>, newProps: Record<string, any>): Record<string, any> {
|
||||
export function compareProps(oldProps: Record<string, any>, newProps: Record<string, any>, dom: Element): Record<string, any> {
|
||||
let updatesForStyle = {};
|
||||
const toUpdateProps = {};
|
||||
const toUpdateProps: Record<string | number, any> = {};
|
||||
const keysOfOldProps = Object.keys(oldProps);
|
||||
const keysOfNewProps = Object.keys(newProps);
|
||||
|
||||
|
@ -103,11 +111,14 @@ export function compareProps(oldProps: Record<string, any>, newProps: Record<str
|
|||
for (let i = 0; i < keysOfNewProps.length; i++) {
|
||||
propName = keysOfNewProps[i];
|
||||
newPropValue = newProps[propName];
|
||||
oldPropValue = oldProps !== null && oldProps !== undefined ? oldProps[propName] : null;
|
||||
oldPropValue = oldProps != null ? oldProps[propName] : null;
|
||||
|
||||
if (
|
||||
newPropValue === oldPropValue ||
|
||||
((newPropValue === null || newPropValue === undefined) && (oldPropValue === null || oldPropValue === undefined))
|
||||
(newPropValue == null && oldPropValue == null) ||
|
||||
(isReactiveObj(newPropValue) &&
|
||||
isReactiveObj(oldPropValue) &&
|
||||
getValue(newPropValue) === getValue(oldPropValue))
|
||||
) {
|
||||
// 新旧属性值未发生变化,或者新旧属性皆为空值,不需要进行处理
|
||||
continue;
|
||||
|
@ -143,31 +154,49 @@ export function compareProps(oldProps: Record<string, any>, newProps: Record<str
|
|||
} else if (propName === 'dangerouslySetInnerHTML') {
|
||||
newHTML = newPropValue ? newPropValue.__html : undefined;
|
||||
oldHTML = oldPropValue ? oldPropValue.__html : undefined;
|
||||
if (newHTML !== null && newHTML !== undefined) {
|
||||
if (newHTML != null) {
|
||||
if (oldHTML !== newHTML) {
|
||||
toUpdateProps[propName] = newPropValue;
|
||||
appendToUpdateProps(toUpdateProps, propName, newPropValue, dom);
|
||||
}
|
||||
}
|
||||
} else if (propName === 'children') {
|
||||
if (typeof newPropValue === 'string' || typeof newPropValue === 'number') {
|
||||
toUpdateProps[propName] = String(newPropValue);
|
||||
if (typeof newPropValue === 'string' || typeof newPropValue === 'number' || isReactiveObj(newPropValue)) {
|
||||
appendToUpdateProps<string | number | ReactiveProxy<any>, string>(
|
||||
toUpdateProps,
|
||||
propName,
|
||||
newPropValue,
|
||||
dom,
|
||||
String
|
||||
);
|
||||
}
|
||||
} else if (isEventProp(propName)) {
|
||||
const currentRoot = getCurrentRoot();
|
||||
if (!allDelegatedInulaEvents.has(propName)) {
|
||||
toUpdateProps[propName] = newPropValue;
|
||||
appendToUpdateProps(toUpdateProps, propName, newPropValue, dom);
|
||||
} else if (currentRoot && !currentRoot.delegatedEvents.has(propName)) {
|
||||
lazyDelegateOnRoot(currentRoot, propName);
|
||||
}
|
||||
} else {
|
||||
toUpdateProps[propName] = newPropValue;
|
||||
appendToUpdateProps(toUpdateProps, propName, newPropValue, dom);
|
||||
}
|
||||
}
|
||||
|
||||
// 处理style
|
||||
if (Object.keys(updatesForStyle).length > 0) {
|
||||
toUpdateProps['style'] = updatesForStyle;
|
||||
appendToUpdateProps(toUpdateProps, 'style', updatesForStyle, dom);
|
||||
}
|
||||
|
||||
return toUpdateProps;
|
||||
}
|
||||
|
||||
function appendToUpdateProps<V, R>(
|
||||
toUpdateProps: Record<string | number, any>,
|
||||
propName: string,
|
||||
propVal: V,
|
||||
dom: Element,
|
||||
formatter?: (value: V) => R
|
||||
) {
|
||||
const rawVal: any = handleReactiveProp(dom, propName, propVal);
|
||||
|
||||
toUpdateProps[propName] = formatter ? formatter(rawVal) : rawVal;
|
||||
}
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
* See the Mulan PSL v2 for more details.
|
||||
*/
|
||||
|
||||
import { unwrapVal } from './DOMPropertiesHandler';
|
||||
|
||||
/**
|
||||
* 不需要加长度单位的 css 属性
|
||||
*/
|
||||
|
@ -85,12 +87,14 @@ export function setStyles(dom, styles) {
|
|||
const style = dom.style;
|
||||
Object.keys(styles).forEach(name => {
|
||||
const styleVal = styles[name];
|
||||
const val = unwrapVal('style', styleVal, dom, name);
|
||||
|
||||
// 以--开始的样式直接设置即可
|
||||
if (name.indexOf('--') === 0) {
|
||||
style.setProperty(name, styleVal);
|
||||
style.setProperty(name, val);
|
||||
} else {
|
||||
// 使用这种赋值方式,浏览器可以将'WebkitLineClamp', 'backgroundColor'分别识别为'-webkit-line-clamp'和'backgroud-color'
|
||||
style[name] = adjustStyleValue(name, styleVal);
|
||||
style[name] = adjustStyleValue(name, val);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ import { isInvalidValue } from '../validators/ValidateProps';
|
|||
import { getNamespaceCtx } from '../../renderer/ContextSaver';
|
||||
import { NSS } from '../utils/DomCreator';
|
||||
import { getDomTag } from '../utils/Common';
|
||||
import { unwrapVal } from './DOMPropertiesHandler';
|
||||
|
||||
// 不需要装换的svg属性集合
|
||||
const svgHumpAttr = new Set();
|
||||
|
@ -120,7 +121,7 @@ export function updateCommonProp(dom: Element, attrName: string, value: any, isN
|
|||
if (value === null) {
|
||||
dom.removeAttribute(attrName);
|
||||
} else {
|
||||
dom.setAttribute(attrName, String(value));
|
||||
dom.setAttribute(attrName, String(unwrapVal(attrName, value, dom)));
|
||||
}
|
||||
} else if (['checked', 'multiple', 'muted', 'selected'].includes(propDetails.attrName)) {
|
||||
if (value === null) {
|
||||
|
@ -141,7 +142,7 @@ export function updateCommonProp(dom: Element, attrName: string, value: any, isN
|
|||
// 即可以用作标志又可以是属性值的属性
|
||||
attributeValue = '';
|
||||
} else {
|
||||
attributeValue = String(value);
|
||||
attributeValue = String(unwrapVal(attrName, value, dom));
|
||||
}
|
||||
|
||||
if (attrNS) {
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
|
||||
import { updateCommonProp } from '../DOMPropertiesHandler/UpdateCommonProp';
|
||||
import { Props } from '../utils/Interface';
|
||||
import { getValue } from '../../reactive/Utils';
|
||||
import { handleReactiveProp } from '../../reactive/RContextCreator';
|
||||
|
||||
function getInitValue(dom: HTMLInputElement, props: Props) {
|
||||
const { value, defaultValue, checked, defaultChecked } = props;
|
||||
|
@ -45,10 +47,12 @@ export function getInputPropsWithoutValue(dom: HTMLInputElement, props: Props) {
|
|||
export function updateInputValue(dom: HTMLInputElement, props: Props) {
|
||||
const { value, checked } = props;
|
||||
|
||||
if (value !== undefined) {
|
||||
const val = getValue(value);
|
||||
|
||||
if (val !== undefined) {
|
||||
// 处理 dom.value 逻辑
|
||||
if (dom.value !== String(value)) {
|
||||
dom.value = String(value);
|
||||
if (dom.value !== String(val)) {
|
||||
dom.value = String(val);
|
||||
}
|
||||
} else if (checked !== undefined) {
|
||||
updateCommonProp(dom, 'checked', checked, true);
|
||||
|
@ -62,7 +66,9 @@ export function setInitInputValue(dom: HTMLInputElement, props: Props) {
|
|||
|
||||
if (value !== undefined || defaultValue !== undefined) {
|
||||
// value 的使用优先级 value 属性 > defaultValue 属性 > 空字符串
|
||||
const initValueStr = String(initValue);
|
||||
const initValueStr = getValue(initValue);
|
||||
|
||||
handleReactiveProp(dom, 'value', value);
|
||||
|
||||
dom.value = initValueStr;
|
||||
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
*/
|
||||
|
||||
import { Props } from '../utils/Interface';
|
||||
import { getValue } from '../../reactive/Utils';
|
||||
import { handleReactiveProp } from '../../reactive/RContextCreator';
|
||||
|
||||
// 值的优先级 value > children > defaultValue
|
||||
function getInitValue(props: Props) {
|
||||
|
@ -48,7 +50,9 @@ export function updateTextareaValue(dom: HTMLTextAreaElement, props: Props, isIn
|
|||
if (isInit) {
|
||||
const initValue = getInitValue(props);
|
||||
if (initValue !== '') {
|
||||
dom.value = initValue;
|
||||
dom.value = getValue(initValue);
|
||||
|
||||
handleReactiveProp(dom, 'value', props.value);
|
||||
}
|
||||
} else {
|
||||
// 获取当前节点的 value 值
|
||||
|
|
|
@ -18,6 +18,7 @@ import { getProcessingClassVNode } from '../renderer/GlobalVar';
|
|||
import { Source } from '../renderer/Types';
|
||||
import { BELONG_CLASS_VNODE_KEY } from '../renderer/vnode/VNode';
|
||||
import { InulaElement, KVObject } from '../types';
|
||||
import { isReactiveObj } from '../reactive/Utils';
|
||||
|
||||
/**
|
||||
* vtype 节点的类型,这里固定是element
|
||||
|
@ -75,7 +76,17 @@ const keyArray = ['key', 'ref', '__source', '__self'];
|
|||
|
||||
function buildElement(isClone, type, setting, children) {
|
||||
// setting中的值优先级最高,clone情况下从 type 中取值,创建情况下直接赋值为 null
|
||||
const key = setting && setting.key !== undefined ? String(setting.key) : isClone ? type.key : null;
|
||||
let key;
|
||||
if (setting && setting.key !== undefined) {
|
||||
if (isReactiveObj(setting.key)) {
|
||||
key = setting.key;
|
||||
} else {
|
||||
key = String(setting.key);
|
||||
}
|
||||
} else {
|
||||
key = isClone ? type.key : null;
|
||||
}
|
||||
|
||||
const ref = setting && setting.ref !== undefined ? setting.ref : isClone ? type.ref : null;
|
||||
const props = isClone ? { ...type.props } : {};
|
||||
let vNode = isClone ? type[BELONG_CLASS_VNODE_KEY] : getProcessingClassVNode();
|
||||
|
|
|
@ -43,6 +43,11 @@ import {
|
|||
useRef,
|
||||
useState,
|
||||
useDebugValue,
|
||||
useAtom,
|
||||
useCompute,
|
||||
useComputed,
|
||||
useReactive,
|
||||
useWatch,
|
||||
} from './renderer/hooks/HookExternal';
|
||||
import {
|
||||
isContextProvider,
|
||||
|
@ -72,6 +77,15 @@ import {
|
|||
import { syncUpdates as flushSync } from './renderer/TreeBuilder';
|
||||
import { toRaw } from './inulax/proxy/ProxyHandler';
|
||||
|
||||
import { For } from './reactive/components/For';
|
||||
import { Show } from './reactive/components/Show';
|
||||
import { Switch } from './reactive/components/Switch';
|
||||
import { RText } from './reactive/components/RText';
|
||||
import { reactive } from './reactive/Reactive';
|
||||
import { computed } from './reactive/Computed';
|
||||
import { isReactiveObj } from './reactive/Utils';
|
||||
import { watch as watchReactive } from './reactive/Watch';
|
||||
|
||||
const Inula = {
|
||||
Children,
|
||||
createRef,
|
||||
|
@ -122,6 +136,20 @@ const Inula = {
|
|||
Profiler,
|
||||
StrictMode,
|
||||
Suspense,
|
||||
// reactive
|
||||
reactive,
|
||||
computed,
|
||||
watchReactive,
|
||||
isReactiveObj,
|
||||
For,
|
||||
Show,
|
||||
Switch,
|
||||
RText,
|
||||
useAtom,
|
||||
useReactive,
|
||||
useCompute,
|
||||
useComputed,
|
||||
useWatch,
|
||||
};
|
||||
|
||||
export const version = __VERSION__;
|
||||
|
@ -178,6 +206,20 @@ export {
|
|||
Profiler,
|
||||
StrictMode,
|
||||
Suspense,
|
||||
// reactive
|
||||
reactive,
|
||||
computed,
|
||||
watchReactive,
|
||||
isReactiveObj,
|
||||
For,
|
||||
Show,
|
||||
Switch,
|
||||
RText,
|
||||
useAtom,
|
||||
useReactive,
|
||||
useCompute,
|
||||
useComputed,
|
||||
useWatch,
|
||||
};
|
||||
|
||||
export * from './types';
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* Copyright (c) 2023 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.
|
||||
*/
|
||||
|
||||
import { RContextSet, triggerRContexts } from './RContext';
|
||||
import { getRNodeVal, trackReactiveData } from './RNode';
|
||||
import { isFunction, isObject } from './Utils';
|
||||
import {AtomNode, RNode, Root, RootRNode} from './types';
|
||||
|
||||
export interface Atom<T = any> extends AtomNode<T> {
|
||||
root: Root<T>;
|
||||
parent: RNode;
|
||||
parentKey: string | symbol | null;
|
||||
usedRContexts?: RContextSet;
|
||||
}
|
||||
|
||||
const atomKey = Symbol('atomAccessKey');
|
||||
export const atomSymbol = Symbol('ReactiveAtom');
|
||||
|
||||
// 对原始数据做响应式的时候使用
|
||||
export function Atom<T>(value: T, parent: RNode | null = null, parentKey: string | symbol | null = null) {
|
||||
if (parent === null && parentKey === null) {
|
||||
this.parent = {
|
||||
parent: null,
|
||||
parentKey: null,
|
||||
root: {
|
||||
$: { [atomKey]: value },
|
||||
},
|
||||
type: atomSymbol,
|
||||
} as RootRNode<T>;
|
||||
this.parentKey = atomKey;
|
||||
this.root = this.parent.root;
|
||||
} else {
|
||||
this.parent = parent;
|
||||
this.parentKey = parentKey;
|
||||
this.root = this.parent.root;
|
||||
}
|
||||
}
|
||||
|
||||
Atom.prototype.get = function <T>(): T {
|
||||
trackReactiveData(this);
|
||||
return this.read();
|
||||
};
|
||||
|
||||
Atom.prototype.set = function <T>(value: T | ((prev: T) => T)) {
|
||||
// 修改Atom值与父元素值
|
||||
const prevParent = getRNodeVal(this.parent);
|
||||
const prevValue = prevParent[this.parentKey];
|
||||
|
||||
const newValue = isFunction(value) ? value(prevValue) : value;
|
||||
|
||||
// 如果要改为非原始对象切父元素类型不为atomSymbol,说明该对象是Atom对象,不允许:从“原生数据”变成“对象”
|
||||
if (this.parent.type === atomSymbol && isObject(newValue)) {
|
||||
throw Error('Not allowed Change Primitive to Object');
|
||||
}
|
||||
|
||||
// 1) 修改Node底层原始值
|
||||
prevParent[this.parentKey] = newValue;
|
||||
|
||||
// 2) 触发使用到它的RContexts
|
||||
triggerRContexts(this, prevValue, newValue, false);
|
||||
return this;
|
||||
};
|
||||
|
||||
Atom.prototype.read = function <T>(): T {
|
||||
return getRNodeVal(this);
|
||||
};
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* Copyright (c) 2023 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.
|
||||
*/
|
||||
|
||||
import { RContextCallback, RContextParam, Reactive } from './types';
|
||||
import { VNode } from '../renderer/Types';
|
||||
import { updateShouldUpdateOfTree } from '../renderer/vnode/VNodeShouldUpdate';
|
||||
|
||||
export interface BatchItem {
|
||||
callback: RContextCallback;
|
||||
params: RContextParam;
|
||||
reactive: Reactive;
|
||||
}
|
||||
|
||||
let batchCount = 0;
|
||||
let _batch: BatchItem[] = [];
|
||||
let _batchMap = new Map();
|
||||
|
||||
export function addToBatch(item: BatchItem) {
|
||||
if (batchCount > 0) {
|
||||
const existing = _batchMap.get(item.callback);
|
||||
|
||||
if (existing) {
|
||||
// const params = existing.params;
|
||||
// params.value = item.params.value;
|
||||
} else {
|
||||
_batch.push(item);
|
||||
_batchMap.set(item.callback, true);
|
||||
}
|
||||
} else {
|
||||
item.callback(item.params, item.reactive);
|
||||
}
|
||||
}
|
||||
|
||||
export function startBatch() {
|
||||
batchCount++;
|
||||
}
|
||||
|
||||
export function endBatch() {
|
||||
batchCount--;
|
||||
|
||||
if (batchCount <= 0) {
|
||||
batchCount = 0;
|
||||
const batch = _batch;
|
||||
_batch = [];
|
||||
_batchMap = new Map();
|
||||
|
||||
const toUpdateVNodes: VNode[] = [];
|
||||
const toUpdateVNodeItems: BatchItem[] = [];
|
||||
|
||||
for (let i = 0; i < batch.length; i++) {
|
||||
const b = batch[i];
|
||||
|
||||
// 如果要刷新的是组件(函数组件或类组件)
|
||||
if (b.params?.vNode) {
|
||||
// 设置vNode为shouldUpdate
|
||||
updateShouldUpdateOfTree(b.params.vNode);
|
||||
|
||||
b.params.vNode.isStoreChange = true;
|
||||
|
||||
toUpdateVNodes.push(b.params.vNode);
|
||||
toUpdateVNodeItems.push(b);
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < batch.length; i++) {
|
||||
const b = batch[i];
|
||||
const { callback, reactive } = b;
|
||||
|
||||
if (!b.params?.vNode) {
|
||||
callback(b.params, reactive);
|
||||
}
|
||||
}
|
||||
|
||||
if (toUpdateVNodes.length) {
|
||||
const b = toUpdateVNodeItems[0];
|
||||
const { callback, reactive } = b;
|
||||
callback(b.params, reactive);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright (c) 2023 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.
|
||||
*/
|
||||
|
||||
import { Computed } from './types';
|
||||
import { createReactiveObj, setRNodeVal } from './RNode';
|
||||
import { calculateReactive, getRNode, isPromise } from './Utils';
|
||||
import { RContext } from './RContext';
|
||||
|
||||
function computed<T = any>(fn: () => T | Promise<T>): Computed<T> {
|
||||
const setComputed = (value: any, trigger: boolean) => {
|
||||
root.readOnly = false;
|
||||
const raw = getRNode(rObj);
|
||||
setRNodeVal(raw, value, trigger);
|
||||
root.readOnly = true;
|
||||
};
|
||||
|
||||
// 依赖的响应式数据变化时调用
|
||||
const update = (trigger: boolean) => {
|
||||
const value = calculateReactive(fn);
|
||||
if (isPromise(value)) {
|
||||
value.then(val => setComputed(val, trigger));
|
||||
} else {
|
||||
setComputed(value, trigger);
|
||||
}
|
||||
};
|
||||
|
||||
const rContext = new RContext(() => update(true));
|
||||
const end = rContext.start();
|
||||
// 首次更新不触发usedRContexts
|
||||
const value = calculateReactive(fn);
|
||||
end();
|
||||
|
||||
const rObj = createReactiveObj(value);
|
||||
|
||||
const rawNode = getRNode(rObj);
|
||||
const root = rawNode.root;
|
||||
// 默认readOnly为true
|
||||
root.readOnly = true;
|
||||
|
||||
return rObj as Computed<T>;
|
||||
}
|
||||
|
||||
export { computed };
|
|
@ -0,0 +1,199 @@
|
|||
/*
|
||||
* Copyright (c) 2023 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.
|
||||
*/
|
||||
|
||||
export enum Operation {
|
||||
// 数组长度不同
|
||||
Nop = 0,
|
||||
Insert = 1,
|
||||
Delete = 2,
|
||||
// 数组长度相同
|
||||
Update = 3,
|
||||
Exchange = 4,
|
||||
}
|
||||
|
||||
export interface Diff {
|
||||
action: Operation;
|
||||
index: number;
|
||||
}
|
||||
|
||||
export interface DiffOperator {
|
||||
isOnlyNop: boolean;
|
||||
opts: Diff[];
|
||||
}
|
||||
|
||||
function longestCommonPrefix<T>(arr1: T[], arr2: T[]): number {
|
||||
if (!arr1.length || !arr2.length || arr1[0] !== arr1[0]) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let low = 0;
|
||||
let start = 0;
|
||||
// 最短数组的长度
|
||||
let high = Math.min(arr1.length, arr2.length);
|
||||
while (low <= high) {
|
||||
const mid = (high + low) >> 1;
|
||||
if (isArrayEqual(arr1, arr2, start, mid)) {
|
||||
low = mid + 1;
|
||||
start = mid;
|
||||
} else {
|
||||
high = mid - 1;
|
||||
}
|
||||
}
|
||||
return low - 1;
|
||||
}
|
||||
|
||||
function isArrayEqual<T>(str1: T[], str2: T[], start: number, end: number): boolean {
|
||||
for (let j = start; j < end; j++) {
|
||||
if (str1[j] !== str2[j]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param origin 原始数组
|
||||
* @param target 目标数组
|
||||
* @returns 返回一个 Diff 数组,表示从原始数组转换为目标数组需要进行的操作
|
||||
*/
|
||||
export function arrayDiff<T>(origin: T[], target: T[]): DiffOperator {
|
||||
// 使用二分查找计算共同前缀与后缀
|
||||
const prefixLen = longestCommonPrefix(origin, target);
|
||||
const suffixLen = longestCommonPrefix([...origin].reverse(), [...target].reverse());
|
||||
// 删除原数组与目标数组的共同前缀与后缀
|
||||
const optimizedOrigin = origin.slice(prefixLen, origin.length - suffixLen);
|
||||
const optimizedTarget = target.slice(prefixLen, target.length - suffixLen);
|
||||
|
||||
const originLen = optimizedOrigin.length;
|
||||
const targetLen = optimizedTarget.length;
|
||||
|
||||
const dp: number[][] = Array.from(Array(originLen + 1), () => {
|
||||
return Array(targetLen + 1).fill(0);
|
||||
});
|
||||
const pathMatrix: Operation[][] = Array.from(Array(originLen + 1), () => {
|
||||
return Array(targetLen + 1).fill('');
|
||||
});
|
||||
|
||||
let diffs: Diff[] = [];
|
||||
|
||||
// 计算最长公共子序列
|
||||
for (let i = 1; i < originLen + 1; i++) {
|
||||
for (let j = 1; j < targetLen + 1; j++) {
|
||||
if (optimizedOrigin[i - 1] === optimizedTarget[j - 1]) {
|
||||
dp[i][j] = dp[i - 1][j - 1] + 1;
|
||||
// 如果相等,则表示不需要进行任何操作
|
||||
pathMatrix[i][j] = Operation.Nop;
|
||||
} else if (dp[i - 1][j] > dp[i][j - 1]) {
|
||||
dp[i][j] = dp[i - 1][j];
|
||||
// 如果不相等,则需要进行删除操作
|
||||
pathMatrix[i][j] = Operation.Delete;
|
||||
} else {
|
||||
dp[i][j] = dp[i][j - 1];
|
||||
// 如果不相等,则需要进行插入操作
|
||||
pathMatrix[i][j] = Operation.Insert;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let hasDelete = false;
|
||||
let hasInsert = false;
|
||||
|
||||
// 计算操作序列
|
||||
function diff(oLen: number, tLen: number) {
|
||||
const stack: Record<string, number>[] = [{ i: oLen, j: tLen }];
|
||||
|
||||
while (stack.length > 0) {
|
||||
const obj = stack.pop();
|
||||
const { i, j } = obj!;
|
||||
if (i === 0 || j === 0) {
|
||||
if (i !== 0) {
|
||||
diffs.unshift(
|
||||
...optimizedOrigin.slice(0, i).map((item, idx) => ({
|
||||
action: Operation.Delete,
|
||||
index: idx,
|
||||
}))
|
||||
);
|
||||
hasDelete = true;
|
||||
}
|
||||
if (j !== 0) {
|
||||
diffs.unshift(
|
||||
...optimizedTarget.slice(0, j).map((item, idx) => ({
|
||||
action: Operation.Insert,
|
||||
index: idx,
|
||||
}))
|
||||
);
|
||||
hasInsert = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (pathMatrix[i][j] === Operation.Nop) {
|
||||
stack.push({ i: i - 1, j: j - 1 });
|
||||
// 如果不需要进行任何操作,则表示是公共元素,将其添加到 diffs 中
|
||||
diffs.unshift({ action: Operation.Nop, index: i - 1 });
|
||||
} else if (pathMatrix[i][j] === Operation.Delete) {
|
||||
stack.push({ i: i - 1, j });
|
||||
// 如果需要进行删除操作,则将其添加到 diffs 中
|
||||
diffs.unshift({ action: Operation.Delete, index: i - 1 });
|
||||
hasDelete = true;
|
||||
} else if (pathMatrix[i][j] === Operation.Insert) {
|
||||
stack.push({ i, j: j - 1 });
|
||||
// 如果需要进行插入操作,则将其添加到 diffs 中
|
||||
diffs.unshift({ action: Operation.Insert, index: j - 1 });
|
||||
hasInsert = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 计算操作序列
|
||||
diff(originLen, targetLen);
|
||||
|
||||
diffs.map(i => (i.index += prefixLen));
|
||||
|
||||
const prefixOpts = Array.from(Array(prefixLen), (_, index) => index).map(idx => ({
|
||||
action: Operation.Nop,
|
||||
index: idx,
|
||||
}));
|
||||
|
||||
const suffixOpts = Array.from(Array(suffixLen), (_, index) => index).map(idx => ({
|
||||
action: Operation.Nop,
|
||||
index: origin.length - suffixLen + idx,
|
||||
}));
|
||||
|
||||
diffs = prefixOpts.concat(diffs, suffixOpts);
|
||||
|
||||
return {
|
||||
isOnlyNop: !hasDelete && !hasInsert,
|
||||
opts: diffs,
|
||||
};
|
||||
}
|
||||
|
||||
// export function sameLenArrayDiff<T>(origin: T[], target: T[]): Diff[] {
|
||||
// // 只要id相同就认为是同一个对象
|
||||
// const diff: Diff[] = [];
|
||||
// const hasId = origin.every(i => getIDField(i));
|
||||
// const arrayLength = origin.length;
|
||||
// if (hasId && arrayLength > 0) {
|
||||
// const idField = getIDField(origin[0])!;
|
||||
// for (let j = 0; j < target.length; j++) {
|
||||
// if (origin[j][idField] === target[j][idField]) {
|
||||
// diff.push({ action: Operation.Nop, index: j });
|
||||
// } else {
|
||||
// diff.push({ action: Operation.Update, index: j });
|
||||
// }
|
||||
// }
|
||||
// return diff;
|
||||
// }
|
||||
// return arrayDiff(origin, target);
|
||||
// }
|
|
@ -0,0 +1,355 @@
|
|||
/*
|
||||
* Copyright (c) 2023 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.
|
||||
*/
|
||||
|
||||
import { isArray } from '../inulax/CommonUtils';
|
||||
import { isObject, isPrimitive } from './Utils';
|
||||
import { arrayDiff, DiffOperator, Operation } from './DiffUtils';
|
||||
import { ArrayState, RContextCallback, RContextParam, Reactive, RNode } from './types';
|
||||
import { getOrCreateChildNode } from './RNode';
|
||||
import {addToBatch, startBatch, endBatch, BatchItem} from './Batch';
|
||||
|
||||
export let currentDependent: RContext | null = null;
|
||||
|
||||
const reactiveContextStack: RContext[] = [];
|
||||
export type RContextSet = Set<RContext>;
|
||||
|
||||
/**
|
||||
* 响应式上下文,有4种情况:1、组件级。2、Block级。3、watch/computed。4、DOM级。
|
||||
*/
|
||||
export class RContext {
|
||||
callback: RContextCallback;
|
||||
|
||||
// 记录该Context使用时的参数
|
||||
params: RContextParam;
|
||||
|
||||
// 记录该RContext中使用到的Reactive中的RContextSet
|
||||
reactiveDependents: Set<RContextSet> | null = null;
|
||||
|
||||
constructor(callback: RContextCallback, params: RContextParam = {}) {
|
||||
this.callback = callback;
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
setParam(params: RContextParam) {
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
start() {
|
||||
cleanupRContext(this);
|
||||
currentDependent = this;
|
||||
reactiveContextStack.push(this);
|
||||
|
||||
return endEffect;
|
||||
}
|
||||
}
|
||||
|
||||
function endEffect() {
|
||||
reactiveContextStack.pop();
|
||||
currentDependent = reactiveContextStack[reactiveContextStack.length - 1] ?? null;
|
||||
}
|
||||
|
||||
// 清除 RContext和响应式数据的绑定,双向清除
|
||||
export function cleanupRContext(rContext: RContext) {
|
||||
if (rContext.reactiveDependents !== null) {
|
||||
for (const usedRContexts of rContext.reactiveDependents) {
|
||||
usedRContexts.delete(rContext);
|
||||
}
|
||||
|
||||
rContext.reactiveDependents.clear();
|
||||
rContext.reactiveDependents = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 绑定RContext和响应式数据,双向绑定
|
||||
export function bindReactiveWithContext(reactive: Reactive, rContext: RContext) {
|
||||
if (reactive.usedRContexts === undefined) {
|
||||
reactive.usedRContexts = new Set<RContext>();
|
||||
}
|
||||
reactive.usedRContexts.add(rContext);
|
||||
|
||||
if (rContext.reactiveDependents === null) {
|
||||
rContext.reactiveDependents = new Set<RContextSet>();
|
||||
}
|
||||
rContext.reactiveDependents.add(reactive.usedRContexts);
|
||||
}
|
||||
|
||||
// 递归触发依赖这reactive数据的所有RContext
|
||||
export function triggerRContexts(reactive: Reactive, prevValue: any, value: any, isFromArrModify?: boolean) {
|
||||
const isObj = isObject(value);
|
||||
const isPrevObj = isObject(prevValue);
|
||||
|
||||
startBatch();
|
||||
|
||||
if (isObj && isPrevObj) {
|
||||
triggerChildrenContexts(reactive as RNode, value, prevValue, isFromArrModify);
|
||||
}
|
||||
|
||||
callRContexts(reactive);
|
||||
|
||||
// 触发父数据的RContext,不希望触发组件刷新(只触发computed和watch)
|
||||
triggerParents(reactive.parent);
|
||||
|
||||
endBatch();
|
||||
}
|
||||
|
||||
function triggerParents(reactive: Reactive | null) {
|
||||
if (reactive) {
|
||||
// 在触发父数据的时候,不希望触发组件刷新(只触发computed和watch)
|
||||
callRContexts(reactive, true);
|
||||
|
||||
triggerParents(reactive.parent);
|
||||
}
|
||||
}
|
||||
|
||||
// 当value和prevValue都是对象或数组时,才触发
|
||||
function triggerChildrenContexts(rNode: RNode, value: any, prevValue: any, isFromArrModify?: boolean): boolean {
|
||||
// 可以精准更新
|
||||
let canPreciseUpdate = true;
|
||||
|
||||
const isArr = isArray(value);
|
||||
const isPrevArr = isArray(prevValue);
|
||||
|
||||
// 1、变化来自数组的Modify方法(某些行可能完全不变)
|
||||
if (isFromArrModify) {
|
||||
// 获取数组间差异,RNode只能增删不能修改,修改会导致Effect不会随数据的位置变化
|
||||
const diffOperator = arrayDiff(prevValue, value);
|
||||
const states: ArrayState[] = [];
|
||||
|
||||
let childIndex = 0;
|
||||
|
||||
for (const opt of diffOperator.opts) {
|
||||
switch (opt.action) {
|
||||
// 从已有RNode中取值
|
||||
case Operation.Nop: {
|
||||
const childRNode = rNode.children?.get(String(opt.index));
|
||||
|
||||
// children没有使用时,可以为undefined或没有该child
|
||||
if (childRNode !== undefined) {
|
||||
childRNode.parentKey = String(childIndex);
|
||||
states.push(ArrayState.Fresh);
|
||||
childIndex++;
|
||||
|
||||
// 删除旧的,重设新值。处理场景:元素还在,但是在数组中的位置变化了。
|
||||
rNode.children?.delete(String(opt.index));
|
||||
rNode.children?.set(childRNode.parentKey, childRNode);
|
||||
}
|
||||
break;
|
||||
}
|
||||
// 从Value中新建RNode
|
||||
case Operation.Insert: {
|
||||
getOrCreateChildNode(value[opt.index], rNode, String(opt.index));
|
||||
states.push(ArrayState.NotFresh);
|
||||
childIndex++;
|
||||
break;
|
||||
}
|
||||
case Operation.Delete: {
|
||||
rNode.children?.delete(String(opt.index));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rNode.diffOperator = diffOperator;
|
||||
if (!rNode.diffOperators) {
|
||||
rNode.diffOperators = [];
|
||||
}
|
||||
rNode.diffOperators.push(diffOperator);
|
||||
// 记录:新数据,哪些需要处理,哪些不需要
|
||||
rNode.states = states;
|
||||
// 数组长度不同,确定会产生变化,调用callDependents一次
|
||||
callRContexts(rNode);
|
||||
|
||||
return canPreciseUpdate;
|
||||
}
|
||||
|
||||
// 2、都是数组
|
||||
if (isArr && isPrevArr) {
|
||||
const minLen = Math.min(value.length, prevValue.length);
|
||||
|
||||
// 遍历数组或对象,触发子数据的Effects
|
||||
const canPreciseUpdates = updateSameLengthArray(rNode, value, prevValue, minLen);
|
||||
|
||||
const maxLen = Math.max(value.length, prevValue.length);
|
||||
if (maxLen !== minLen || canPreciseUpdates.includes(false)) {
|
||||
canPreciseUpdate = false;
|
||||
}
|
||||
|
||||
// 在reactive中保存opts
|
||||
const diffOperator: DiffOperator = {
|
||||
isOnlyNop: false,
|
||||
opts: [],
|
||||
};
|
||||
const states: ArrayState[] = [];
|
||||
|
||||
// 相同长度的部分
|
||||
for (let i = 0; i < minLen; i++) {
|
||||
diffOperator.opts.push({ action: Operation.Nop, index: i });
|
||||
// 如果该行数据无法精准更新,设置为NotFresh
|
||||
states.push(canPreciseUpdates[i] ? ArrayState.Fresh : ArrayState.NotFresh);
|
||||
}
|
||||
|
||||
// 超出部分:新增
|
||||
if (value.length > prevValue.length) {
|
||||
for (let i = minLen; i < maxLen; i++) {
|
||||
diffOperator.opts.push({ action: Operation.Insert, index: i });
|
||||
states.push(ArrayState.NotFresh);
|
||||
getOrCreateChildNode(value[i], rNode, String(i));
|
||||
}
|
||||
} else if (value.length < prevValue.length) { // 减少部分:删除
|
||||
for (let i = minLen; i < maxLen; i++) {
|
||||
diffOperator.opts.push({ action: Operation.Delete, index: i });
|
||||
states.push(ArrayState.NotFresh);
|
||||
}
|
||||
}
|
||||
|
||||
diffOperator.isOnlyNop = !states.includes(ArrayState.NotFresh);
|
||||
rNode.diffOperator = diffOperator;
|
||||
rNode.states = states;
|
||||
|
||||
return canPreciseUpdate;
|
||||
}
|
||||
|
||||
// 都是对象
|
||||
if (!isArr && !isPrevArr) {
|
||||
const keys = Object.keys(value);
|
||||
const prevKeys = Object.keys(prevValue);
|
||||
|
||||
// 合并keys和prevKeys
|
||||
const keySet = new Set(keys.concat(prevKeys));
|
||||
|
||||
keySet.forEach((key) => {
|
||||
const val = value[key];
|
||||
const prevVal = prevValue[key];
|
||||
const isChanged = val !== prevVal;
|
||||
|
||||
// 如果数据有变化,就触发Effects
|
||||
if (isChanged) {
|
||||
const childRNode = rNode.children?.get(key);
|
||||
|
||||
const isObj = isObject(val);
|
||||
const isPrevObj = isObject(prevVal);
|
||||
// val和prevVal都是对象或数组
|
||||
if (isObj) {
|
||||
// 1、如果上一个属性无法精准更新,就不再递归下一个属性了
|
||||
// 2、如果childRNode为空,说明这个数据未被引用过,也不需要调用RContexts
|
||||
if (canPreciseUpdate && childRNode !== undefined) {
|
||||
canPreciseUpdate = triggerChildrenContexts(childRNode as RNode, val, prevVal);
|
||||
}
|
||||
} else if (!isObj && !isPrevObj) { // val和prevVal都不是对象或数组
|
||||
canPreciseUpdate = true;
|
||||
} else { // 类型不同(一个是对象或数组,另外一个不是)
|
||||
canPreciseUpdate = false;
|
||||
}
|
||||
|
||||
// 有childRNode,说明这个数据被使引用过
|
||||
if (childRNode !== undefined) {
|
||||
callRContexts(childRNode);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return canPreciseUpdate;
|
||||
}
|
||||
|
||||
// 一个是对象,一个是数组
|
||||
canPreciseUpdate = false;
|
||||
|
||||
return canPreciseUpdate;
|
||||
}
|
||||
|
||||
// 对于数组的变更,尽量尝试精准更新,会记录每行数据是否能够精准更新
|
||||
function updateSameLengthArray(rNode: RNode, value: any, prevValue: any, len: number): boolean[] {
|
||||
const canPreciseUpdates: boolean[] = [];
|
||||
|
||||
// 遍历数组或对象,触发子数据的RContexts
|
||||
for (let i = 0; i < len; i++) {
|
||||
const val = value[i];
|
||||
const prevVal = prevValue[i];
|
||||
const isChanged = val !== prevVal;
|
||||
|
||||
// 如果数据有变化,就触发RContexts
|
||||
if (isChanged) {
|
||||
const childRNode = rNode.children?.get(String(i));
|
||||
|
||||
const isObj = isObject(val);
|
||||
const isPrevObj = isObject(prevVal);
|
||||
// val和prevVal都是对象或数组时
|
||||
if (isObj && isPrevObj) {
|
||||
// 如果childRNode为空,说明这个数据未被引用过,也不需要调用RContexts
|
||||
if (childRNode !== undefined) {
|
||||
canPreciseUpdates[i] = triggerChildrenContexts(childRNode as RNode, val, prevVal);
|
||||
}
|
||||
} else if (!isObj && !isPrevObj) { // val和prevVal都不是对象或数组
|
||||
canPreciseUpdates[i] = true;
|
||||
} else { // 类型不同(一个是对象或数组,另外一个不是)
|
||||
canPreciseUpdates[i] = false;
|
||||
}
|
||||
|
||||
// 有childRNode,说明这个数据被使引用过
|
||||
if (childRNode) {
|
||||
callRContexts(childRNode);
|
||||
}
|
||||
} else {
|
||||
canPreciseUpdates[i] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return canPreciseUpdates;
|
||||
}
|
||||
|
||||
// 调用:响应式数据reactive所收集的依赖(RContext)
|
||||
function callRContexts(reactive: Reactive, isNoComponentRContext = false) {
|
||||
if (reactive.usedRContexts !== undefined && reactive.usedRContexts.size) {
|
||||
// Array.from 浅克隆防止callback中扩缩usedRContexts数组
|
||||
const usedRContexts = Array.from<RContext>(reactive.usedRContexts);
|
||||
const len = usedRContexts.length;
|
||||
let rContext: RContext;
|
||||
|
||||
for (let i = 0; i < len; i++) {
|
||||
rContext = usedRContexts[i];
|
||||
|
||||
// dep.callback可能被清除
|
||||
if (rContext.callback) {
|
||||
// 在触发父数据的时候,不希望触发组件刷新
|
||||
if (isNoComponentRContext && rContext.params.vNode) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const batchItem: BatchItem = {
|
||||
callback: rContext.callback,
|
||||
params: rContext.params,
|
||||
reactive: reactive,
|
||||
};
|
||||
|
||||
addToBatch(batchItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理Reactive的dependents
|
||||
* @param reactive
|
||||
*/
|
||||
export function disposeReactive(reactive: Reactive) {
|
||||
if (reactive.usedRContexts) {
|
||||
const usedRContexts = reactive.usedRContexts;
|
||||
for (const rContext of usedRContexts) {
|
||||
cleanupRContext(rContext);
|
||||
}
|
||||
delete reactive.usedRContexts;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,202 @@
|
|||
/*
|
||||
* Copyright (c) 2023 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.
|
||||
*/
|
||||
|
||||
import { launchUpdateFromVNode } from '../renderer/TreeBuilder';
|
||||
import { bindReactiveWithContext, cleanupRContext, RContext } from './RContext';
|
||||
import { For } from './components/For';
|
||||
import { VNode } from '../renderer/Types';
|
||||
import { RContextParam, Reactive, RNode } from './types';
|
||||
import { getRNodeVal } from './RNode';
|
||||
import { getVNode } from '../dom/DOMInternalKeys';
|
||||
import { updateInputValue } from '../dom/valueHandler/InputValueHandler';
|
||||
import { updateTextareaValue } from '../dom/valueHandler/TextareaValueHandler';
|
||||
import { setDomProps } from '../dom/DOMPropertiesHandler/DOMPropertiesHandler';
|
||||
import { getRNodeFromProxy, isAtom, isReactiveProxy } from './Utils';
|
||||
|
||||
const vNodeEffectMap = new WeakMap<VNode, RContext>();
|
||||
|
||||
/**
|
||||
* 创建组件(函数组件或Class组件)级别Dependent
|
||||
* @param renderFn 函数组件 或 Class的render
|
||||
* @param vNode
|
||||
*/
|
||||
export function createComponentDependent<T>(renderFn: () => T, vNode: VNode): T {
|
||||
let compRContext = vNode.compRContext;
|
||||
|
||||
if (!compRContext) {
|
||||
compRContext = new RContext(
|
||||
(params, reactive) => {
|
||||
vNode.isStoreChange = true;
|
||||
|
||||
// 如果是For组件
|
||||
if (For === vNode.type && reactive.diffOperator) {
|
||||
// 如果Reactive的For组件
|
||||
const { isOnlyNop } = reactive.diffOperator;
|
||||
|
||||
// 如果没有需要处理
|
||||
if (isOnlyNop) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 同步刷新
|
||||
// syncUpdates(() =>{
|
||||
// // 触发vNode更新
|
||||
// launchUpdateFromVNode(vNode);
|
||||
// });
|
||||
|
||||
// 触发vNode更新
|
||||
launchUpdateFromVNode(vNode);
|
||||
},
|
||||
{ vNode }
|
||||
);
|
||||
|
||||
vNode.compRContext = compRContext;
|
||||
}
|
||||
|
||||
const endRContext = compRContext.start();
|
||||
|
||||
const result = renderFn();
|
||||
|
||||
endRContext();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 订阅DOM的属性(props或children),创建一个专门更新该属性的上下文,当响应式数据变化就触发该上下文的callback
|
||||
* @param dom DOM元素
|
||||
* @param propName 属性名字
|
||||
* @param propVal 属性值,是响应式数据
|
||||
* @param styleName style里面的某个属性
|
||||
*/
|
||||
function subscribeAttr(dom: Element, propName: string, propVal: Reactive, styleName?: string) {
|
||||
const attrRContext = new RContext(
|
||||
params => {
|
||||
let changeList;
|
||||
if (propName === 'style' && styleName) {
|
||||
changeList = {
|
||||
style: {
|
||||
[styleName]: getRNodeVal(params.reactive!),
|
||||
},
|
||||
};
|
||||
} else {
|
||||
changeList = {
|
||||
[propName]: getRNodeVal(params.reactive!),
|
||||
};
|
||||
}
|
||||
|
||||
const type = getVNode(dom)?.type;
|
||||
if (type === 'input' && propName === 'value') {
|
||||
updateInputValue(dom as HTMLInputElement, changeList);
|
||||
} else if (type === 'textarea' && propName === 'value') {
|
||||
updateTextareaValue(dom as HTMLTextAreaElement, changeList);
|
||||
} else {
|
||||
setDomProps(dom, changeList, true, false);
|
||||
}
|
||||
},
|
||||
{ reactive: propVal }
|
||||
);
|
||||
|
||||
bindReactiveWithContext(propVal, attrRContext);
|
||||
|
||||
// vNode保存RContext,用于cleanup
|
||||
const vNode = getVNode(dom);
|
||||
saveAttrRContexts(vNode, attrRContext);
|
||||
}
|
||||
|
||||
export function handleReactiveProp(dom: Element, propName: string, propVal: any, styleName?: string): any {
|
||||
let rawVal = propVal;
|
||||
const isA = isAtom(propVal);
|
||||
const isProxy = isReactiveProxy(propVal);
|
||||
|
||||
if (isA || isProxy) {
|
||||
let reactive = propVal;
|
||||
if (isProxy) {
|
||||
reactive = getRNodeFromProxy(propVal);
|
||||
}
|
||||
rawVal = getRNodeVal(reactive as Reactive);
|
||||
subscribeAttr(dom, propName, reactive, styleName);
|
||||
}
|
||||
|
||||
return rawVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建DOM Key Dependent
|
||||
* @param callback 用于修改VNode的key
|
||||
* @param params Effect调用所需要的参数
|
||||
*/
|
||||
export function createKeyDependent(callback: (params: RContextParam) => void, params?: RContextParam) {
|
||||
return new RContext(callback, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理 <div>Count: {_rObj}</div>
|
||||
* @param textDom 用于修改VNode的key
|
||||
* @param rText Effect调用所需要的参数
|
||||
*/
|
||||
export function subscribeReactiveComponent(textDom: Element, rText: Reactive) {
|
||||
const textContext = new RContext(
|
||||
params => {
|
||||
textDom.textContent = getRNodeVal(params.reactive as Reactive);
|
||||
},
|
||||
{ reactive: rText }
|
||||
);
|
||||
|
||||
bindReactiveWithContext(rText, textContext);
|
||||
|
||||
// vNode保存RContext,用于cleanup
|
||||
const vNode = getVNode(textDom);
|
||||
saveAttrRContexts(vNode, textContext);
|
||||
}
|
||||
|
||||
// TODO 删除
|
||||
export function cleanupVNodeEffect(vNode: VNode) {
|
||||
const effect = vNodeEffectMap.get(vNode);
|
||||
if (effect) {
|
||||
cleanupRContext(effect);
|
||||
vNodeEffectMap.delete(vNode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建DOM Key Dependent
|
||||
* @param vNode
|
||||
* @param rKey
|
||||
*/
|
||||
export function subscribeKeyEffect(vNode: VNode, rKey: RNode) {
|
||||
const keyContext = new RContext(
|
||||
params => {
|
||||
vNode.key = getRNodeVal(rKey);
|
||||
},
|
||||
{ reactive: rKey }
|
||||
);
|
||||
|
||||
bindReactiveWithContext(rKey, keyContext);
|
||||
|
||||
// vNode保存RContext,用于cleanup
|
||||
saveAttrRContexts(vNode, keyContext);
|
||||
}
|
||||
|
||||
// vNode保存RContext,用于cleanup
|
||||
function saveAttrRContexts(vNode: VNode | null, rContext: RContext) {
|
||||
if (vNode) {
|
||||
if (!vNode.attrRContexts) {
|
||||
vNode.attrRContexts = new Set();
|
||||
}
|
||||
vNode.attrRContexts.add(rContext);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,156 @@
|
|||
/*
|
||||
* Copyright (c) 2023 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.
|
||||
*/
|
||||
|
||||
import { bindReactiveWithContext, currentDependent, triggerRContexts } from './RContext';
|
||||
import { isAtom, isFunction, isRNode, isPrimitive } from './Utils';
|
||||
import { createProxy } from './proxy/RProxyHandler';
|
||||
import { Atom } from './Atom';
|
||||
import { isSame } from '../renderer/utils/compare';
|
||||
import { ProxyRNode, Reactive, ReactiveProxy, RNode, RootRNode } from './types';
|
||||
|
||||
export const nodeSymbol = Symbol('ReactiveNode');
|
||||
|
||||
export function createReactiveObj<T extends any>(raw?: T): ReactiveProxy<T> {
|
||||
if (isPrimitive(raw) || raw === null || raw === undefined) {
|
||||
return new Atom(raw);
|
||||
} else {
|
||||
const node = createRootRNode(raw);
|
||||
const proxyObj = createProxy<T>(node);
|
||||
node.proxy = proxyObj;
|
||||
return proxyObj as ReactiveProxy<T>;
|
||||
}
|
||||
}
|
||||
|
||||
export function createRootRNode<T extends any>(raw?: T): RootRNode<T> {
|
||||
const root: RootRNode<T> = {
|
||||
type: nodeSymbol,
|
||||
root: { $: raw },
|
||||
parent: null,
|
||||
parentKey: null,
|
||||
};
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
export function getOrCreateChildRNode(node: RNode, key: string | symbol): RNode {
|
||||
let child = node.children?.get(key);
|
||||
|
||||
if (!child || isAtom(child)) {
|
||||
child = {
|
||||
type: nodeSymbol,
|
||||
root: node.root,
|
||||
parent: node,
|
||||
parentKey: key,
|
||||
};
|
||||
if (!node.children) {
|
||||
node.children = new Map();
|
||||
}
|
||||
|
||||
node.children.set(key, child);
|
||||
}
|
||||
return child;
|
||||
}
|
||||
|
||||
export function getOrCreateChildAtom(node: RNode, key: string | symbol, value: any): Atom {
|
||||
const child = node.children?.get(key);
|
||||
|
||||
// 如果没有子节点或者子节点是RNode类型,就创建一个新的Atom,否则返回已存在的子节点
|
||||
// 注意:不能使用getNodeVal来判断之前节点的值,因为根数据已被修改,只能通过判断child的类型为
|
||||
if (!child || isRNode(child)) {
|
||||
const atom = new Atom(value, node, key);
|
||||
if (!node.children) {
|
||||
node.children = new Map();
|
||||
}
|
||||
node.children.set(key, atom);
|
||||
return atom;
|
||||
}
|
||||
|
||||
return child;
|
||||
}
|
||||
|
||||
export function getOrCreateChildNode(value: unknown, parent: RNode, key: string | symbol): Atom | RNode {
|
||||
let child: Atom | RNode;
|
||||
// if (isPrimitive(value) || value === null || value === undefined) {
|
||||
// child = getOrCreateChildAtom(parent, key, value);
|
||||
// } else {
|
||||
child = getOrCreateChildRNode(parent, key);
|
||||
// }
|
||||
return child;
|
||||
}
|
||||
|
||||
export function getOrCreateChildProxy(value: unknown, parent: RNode, key: string | symbol): Atom | ProxyRNode<any> {
|
||||
let child: Atom | RNode;
|
||||
// if (isPrimitive(value) || value === null || value === undefined) {
|
||||
// child = getOrCreateChildAtom(parent, key, value);
|
||||
// return child;
|
||||
// } else {
|
||||
child = getOrCreateChildRNode(parent, key);
|
||||
if (!child.proxy) {
|
||||
child.proxy = createProxy(child);
|
||||
}
|
||||
return child.proxy;
|
||||
// }
|
||||
}
|
||||
|
||||
// 最终响应式数据的使用
|
||||
export function trackReactiveData(reactive: Reactive) {
|
||||
if (currentDependent !== null) {
|
||||
bindReactiveWithContext(reactive, currentDependent);
|
||||
}
|
||||
}
|
||||
|
||||
export function getRNodeVal(node: Reactive): any {
|
||||
let currentNode = node;
|
||||
const keys: (string | symbol)[] = [];
|
||||
while (currentNode.parentKey !== null && currentNode.parent !== null) {
|
||||
keys.push(currentNode.parentKey);
|
||||
currentNode = currentNode.parent;
|
||||
}
|
||||
|
||||
let rawObj = node.root.$;
|
||||
for (let i = keys.length - 1; i >= 0; i--) {
|
||||
if (keys[i] !== undefined && rawObj) {
|
||||
rawObj = rawObj[keys[i]];
|
||||
}
|
||||
}
|
||||
|
||||
return rawObj;
|
||||
}
|
||||
|
||||
export function setRNodeVal(rNode: RNode, value: unknown, trigger = false, isArrayModified = false): void {
|
||||
if (rNode.root.readOnly) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { parent, parentKey } = rNode;
|
||||
const isRoot = parent === null;
|
||||
let prevValue: unknown;
|
||||
let newValue: unknown;
|
||||
|
||||
if (isRoot) {
|
||||
prevValue = rNode.root.$;
|
||||
newValue = isFunction<(...prev: any) => any>(value) ? value(prevValue) : value;
|
||||
rNode.root.$ = newValue;
|
||||
} else {
|
||||
const parentVal = getRNodeVal(parent!);
|
||||
prevValue = parentVal[parentKey!];
|
||||
newValue = isFunction<(...prev: any) => any>(value) ? value(prevValue) : value;
|
||||
parentVal[parentKey!] = newValue;
|
||||
}
|
||||
|
||||
if (trigger && !isSame(newValue, prevValue)) {
|
||||
triggerRContexts(rNode, prevValue, newValue, isArrayModified);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Copyright (c) 2023 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.
|
||||
*/
|
||||
|
||||
import { createReactiveObj } from './RNode';
|
||||
import { ReactiveProxy } from './types';
|
||||
|
||||
export function reactive<T extends any>(obj: T): ReactiveProxy<T> {
|
||||
return createReactiveObj(obj);
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* Copyright (c) 2023 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.
|
||||
*/
|
||||
|
||||
import {Atom} from './Atom';
|
||||
import {getRNodeVal, nodeSymbol} from './RNode';
|
||||
import {AtomNode, AtomNodeFn, ProxyRNode, ProxyRNodeFn, Reactive, ReactiveProxy, RNode} from './types';
|
||||
import {GET_R_NODE} from './proxy/RProxyHandler';
|
||||
|
||||
export function isAtom(val: unknown): val is Atom {
|
||||
return val instanceof Atom;
|
||||
}
|
||||
|
||||
export function isRNode(val: unknown): val is RNode {
|
||||
return typeof val === 'object' && val != null && val['type'] === nodeSymbol;
|
||||
}
|
||||
|
||||
export function isReactiveProxy(val: unknown): val is RNode {
|
||||
return typeof val === 'object' && val != null && val[GET_R_NODE] !== undefined;
|
||||
}
|
||||
|
||||
export function isReactiveObj(val: unknown): val is ProxyRNodeFn<any> | AtomNodeFn<any> {
|
||||
return isAtom(val) || isReactiveProxy(val);
|
||||
}
|
||||
|
||||
export function isObject(obj: unknown): boolean {
|
||||
const type = typeof obj;
|
||||
return obj != null && (type === 'object' || type === 'function');
|
||||
}
|
||||
|
||||
export function isPrimitive(obj: unknown): boolean {
|
||||
const type = typeof obj;
|
||||
return obj != null && type !== 'object' && type !== 'function';
|
||||
}
|
||||
|
||||
export function isFunction<T extends (...prev: any) => any>(obj: unknown): obj is T {
|
||||
return typeof obj === 'function';
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果是函数就执行,如果是reactive就调用get()
|
||||
* @param val 值/reactive对象/函数
|
||||
* @return 返回真实值
|
||||
*/
|
||||
export function calculateReactive(val: any | (() => any)): any {
|
||||
let ret = val;
|
||||
if (typeof val === 'function') {
|
||||
ret = val();
|
||||
}
|
||||
|
||||
if (isReactiveObj(ret)) {
|
||||
ret = ret.get();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
export function getRNode<T = any>(rObj: ProxyRNode<T> | AtomNode<T>): RNode {
|
||||
return isReactiveProxy(rObj) ? rObj[GET_R_NODE] : rObj;
|
||||
}
|
||||
|
||||
export function getRNodeFromProxy<T = any>(rObj: ProxyRNode<T>): RNode {
|
||||
return rObj[GET_R_NODE];
|
||||
}
|
||||
|
||||
export function isPromise<T>(obj: unknown): obj is Promise<T> {
|
||||
return obj instanceof Promise;
|
||||
}
|
||||
|
||||
export function getValue(value: any): any {
|
||||
if (isAtom(value)) {
|
||||
return getRNodeVal(value as Reactive);
|
||||
}
|
||||
if (isReactiveProxy(value)) {
|
||||
return getRNodeVal(value[GET_R_NODE]);
|
||||
}
|
||||
return value;
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright (c) 2023 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.
|
||||
*/
|
||||
|
||||
export const inJSX: { current: boolean } = {
|
||||
current: false,
|
||||
};
|
||||
|
||||
export const inComputation: { current: boolean } = {
|
||||
current: false,
|
||||
};
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright (c) 2023 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.
|
||||
*/
|
||||
|
||||
import { RContext } from './RContext';
|
||||
import { calculateReactive } from './Utils';
|
||||
import { Reactive } from './types';
|
||||
|
||||
export function watch(fn: () => any | Reactive, callback?: () => void): any {
|
||||
// 有两个参数,第一个是要监听数据/函数,第二个是回调
|
||||
if (typeof callback === 'function') {
|
||||
const effect = new RContext(() => {
|
||||
callback();
|
||||
});
|
||||
|
||||
const endEffect = effect.start();
|
||||
|
||||
calculateReactive(fn);
|
||||
|
||||
endEffect();
|
||||
} else {
|
||||
// 只有一个参数
|
||||
const effect = new RContext(fn);
|
||||
|
||||
const endEffect = effect.start();
|
||||
|
||||
fn();
|
||||
|
||||
endEffect();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright (c) 2023 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.
|
||||
*/
|
||||
|
||||
import { JSXElement } from '../../renderer/Types';
|
||||
import { calculateReactive } from '../Utils';
|
||||
|
||||
/**
|
||||
* Block可以控制更新范围,若View的children函数中通过get()使用了响应式数据,当响应式数据变化时,children函数会被重新执行,不会影响父组件。
|
||||
* <Block>
|
||||
{() => {
|
||||
const count = _rObj.count.get();
|
||||
return <>
|
||||
<div>{count}</div>
|
||||
</>;
|
||||
}}
|
||||
</Block>
|
||||
* @param children
|
||||
*/
|
||||
export function Block({ children }: { children: () => JSXElement }): any {
|
||||
const result = calculateReactive(children);
|
||||
|
||||
return result;
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* Copyright (c) 2023 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.
|
||||
*/
|
||||
|
||||
import { JSXElement } from '../../renderer/Types';
|
||||
import { createElement } from '../../external/JSXElement';
|
||||
import { useMemo } from '../../renderer/hooks/HookExternal';
|
||||
import { memo } from '../../renderer/components/Memo';
|
||||
import { shallowCompare } from '../../renderer/utils/compare';
|
||||
import { ArrayState } from '../types';
|
||||
import { GET_R_NODE } from '../proxy/RProxyHandler';
|
||||
|
||||
/**
|
||||
* For组件用于循环渲染数据
|
||||
* @param each 包含要循环的数据的可观察对象
|
||||
* @param children 渲染每个数据项的子组件
|
||||
* @returns 返回JSX元素或null
|
||||
*/
|
||||
export function For<T>({
|
||||
each,
|
||||
children,
|
||||
}: {
|
||||
each: any;
|
||||
children?: (value: any, index: number) => JSXElement;
|
||||
}): JSXElement[] | null {
|
||||
// 获取可观察对象中的数据数组
|
||||
const reactiveArr = each.get();
|
||||
|
||||
if (!reactiveArr || !reactiveArr.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const rNode = each[GET_R_NODE];
|
||||
const states = rNode.states !== undefined ? rNode.states : [];
|
||||
|
||||
let Item: Partial<JSXElement> | null = null;
|
||||
|
||||
if (children) {
|
||||
Item = useMemo(() => {
|
||||
return memo(({ item, index }) => children(item, index), itemCompare);
|
||||
}, []);
|
||||
}
|
||||
|
||||
const ret: JSXElement[] = [];
|
||||
const len = reactiveArr.length;
|
||||
for (let i = 0; i < len; i++) {
|
||||
const state = states[i];
|
||||
const isFresh = state === ArrayState.Fresh;
|
||||
|
||||
// 创建并添加JSX元素
|
||||
ret.push(createElement(Item, { isFresh, item: each[i], index: i }));
|
||||
}
|
||||
|
||||
// 用完,重置
|
||||
rNode.states = [];
|
||||
|
||||
// 合并多次的diffOperator
|
||||
if (rNode.diffOperators) {
|
||||
rNode.diffOperators.forEach(() => {
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
ret.diffOperator = rNode.diffOperator;
|
||||
// 用完,删除
|
||||
delete rNode.diffOperators;
|
||||
delete rNode.diffOperator;
|
||||
|
||||
// 返回JSX元素数组
|
||||
return ret;
|
||||
}
|
||||
|
||||
// 如果属性中有isFresh,就优先判断isFresh
|
||||
function itemCompare(oldProps, newProps) {
|
||||
if (newProps.isFresh !== undefined) {
|
||||
return Boolean(newProps.isFresh);
|
||||
} else {
|
||||
return shallowCompare(oldProps, newProps);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright (c) 2023 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.
|
||||
*/
|
||||
|
||||
import { memo } from '../../renderer/components/Memo';
|
||||
import { JSXElement } from '../../renderer/Types';
|
||||
import { calculateReactive } from '../Utils';
|
||||
|
||||
export const RText = memo(
|
||||
function ({ children }: { children: any }): JSXElement {
|
||||
return calculateReactive(children);
|
||||
},
|
||||
() => true // 属性指针始终为true,不刷新
|
||||
);
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright (c) 2023 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.
|
||||
*/
|
||||
|
||||
import { JSXElement } from '../../renderer/Types';
|
||||
import { calculateReactive } from '../Utils';
|
||||
|
||||
export function Show<T>({
|
||||
if: rIf,
|
||||
else: rElse,
|
||||
children,
|
||||
}: {
|
||||
if: any | (() => T);
|
||||
else?: any;
|
||||
children: JSXElement | (() => JSXElement);
|
||||
}): any {
|
||||
const ifValue: any = calculateReactive(rIf);
|
||||
|
||||
let child: JSXElement | (() => JSXElement) | null = null;
|
||||
if (ifValue) {
|
||||
child = typeof children === 'function' ? children() : children;
|
||||
} else {
|
||||
child = typeof rElse === 'function' ? rElse() : rElse;
|
||||
}
|
||||
|
||||
return child;
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright (c) 2023 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.
|
||||
*/
|
||||
|
||||
import { calculateReactive } from '../Utils';
|
||||
import { JSXElement, VNode } from '../../renderer/Types';
|
||||
import { Children } from '../../external/ChildrenUtil';
|
||||
|
||||
export function Switch<T>({
|
||||
children,
|
||||
default: df,
|
||||
}: {
|
||||
children: JSXElement[] | Record<any, () => JSXElement>;
|
||||
default?: JSXElement;
|
||||
}): JSXElement | null {
|
||||
const arr = Children.toArray(children);
|
||||
|
||||
let index = -1;
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
const showComp = arr[i];
|
||||
|
||||
const ifValue: any = calculateReactive((showComp as VNode).props.if);
|
||||
|
||||
if (ifValue) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return index >= 0 ? arr[index] : df ?? null;
|
||||
}
|
|
@ -0,0 +1,146 @@
|
|||
/*
|
||||
* Copyright (c) 2023 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.
|
||||
*/
|
||||
|
||||
import { isArray } from '../../inulax/CommonUtils';
|
||||
import { getOrCreateChildProxy, getRNodeVal, setRNodeVal, trackReactiveData } from '../RNode';
|
||||
import { RNode, ProxyRNode } from '../types';
|
||||
|
||||
const GET = 'get';
|
||||
const SET = 'set';
|
||||
const READ = 'read';
|
||||
const DELETE = 'delete';
|
||||
const ONCHANGE = 'onChange';
|
||||
export const GET_R_NODE = '$$getRNode';
|
||||
const PROTOTYPE = 'prototype';
|
||||
|
||||
// 数组的修改方法
|
||||
const MODIFY_ARR_FNS = new Set<string | symbol>([
|
||||
'push',
|
||||
'pop',
|
||||
'splice',
|
||||
'shift',
|
||||
'unshift',
|
||||
'reverse',
|
||||
'sort',
|
||||
'fill',
|
||||
'from',
|
||||
'copyWithin',
|
||||
]);
|
||||
|
||||
// 数组的遍历方法
|
||||
const LOOP_ARR_FNS = new Set<string | symbol>(['forEach', 'map', 'every', 'some', 'filter', 'join']);
|
||||
|
||||
export function createProxy<T extends any>(proxyNode: RNode): ProxyRNode<T> {
|
||||
return new Proxy(proxyNode, {
|
||||
get,
|
||||
set,
|
||||
});
|
||||
}
|
||||
|
||||
const GetFns: Record<string | symbol, (args: RNode) => any> = {
|
||||
[GET]: getFn,
|
||||
[READ]: readFn,
|
||||
[DELETE]: deleteFn,
|
||||
[ONCHANGE]: onChangeFn,
|
||||
};
|
||||
|
||||
function get(rNode: RNode, key: string | symbol): any {
|
||||
// 处理get,read,delete,onchange方法
|
||||
const fn = GetFns[key];
|
||||
if (fn && typeof fn === 'function') {
|
||||
return () => fn(rNode);
|
||||
}
|
||||
|
||||
// 调用set()方法
|
||||
if (key === SET) {
|
||||
return function (val: any) {
|
||||
setRNodeVal(rNode, val, true, false);
|
||||
};
|
||||
}
|
||||
|
||||
if (key === GET_R_NODE) {
|
||||
return rNode;
|
||||
}
|
||||
|
||||
const rawObj = getRNodeVal(rNode);
|
||||
|
||||
// const value = rawObj !== undefined ? Reflect.get(rawObj, key) : rawObj;
|
||||
const value = rawObj !== undefined ? rawObj[key] : rawObj;
|
||||
|
||||
// 对于prototype不做代理
|
||||
if (key === PROTOTYPE) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (isArray(rawObj) && key === 'length') {
|
||||
// 标记依赖
|
||||
trackReactiveData(rNode);
|
||||
return value;
|
||||
}
|
||||
|
||||
// 处理数组的方法
|
||||
if (typeof value === 'function') {
|
||||
if (isArray(rawObj)) {
|
||||
// 处理数组的修改方法
|
||||
if (MODIFY_ARR_FNS.has(key)) {
|
||||
return (...args: any[]) => {
|
||||
// 调用数组方法的时候,前后是相同的引用,所以需要先浅拷贝数组,并在浅拷贝的数组上进行操作
|
||||
const value = rawObj.slice();
|
||||
const ret = value[key](...args);
|
||||
// 调用了数组的修改方法,默认值有变化
|
||||
setRNodeVal(rNode, value, true, true);
|
||||
|
||||
return ret;
|
||||
};
|
||||
} else if (LOOP_ARR_FNS.has(key)) { // 处理数组的遍历方法
|
||||
// 标记被使用了
|
||||
trackReactiveData(rNode);
|
||||
|
||||
return function (callBackFn: any, thisArg?: any) {
|
||||
function cb(_: any, index: number, array: any[]) {
|
||||
const idx = String(index);
|
||||
const itemProxy = getOrCreateChildProxy(array[idx], rNode, idx);
|
||||
return callBackFn(itemProxy, index, array);
|
||||
}
|
||||
|
||||
return rawObj[key](cb, thisArg);
|
||||
};
|
||||
}
|
||||
}
|
||||
return value.bind(rawObj);
|
||||
}
|
||||
|
||||
return getOrCreateChildProxy(value, rNode, key);
|
||||
}
|
||||
|
||||
function set(proxyNode: any, key: string, value: any, receiver: any): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
// get()调用的处理
|
||||
function getFn(node: RNode) {
|
||||
trackReactiveData(node);
|
||||
return readFn(node);
|
||||
}
|
||||
|
||||
function readFn(node: RNode) {
|
||||
return getRNodeVal(node);
|
||||
}
|
||||
|
||||
// delete()调用的处理
|
||||
function deleteFn() {}
|
||||
|
||||
// onChange()调用的处理
|
||||
function onChangeFn() {}
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* Copyright (c) 2023 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.
|
||||
*/
|
||||
|
||||
import { VNode } from '../renderer/Types';
|
||||
import { Atom } from './Atom';
|
||||
import { RContextSet } from './RContext';
|
||||
import { DiffOperator } from './DiffUtils';
|
||||
|
||||
export enum ArrayState {
|
||||
Fresh = 0,
|
||||
NotFresh = 1,
|
||||
}
|
||||
|
||||
export interface Root<T> {
|
||||
$?: T;
|
||||
|
||||
/**
|
||||
* 下面属性computed使用
|
||||
* @param {readOnly} 标识computed 是否处于写入状态
|
||||
*/
|
||||
readOnly?: boolean;
|
||||
}
|
||||
|
||||
export type PrimitiveType = string | number | boolean;
|
||||
|
||||
export type ValueType<T> = { [K in keyof T]: any } | Record<string, any> | PrimitiveType;
|
||||
|
||||
export interface BaseNodeFns<T> {
|
||||
/**
|
||||
* 返回响应式对象的值,自动追踪依赖
|
||||
*/
|
||||
get(): T;
|
||||
|
||||
/**
|
||||
* 返回响应式对象的值,不追踪依赖
|
||||
*/
|
||||
read(): T;
|
||||
}
|
||||
|
||||
export interface ProxyRNodeFn<T> extends BaseNodeFns<T> {
|
||||
set<V = ValueType<T>>(value: V | ((prev: T) => V));
|
||||
}
|
||||
|
||||
export interface AtomNodeFn<T> extends BaseNodeFns<T> {
|
||||
set<V extends PrimitiveType>(value: V | ((prev: T) => V));
|
||||
}
|
||||
|
||||
type PropsRecursive<T, K extends keyof T, RecurseType> = T[K] extends PrimitiveType
|
||||
? AtomNode<T[K]>
|
||||
: T[K] extends any[]
|
||||
? any[] & ProxyRNodeFn<T[K]>
|
||||
: T extends Record<string, any>
|
||||
? RecurseType
|
||||
: T[K];
|
||||
|
||||
export type ProxyRNodeProps<T> = {
|
||||
[K in keyof T]: PropsRecursive<T, K, ProxyRNode<T[K]>>;
|
||||
};
|
||||
|
||||
export type ComputedProps<T> = {
|
||||
[K in keyof T]: PropsRecursive<T, K, BaseNodeFns<T[K]>>;
|
||||
};
|
||||
|
||||
export type ProxyRNode<T> = ProxyRNodeFn<T> & ProxyRNodeProps<T>;
|
||||
|
||||
export type AtomNode<T> = AtomNodeFn<T>;
|
||||
|
||||
export type Computed<T> = BaseNodeFns<T> & ComputedProps<T>;
|
||||
|
||||
export type ReactiveProxy<T> = T extends PrimitiveType ? AtomNode<T> : ProxyRNode<T>;
|
||||
|
||||
export interface BaseRNode<T> {
|
||||
// 标识Node类型 atomSymbol,nodeSymbol,computedSymbol
|
||||
type: symbol;
|
||||
root: Root<T>;
|
||||
children?: Map<string | symbol, RNode<T> | Atom<T>>;
|
||||
usedRContexts?: RContextSet;
|
||||
proxy?: any;
|
||||
|
||||
diffOperator?: DiffOperator;
|
||||
diffOperators?: DiffOperator[];
|
||||
states?: ArrayState[];
|
||||
}
|
||||
|
||||
export interface RootRNode<T> extends BaseRNode<T> {
|
||||
parentKey: null;
|
||||
parent: null;
|
||||
}
|
||||
|
||||
export interface ChildrenRNode<T> extends BaseRNode<T> {
|
||||
parentKey: string | symbol;
|
||||
parent: RNode;
|
||||
}
|
||||
|
||||
export type RNode<T = any> = RootRNode<T> | ChildrenRNode<T>;
|
||||
|
||||
export type Reactive<T = any> = RNode<T> | Atom<T>;
|
||||
|
||||
export interface RContextParam {
|
||||
vNode?: VNode;
|
||||
reactive?: Reactive;
|
||||
}
|
||||
|
||||
export type RContextCallback = (params: RContextParam, reactive: Reactive) => void;
|
|
@ -18,7 +18,7 @@ import type { VNode } from './Types';
|
|||
import { callRenderQueueImmediate, pushRenderCallback } from './taskExecutor/RenderQueue';
|
||||
import { updateVNode } from './vnode/VNodeCreator';
|
||||
import { ContextProvider, DomComponent, DomPortal, TreeRoot } from './vnode/VNodeTags';
|
||||
import { FlagUtils, InitFlag, Interrupted } from './vnode/VNodeFlags';
|
||||
import { FlagUtils, InitFlag, Interrupted, Deletion } from './vnode/VNodeFlags';
|
||||
import { captureVNode } from './render/BaseComponent';
|
||||
import { checkLoopingUpdateLimit, submitToRender } from './submit/Submit';
|
||||
import { runAsyncEffects } from './submit/HookEffectHandler';
|
||||
|
@ -81,13 +81,13 @@ function collectDirtyNodes(vNode: VNode, parent: VNode): void {
|
|||
if (parent.dirtyNodes === null) {
|
||||
parent.dirtyNodes = dirtyNodes;
|
||||
} else {
|
||||
parent.dirtyNodes.push(...vNode.dirtyNodes!);
|
||||
parent.dirtyNodes.push(...dirtyNodes);
|
||||
dirtyNodes.length = 0;
|
||||
}
|
||||
vNode.dirtyNodes = null;
|
||||
}
|
||||
|
||||
if (FlagUtils.hasAnyFlag(vNode)) {
|
||||
if (FlagUtils.hasAnyFlag(vNode) && vNode.flags !== Deletion) {
|
||||
if (parent.dirtyNodes === null) {
|
||||
parent.dirtyNodes = [vNode];
|
||||
} else {
|
||||
|
@ -273,44 +273,44 @@ function buildVNodeTree(treeRoot: VNode) {
|
|||
changeMode(InRender, true);
|
||||
|
||||
// 计算出开始节点
|
||||
const startVNode = calcStartUpdateVNode(treeRoot);
|
||||
// const startVNode = calcStartUpdateVNode(treeRoot);
|
||||
// 缓存起来
|
||||
setStartVNode(startVNode);
|
||||
setStartVNode(treeRoot);
|
||||
|
||||
// 清空toUpdateNodes
|
||||
treeRoot.toUpdateNodes?.clear();
|
||||
|
||||
if (startVNode.tag !== TreeRoot) {
|
||||
// 不是根节点
|
||||
// 设置namespace,用于createElement
|
||||
let parent = startVNode.parent;
|
||||
while (parent !== null) {
|
||||
const tag = parent.tag;
|
||||
if (tag === DomComponent) {
|
||||
break;
|
||||
} else if (tag === TreeRoot || tag === DomPortal) {
|
||||
break;
|
||||
}
|
||||
parent = parent.parent;
|
||||
}
|
||||
|
||||
// 当在componentWillUnmount中调用setState,parent可能是null,因为startVNode会被clear
|
||||
if (parent !== null) {
|
||||
resetNamespaceCtx(parent);
|
||||
setNamespaceCtx(parent, parent.realNode);
|
||||
}
|
||||
|
||||
// 恢复父节点的context
|
||||
recoverTreeContext(startVNode);
|
||||
}
|
||||
// if (startVNode.tag !== TreeRoot) {
|
||||
// // 不是根节点
|
||||
// // 设置namespace,用于createElement
|
||||
// let parent = startVNode.parent;
|
||||
// while (parent !== null) {
|
||||
// const tag = parent.tag;
|
||||
// if (tag === DomComponent) {
|
||||
// break;
|
||||
// } else if (tag === TreeRoot || tag === DomPortal) {
|
||||
// break;
|
||||
// }
|
||||
// parent = parent.parent;
|
||||
// }
|
||||
//
|
||||
// // 当在componentWillUnmount中调用setState,parent可能是null,因为startVNode会被clear
|
||||
// if (parent !== null) {
|
||||
// resetNamespaceCtx(parent);
|
||||
// setNamespaceCtx(parent, parent.realNode);
|
||||
// }
|
||||
//
|
||||
// // 恢复父节点的context
|
||||
// recoverTreeContext(startVNode);
|
||||
// }
|
||||
|
||||
// 重置环境变量,为重新进行深度遍历做准备
|
||||
resetProcessingVariables(startVNode);
|
||||
resetProcessingVariables(treeRoot);
|
||||
// devProps 用于插件手动更新props值
|
||||
if (startVNode.devProps !== undefined) {
|
||||
startVNode.props = startVNode.devProps;
|
||||
startVNode.devProps = undefined;
|
||||
}
|
||||
// if (startVNode.devProps !== undefined) {
|
||||
// startVNode.props = startVNode.devProps;
|
||||
// startVNode.devProps = undefined;
|
||||
// }
|
||||
|
||||
while (processing !== null) {
|
||||
try {
|
||||
|
@ -327,11 +327,11 @@ function buildVNodeTree(treeRoot: VNode) {
|
|||
handleError(treeRoot, thrownValue);
|
||||
}
|
||||
}
|
||||
if (startVNode.tag !== TreeRoot) {
|
||||
// 不是根节点
|
||||
// 恢复父节点的context
|
||||
resetTreeContext(startVNode);
|
||||
}
|
||||
// if (startVNode.tag !== TreeRoot) {
|
||||
// // 不是根节点
|
||||
// // 恢复父节点的context
|
||||
// resetTreeContext(startVNode);
|
||||
// }
|
||||
|
||||
setProcessingClassVNode(null);
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
*/
|
||||
|
||||
import { BELONG_CLASS_VNODE_KEY } from './vnode/VNode';
|
||||
import { VNodeTag } from './vnode/VNodeTags';
|
||||
|
||||
export { VNode } from './vnode/VNode';
|
||||
|
||||
|
@ -82,3 +83,75 @@ export type Source = {
|
|||
};
|
||||
|
||||
export type Callback = () => void;
|
||||
|
||||
export type VNode = {
|
||||
tag: VNodeTag,
|
||||
key: string | null, // 唯一标识符
|
||||
props: any, // 传给组件的props的值,类组件包含defaultProps,Lazy组件不包含
|
||||
type: any,
|
||||
realNode: any, // 如果是类,则存放实例;如果是div这种,则存放真实DOM;
|
||||
|
||||
// 关系结构
|
||||
parent: VNode | null, // 父节点
|
||||
child: VNode | null, // 子节点
|
||||
next: VNode | null, // 兄弟节点
|
||||
cIndex, // 节点在children数组中的位置
|
||||
eIndex, // HorizonElement在jsx中的位置,例如:jsx中的null不会生成vNode,所以eIndex和cIndex不一致
|
||||
|
||||
ref: RefType | ((handle: any) => void) | null, // 包裹一个函数,submit阶段使用,比如将外部useRef生成的对象赋值到ref上
|
||||
oldProps: any,
|
||||
|
||||
// 是否已经被从树上移除
|
||||
isCleared,
|
||||
changeList: any, // DOM的变更列表
|
||||
effectList: any[] | null, // useEffect 的更新数组
|
||||
updates: any[] | null, // TreeRoot和ClassComponent使用的更新数组
|
||||
stateCallbacks: any[] | null, // 存放存在setState的第二个参数和HorizonDOM.render的第三个参数所在的node数组
|
||||
isForceUpdate: boolean, // 是否使用强制更新
|
||||
isSuspended, // 是否被suspense打断更新
|
||||
state: any, // ClassComponent和TreeRoot的状态
|
||||
hooks: Array<Hook<any, any>> | null, // 保存hook
|
||||
depContexts: Array<ContextType<any>> | null, // FunctionComponent和ClassComponent对context的依赖列表
|
||||
isDepContextChange: boolean, // context是否变更
|
||||
dirtyNodes: Array<VNode> | null, // 需要改动的节点数组
|
||||
shouldUpdate,
|
||||
childShouldUpdate,
|
||||
task: any,
|
||||
|
||||
// 使用这个变量来记录修改前的值,用于恢复。
|
||||
context: any,
|
||||
// 因为LazyComponent会修改tag和type属性,为了能识别,增加一个属性
|
||||
isLazyComponent: boolean,
|
||||
|
||||
// 因为LazyComponent会修改type属性,为了在diff中判断是否可以复用,需要增加一个lazyType
|
||||
lazyType: any,
|
||||
flags,
|
||||
clearChild: VNode | null,
|
||||
// one tree相关属性
|
||||
isCreated,
|
||||
oldHooks: Array<Hook<any, any>> | null, // 保存上一次执行的hook
|
||||
oldState: any,
|
||||
oldRef: RefType | ((handle: any) => void) | null,
|
||||
oldChild: VNode | null,
|
||||
promiseResolve: boolean, // suspense的promise是否resolve
|
||||
devProps: any, // 用于dev插件临时保存更新props值
|
||||
suspenseState: SuspenseState | null,
|
||||
|
||||
path: string, // 保存从根到本节点的路径
|
||||
|
||||
// 根节点数据
|
||||
toUpdateNodes: Set<VNode> | null, // 保存要更新的节点
|
||||
delegatedEvents: Set<string>,
|
||||
|
||||
belongClassVNode: VNode | null, // 记录JSXElement所属class vNode,处理ref的时候使用
|
||||
|
||||
// 状态管理器HorizonX使用
|
||||
isStoreChange: boolean,
|
||||
observers: Set<any> | null, // 记录这个函数组件/类组件依赖哪些Observer
|
||||
classComponentWillUnmount: Function | null, // HorizonX会在classComponentWillUnmount中清除对VNode的引入用
|
||||
src: Source | null, // 节点所在代码位置
|
||||
|
||||
// reactive
|
||||
attrRContexts: Set<any> | null,
|
||||
compRContext: any | null,
|
||||
}
|
||||
|
|
|
@ -23,11 +23,14 @@ import {
|
|||
createFragmentVNode,
|
||||
createPortalVNode,
|
||||
createDomTextVNode,
|
||||
createReactiveVNode,
|
||||
} from '../vnode/VNodeCreator';
|
||||
import { isSameType, getIteratorFn, isTextType, isIteratorType, isObjectType } from './DiffTools';
|
||||
import { travelChildren } from '../vnode/VNodeUtils';
|
||||
import { markVNodePath } from '../utils/vNodePath';
|
||||
import { BELONG_CLASS_VNODE_KEY } from '../vnode/VNode';
|
||||
import { Operation } from '../../reactive/DiffUtils';
|
||||
import { isReactiveProxy, isReactiveObj, getRNodeFromProxy } from '../../reactive/Utils';
|
||||
|
||||
enum DiffCategory {
|
||||
TEXT_NODE = 'TEXT_NODE',
|
||||
|
@ -101,6 +104,9 @@ function getNodeType(newChild: any): string | null {
|
|||
return DiffCategory.TEXT_NODE;
|
||||
}
|
||||
if (isObjectType(newChild)) {
|
||||
if (isReactiveObj(newChild)) {
|
||||
return DiffCategory.REACTIVE_NODE;
|
||||
}
|
||||
if (Array.isArray(newChild) || isIteratorType(newChild)) {
|
||||
return DiffCategory.ARR_NODE;
|
||||
}
|
||||
|
@ -152,6 +158,14 @@ function getNewNode(parentNode: VNode, newChild: any, oldNode: VNode | null) {
|
|||
}
|
||||
break;
|
||||
}
|
||||
case DiffCategory.REACTIVE_NODE: {
|
||||
if (oldNode === null || oldNode.tag !== ReactiveComponent) {
|
||||
resultNode = createReactiveVNode(newChild);
|
||||
} else {
|
||||
resultNode = updateVNode(oldNode, newChild);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DiffCategory.OBJECT_NODE: {
|
||||
if (newChild.vtype === TYPE_COMMON_ELEMENT) {
|
||||
if (newChild.type === TYPE_FRAGMENT) {
|
||||
|
@ -564,7 +578,7 @@ function diffObjectNodeHandler(
|
|||
}
|
||||
|
||||
let resultNode: VNode | null = null;
|
||||
let startDelVNode: VNode | null = firstChildVNode;
|
||||
let startDelVNode: VNode | null = node;
|
||||
if (newChild.vtype === TYPE_COMMON_ELEMENT) {
|
||||
if (canReuseNode) {
|
||||
// 可以复用
|
||||
|
@ -624,13 +638,91 @@ function diffObjectNodeHandler(
|
|||
return null;
|
||||
}
|
||||
|
||||
// 响应式For组件专用
|
||||
function diffReactiveForNodeHandler(
|
||||
parentNode: VNode,
|
||||
firstChild: VNode | null,
|
||||
newChildren: Array<any>,
|
||||
): VNode | null {
|
||||
let oldNode = firstChild;
|
||||
let nextOldNode: VNode | null = null;
|
||||
let resultingFirstChild: VNode | null = null;
|
||||
let prevNewNode: VNode | null = null;
|
||||
|
||||
function appendNode(newNode: VNode) {
|
||||
if (prevNewNode === null) {
|
||||
resultingFirstChild = newNode;
|
||||
newNode.cIndex = 0;
|
||||
} else {
|
||||
prevNewNode.next = newNode;
|
||||
newNode.cIndex = prevNewNode.cIndex + 1;
|
||||
}
|
||||
markVNodePath(newNode);
|
||||
prevNewNode = newNode;
|
||||
}
|
||||
|
||||
// 特殊处理
|
||||
// 如果新节点为空
|
||||
if (newChildren.length === 0) {
|
||||
if (firstChild) {
|
||||
FlagUtils.markClear(parentNode);
|
||||
parentNode.clearChild = firstChild;
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
let childIndex = 0;
|
||||
const { opts } = newChildren.diffOperator;
|
||||
for (let i = 0; i < opts.length; i++) {
|
||||
const opt = opts[i];
|
||||
if (oldNode !== null) {
|
||||
// 先保存next,因为getNewNode会修改next
|
||||
nextOldNode = oldNode.next;
|
||||
}
|
||||
|
||||
switch (opt.action) {
|
||||
case Operation.Nop: {
|
||||
const newNode = getNewNode(parentNode, newChildren[childIndex], oldNode);
|
||||
newNode.eIndex = childIndex;
|
||||
appendNode(newNode);
|
||||
// 使用了加1
|
||||
childIndex++;
|
||||
// 使用了oldNode,更新
|
||||
oldNode = nextOldNode;
|
||||
break;
|
||||
}
|
||||
case Operation.Insert: {
|
||||
const newNode = getNewNode(parentNode, newChildren[childIndex], null);
|
||||
FlagUtils.setAddition(newNode);
|
||||
newNode.eIndex = childIndex;
|
||||
appendNode(newNode);
|
||||
// 使用了加1
|
||||
childIndex++;
|
||||
break;
|
||||
}
|
||||
case Operation.Delete: {
|
||||
deleteVNode(parentNode, oldNode);
|
||||
// 使用了oldNode,更新
|
||||
oldNode = nextOldNode;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return resultingFirstChild;
|
||||
}
|
||||
|
||||
// Diff算法的对外接口
|
||||
export function createChildrenByDiff(
|
||||
parentNode: VNode,
|
||||
firstChild: VNode | null,
|
||||
newChild: any,
|
||||
child: any,
|
||||
isComparing: boolean
|
||||
): VNode | null {
|
||||
let newChild = isReactiveProxy(child) ? getRNodeFromProxy(child) : child;
|
||||
|
||||
const isFragment = isNoKeyFragment(newChild);
|
||||
newChild = isFragment ? newChild.props.children : newChild;
|
||||
|
||||
|
@ -649,7 +741,11 @@ export function createChildrenByDiff(
|
|||
|
||||
// 3. newChild是数组类型
|
||||
if (Array.isArray(newChild)) {
|
||||
return diffArrayNodesHandler(parentNode, firstChild, newChild);
|
||||
if (newChild.diffOperator) {
|
||||
return diffReactiveForNodeHandler(parentNode, firstChild, newChild);
|
||||
} else {
|
||||
return diffArrayNodesHandler(parentNode, firstChild, newChild);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. newChild是迭代器类型
|
||||
|
|
|
@ -24,6 +24,11 @@ import { useReducerImpl } from './UseReducerHook';
|
|||
import { useStateImpl } from './UseStateHook';
|
||||
import { getNewContext } from '../components/context/Context';
|
||||
import { getProcessingVNode } from '../GlobalVar';
|
||||
import { useAtomImpl } from './reactive/UseAtom';
|
||||
import { useComputedImpl } from './reactive/UseCompute';
|
||||
import { useReactiveImpl } from './reactive/UseReactive';
|
||||
import { AtomNode, PrimitiveType, Reactive } from '../../reactive/types';
|
||||
import { useWatchImpl } from './reactive/UseWatch';
|
||||
import type { MutableRef, RefCallBack, RefObject } from './HookType';
|
||||
|
||||
import type {
|
||||
|
@ -115,3 +120,23 @@ export function useImperativeHandle<T, R extends T>(
|
|||
|
||||
// 兼容react-redux
|
||||
export const useDebugValue = () => {};
|
||||
|
||||
// reactive hooks
|
||||
export function useAtom<T extends PrimitiveType>(initialValue: T): [AtomNode<T>, (value: T) => void] {
|
||||
return useAtomImpl(initialValue);
|
||||
}
|
||||
|
||||
export function useCompute<T>(compute: () => T) {
|
||||
return useComputedImpl(compute);
|
||||
}
|
||||
export function useComputed<T>(compute: () => T) {
|
||||
return useComputedImpl(compute);
|
||||
}
|
||||
|
||||
export function useReactive<T>(obj: T) {
|
||||
return useReactiveImpl<T>(obj);
|
||||
}
|
||||
|
||||
export function useWatch<T>(fn: () => any | Reactive, callback?: () => void) {
|
||||
return useWatchImpl<T>(fn, callback);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Copyright (c) 2023 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.
|
||||
*/
|
||||
|
||||
import { watch } from '../../horizonx/proxy/watch';
|
||||
|
||||
export function useWatchImpl(fn: () => void) {
|
||||
watch(fn);
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright (c) 2023 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.
|
||||
*/
|
||||
|
||||
import { getHookStage, HookStage } from '../HookStage';
|
||||
import { createHook, getCurrentHook, throwNotInFuncError } from '../BaseHook';
|
||||
import { disposeReactive } from '../../../reactive/RContext';
|
||||
import { useCallbackImpl } from '../UseCallbackHook';
|
||||
import { useEffectImpl } from '../UseEffectHook';
|
||||
import { Atom } from '../../../reactive/Atom';
|
||||
import { AtomNode, PrimitiveType } from '../../../reactive/types';
|
||||
|
||||
export function useAtomImpl<T extends PrimitiveType>(initialValue: T): [AtomNode<T>, (value: T) => void] {
|
||||
const stage = getHookStage();
|
||||
let atom: Atom<T>;
|
||||
|
||||
switch (stage) {
|
||||
case HookStage.Init:
|
||||
atom = new Atom(initialValue);
|
||||
createHook(atom);
|
||||
break;
|
||||
case HookStage.Update:
|
||||
atom = getCurrentHook().state as unknown as Atom;
|
||||
break;
|
||||
default:
|
||||
throwNotInFuncError();
|
||||
}
|
||||
|
||||
const setAtom = useCallbackImpl((value: T) => {
|
||||
atom.set(value);
|
||||
}, []);
|
||||
|
||||
// 组件销毁时,清除effect
|
||||
useEffectImpl(
|
||||
() => () => {
|
||||
disposeReactive(atom);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
return [atom, setAtom];
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright (c) 2023 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.
|
||||
*/
|
||||
|
||||
import { useMemoImpl } from '../UseMemoHook';
|
||||
import { Computed } from '../../../reactive/types';
|
||||
import { computed } from '../../../reactive/Computed';
|
||||
|
||||
export function useComputedImpl<T>(fn: () => T): Computed<T> {
|
||||
return useMemoImpl(() => {
|
||||
return computed(fn);
|
||||
}, []);
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright (c) 2023 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.
|
||||
*/
|
||||
|
||||
import { reactive } from '../../../reactive/Reactive';
|
||||
import { getHookStage, HookStage } from '../HookStage';
|
||||
import { createHook, getCurrentHook, throwNotInFuncError } from '../BaseHook';
|
||||
import { useEffectImpl } from '../UseEffectHook';
|
||||
import { disposeReactive } from '../../../reactive/RContext';
|
||||
import { Reactive, ReactiveProxy } from '../../../reactive/types';
|
||||
|
||||
export function useReactiveImpl<T>(obj) {
|
||||
const stage = getHookStage();
|
||||
let reactiveObj: any;
|
||||
|
||||
switch (stage) {
|
||||
case HookStage.Init:
|
||||
reactiveObj = reactive<T>(obj);
|
||||
|
||||
createHook(reactiveObj);
|
||||
break;
|
||||
case HookStage.Update:
|
||||
reactiveObj = getCurrentHook().state as unknown as Reactive<T>;
|
||||
break;
|
||||
default:
|
||||
throwNotInFuncError();
|
||||
}
|
||||
|
||||
// 组件销毁时,清除effect
|
||||
useEffectImpl(
|
||||
() => () => {
|
||||
disposeReactive(reactiveObj);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
return reactiveObj as ReactiveProxy<T>;
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright (c) 2023 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.
|
||||
*/
|
||||
|
||||
import { useEffectImpl } from '../UseEffectHook';
|
||||
import { Reactive } from '../../../reactive/types';
|
||||
import { watch } from '../../../reactive/Watch';
|
||||
import { useMemoImpl } from '../UseMemoHook';
|
||||
|
||||
export function useWatchImpl<T>(fn: () => any | Reactive, callback?: () => void): void {
|
||||
useMemoImpl(() => {
|
||||
watch(fn, callback);
|
||||
}, []);
|
||||
|
||||
// 组件销毁时
|
||||
useEffectImpl(() => () => {}, []);
|
||||
}
|
|
@ -34,6 +34,7 @@ import { processUpdates } from '../UpdateHandler';
|
|||
import { setProcessingClassVNode } from '../GlobalVar';
|
||||
import { onlyUpdateChildVNodes } from '../vnode/VNodeCreator';
|
||||
import { createChildrenByDiff } from '../diff/nodeDiffComparator';
|
||||
import { createComponentDependent } from '../../reactive/RContextCreator';
|
||||
|
||||
const emptyContextObj = {};
|
||||
// 获取当前节点的context
|
||||
|
@ -174,7 +175,7 @@ export function captureRender(processing: VNode): VNode | null {
|
|||
|
||||
// 不复用
|
||||
if (shouldUpdate) {
|
||||
return createChildren(ctor, processing);
|
||||
return createComponentDependent(() => createChildren(ctor, processing), processing);
|
||||
} else {
|
||||
return onlyUpdateChildVNodes(processing);
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ import { appendChildElement, newDom, initDomProps, getPropChangeList, isTextChil
|
|||
import { FlagUtils } from '../vnode/VNodeFlags';
|
||||
import { markRef } from './BaseComponent';
|
||||
import { DomComponent, DomPortal, DomText } from '../vnode/VNodeTags';
|
||||
import { travelVNodeTree } from '../vnode/VNodeUtils';
|
||||
import { isDomVNode, travelVNodeTree } from '../vnode/VNodeUtils';
|
||||
import { createChildrenByDiff } from '../diff/nodeDiffComparator';
|
||||
|
||||
function updateDom(processing: VNode, type: any, newProps: Props) {
|
||||
|
@ -74,7 +74,7 @@ export function bubbleRender(processing: VNode) {
|
|||
travelVNodeTree(
|
||||
vNode,
|
||||
node => {
|
||||
if (node.tag === DomComponent || node.tag === DomText) {
|
||||
if (isDomVNode(node)) {
|
||||
appendChildElement(dom, node.realNode);
|
||||
}
|
||||
},
|
||||
|
|
|
@ -22,6 +22,7 @@ import { ForwardRef } from '../vnode/VNodeTags';
|
|||
import { FlagUtils, Update } from '../vnode/VNodeFlags';
|
||||
import { onlyUpdateChildVNodes } from '../vnode/VNodeCreator';
|
||||
import { createChildrenByDiff } from '../diff/nodeDiffComparator';
|
||||
import { createComponentDependent } from '../../reactive/RContextCreator';
|
||||
|
||||
// 在useState, useReducer的时候,会触发state变化
|
||||
let stateChange = false;
|
||||
|
@ -56,10 +57,14 @@ export function captureFunctionComponent(processing: VNode, funcComp: any, nextP
|
|||
// 在执行exeFunctionHook前先设置stateChange为false
|
||||
setStateChange(false);
|
||||
|
||||
const newElements = runFunctionWithHooks(
|
||||
processing.tag === ForwardRef ? funcComp.render : funcComp,
|
||||
nextProps,
|
||||
processing.tag === ForwardRef ? processing.ref : undefined,
|
||||
const newElements = createComponentDependent(
|
||||
() =>
|
||||
runFunctionWithHooks(
|
||||
processing.tag === ForwardRef ? funcComp.render : funcComp,
|
||||
nextProps,
|
||||
processing.tag === ForwardRef ? processing.ref : undefined,
|
||||
processing
|
||||
),
|
||||
processing
|
||||
);
|
||||
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright (c) 2023 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.
|
||||
*/
|
||||
|
||||
import type { VNode } from '../Types';
|
||||
|
||||
import { throwIfTrue } from '../utils/throwIfTrue';
|
||||
import { newTextDom } from '../../dom/DOMOperator';
|
||||
import { subscribeReactiveComponent } from '../../reactive/RContextCreator';
|
||||
import { getRNode, getValue, isPrimitive } from '../../reactive/Utils';
|
||||
|
||||
export function captureRender(): VNode | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
export function bubbleRender(processing: VNode) {
|
||||
const text = processing.props;
|
||||
const newText = getValue(text);
|
||||
|
||||
if (!processing.isCreated && processing.realNode != null) {
|
||||
// 更新不需要处理
|
||||
} else {
|
||||
// 初始化
|
||||
if (!isPrimitive(newText)) {
|
||||
// 如果存在bug,可能出现这种情况
|
||||
throwIfTrue(processing.realNode === null, 'The reactive obj value must be a primitive.');
|
||||
}
|
||||
|
||||
// 获得对应节点
|
||||
processing.realNode = newTextDom(newText, processing);
|
||||
|
||||
// 监听Reactive
|
||||
subscribeReactiveComponent(processing.realNode, getRNode(text));
|
||||
}
|
||||
}
|
|
@ -27,6 +27,7 @@ import * as DomTextRender from './DomText';
|
|||
import * as LazyComponentRender from './LazyComponent';
|
||||
import * as MemoComponentRender from './MemoComponent';
|
||||
import * as SuspenseComponentRender from './SuspenseComponent';
|
||||
import * as ReactiveComponentRender from './ReactiveComponent';
|
||||
|
||||
import {
|
||||
ClassComponent,
|
||||
|
@ -42,6 +43,7 @@ import {
|
|||
LazyComponent,
|
||||
MemoComponent,
|
||||
SuspenseComponent,
|
||||
ReactiveComponent,
|
||||
} from '../vnode/VNodeTags';
|
||||
|
||||
export { BaseComponentRender };
|
||||
|
@ -60,4 +62,5 @@ export default {
|
|||
[LazyComponent]: LazyComponentRender,
|
||||
[MemoComponent]: MemoComponentRender,
|
||||
[SuspenseComponent]: SuspenseComponentRender,
|
||||
[ReactiveComponent]: ReactiveComponentRender,
|
||||
};
|
||||
|
|
|
@ -50,9 +50,10 @@ import {
|
|||
callUseLayoutEffectRemove,
|
||||
} from './HookEffectHandler';
|
||||
import { handleSubmitError } from '../ErrorHandler';
|
||||
import { travelVNodeTree, clearVNode, isDomVNode, getSiblingDom } from '../vnode/VNodeUtils';
|
||||
import { travelVNodeTree, clearVNode, isDomVNode, getSiblingDom, findDOMContainer, isDomContainer } from '../vnode/VNodeUtils';
|
||||
import { shouldAutoFocus } from '../../dom/utils/Common';
|
||||
import { BELONG_CLASS_VNODE_KEY } from '../vnode/VNode';
|
||||
import { cleanupRContext } from '../../reactive/RContext';
|
||||
|
||||
function callComponentWillUnmount(vNode: VNode, instance: any) {
|
||||
try {
|
||||
|
@ -205,48 +206,119 @@ function unmountDomComponents(vNode: VNode): void {
|
|||
// 这两个变量要一起更新
|
||||
let currentParent;
|
||||
|
||||
travelVNodeTree(
|
||||
vNode,
|
||||
node => {
|
||||
if (!currentParentIsValid) {
|
||||
let parent = node.parent;
|
||||
let tag;
|
||||
while (parent !== null) {
|
||||
tag = parent.tag;
|
||||
if (tag === DomComponent || tag === TreeRoot || tag === DomPortal) {
|
||||
currentParent = parent.realNode;
|
||||
break;
|
||||
let node = vNode;
|
||||
|
||||
while (true) {
|
||||
// handle
|
||||
if (!currentParentIsValid) {
|
||||
let parent = node.parent;
|
||||
let tag;
|
||||
while (parent !== null) {
|
||||
tag = parent.tag;
|
||||
if (tag === DomComponent || tag === TreeRoot || tag === DomPortal) {
|
||||
currentParent = parent.realNode;
|
||||
break;
|
||||
}
|
||||
parent = parent.parent;
|
||||
}
|
||||
currentParentIsValid = true;
|
||||
}
|
||||
|
||||
if (node.tag === DomComponent || node.tag === DomText) {
|
||||
let nd = node;
|
||||
|
||||
// 卸载vNode,递归遍历子vNode
|
||||
outer: while (true) {
|
||||
unmountVNode(nd);
|
||||
|
||||
// 找子节点
|
||||
const childVNode = nd.child;
|
||||
if (childVNode !== null && nd.tag !== DomPortal) {
|
||||
childVNode.parent = nd;
|
||||
nd = childVNode;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 回到开始节点
|
||||
if (nd === node) {
|
||||
break;
|
||||
}
|
||||
|
||||
// 找兄弟,没有就往上再找兄弟
|
||||
while (nd.next === null) {
|
||||
if (nd.parent === null || nd.parent === node) {
|
||||
break outer;
|
||||
}
|
||||
parent = parent.parent;
|
||||
nd = nd.parent;
|
||||
}
|
||||
currentParentIsValid = true;
|
||||
// 找到兄弟
|
||||
const siblingVNode = nd.next;
|
||||
siblingVNode.parent = nd.parent;
|
||||
nd = siblingVNode;
|
||||
}
|
||||
|
||||
if (node.tag === DomComponent || node.tag === DomText) {
|
||||
// 卸载vNode,递归遍历子vNode
|
||||
unmountNestedVNodes(node);
|
||||
|
||||
// 在所有子项都卸载后,删除dom树中的节点
|
||||
removeChildDom(currentParent, node.realNode);
|
||||
} else if (node.tag === DomPortal) {
|
||||
if (node.child !== null) {
|
||||
currentParent = node.realNode;
|
||||
}
|
||||
} else {
|
||||
unmountVNode(node);
|
||||
// 在所有子项都卸载后,删除dom树中的节点
|
||||
removeChildDom(currentParent, node.realNode);
|
||||
} else if (node.tag === DomPortal) {
|
||||
if (node.child !== null) {
|
||||
currentParent = node.realNode;
|
||||
}
|
||||
},
|
||||
node =>
|
||||
// 如果是dom不用再遍历child
|
||||
node.tag === DomComponent || node.tag === DomText,
|
||||
vNode,
|
||||
node => {
|
||||
} else {
|
||||
unmountVNode(node);
|
||||
}
|
||||
|
||||
// 找子节点
|
||||
const childVNode = node.child;
|
||||
if (childVNode !== null && node.tag !== DomComponent && node.tag !== DomText) {
|
||||
childVNode.parent = node;
|
||||
node = childVNode;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 回到开始节点
|
||||
if (node === vNode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 找兄弟,没有就往上再找兄弟
|
||||
while (node.next === null) {
|
||||
if (node.parent === null || node.parent === vNode) {
|
||||
return null;
|
||||
}
|
||||
node = node.parent;
|
||||
|
||||
if (node.tag === DomPortal) {
|
||||
// 当离开portal,需要重新设置parent
|
||||
currentParentIsValid = false;
|
||||
}
|
||||
}
|
||||
);
|
||||
// 找到兄弟
|
||||
const siblingVNode = node.next;
|
||||
siblingVNode.parent = node.parent;
|
||||
node = siblingVNode;
|
||||
}
|
||||
}
|
||||
|
||||
// dom节点上的attr值,如果是reactive对象,会自动创建attrEffect。所以在清除时,需要销毁attrEffect。
|
||||
function detachAttrRContexts(vNode: VNode) {
|
||||
const attrRContexts = vNode.attrRContexts;
|
||||
if (attrRContexts) {
|
||||
for (const attrRContext of attrRContexts) {
|
||||
cleanupRContext(attrRContext);
|
||||
attrRContext.callback = null;
|
||||
}
|
||||
|
||||
attrRContexts.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// 函数组件和Class组件会自动创rContext,所以在清除时,需要销毁rContext
|
||||
function detachCompRContext(vNode: VNode) {
|
||||
const rContext = vNode.compRContext;
|
||||
if (rContext) {
|
||||
cleanupRContext(rContext);
|
||||
rContext.callback = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 卸载一个vNode,不会递归
|
||||
|
@ -256,6 +328,8 @@ function unmountVNode(vNode: VNode): void {
|
|||
case ForwardRef:
|
||||
case MemoComponent: {
|
||||
callEffectRemove(vNode);
|
||||
|
||||
detachCompRContext(vNode);
|
||||
break;
|
||||
}
|
||||
case ClassComponent: {
|
||||
|
@ -273,10 +347,14 @@ function unmountVNode(vNode: VNode): void {
|
|||
vNode.classComponentWillUnmount(vNode);
|
||||
vNode.classComponentWillUnmount = null;
|
||||
}
|
||||
|
||||
detachCompRContext(vNode);
|
||||
break;
|
||||
}
|
||||
case DomComponent: {
|
||||
detachRef(vNode);
|
||||
|
||||
detachAttrRContexts(vNode);
|
||||
break;
|
||||
}
|
||||
case DomPortal: {
|
||||
|
@ -390,6 +468,64 @@ function submitClear(vNode: VNode): void {
|
|||
vNode.clearChild = null;
|
||||
}
|
||||
|
||||
function submitClear2(vNode: VNode): void {
|
||||
let domVNode;
|
||||
if (isDomContainer(vNode)) {
|
||||
domVNode = vNode;
|
||||
}
|
||||
|
||||
// 1、拿到最近的父DOM
|
||||
domVNode = findDOMContainer(vNode);
|
||||
const dom = domVNode.realNode;
|
||||
|
||||
// 2、复制DOM节点
|
||||
const cloneDom = dom.cloneNode(false);
|
||||
|
||||
// 3、复制cloneNode未能复制的属性
|
||||
// 真实 dom 获取的keys只包含新增的属性
|
||||
// 比如真实 dom 拿到的 keys 一般只有两个 horizon 自定义属性
|
||||
// 但考虑到用户可能自定义其他属性,所以采用遍历赋值的方式
|
||||
const customizeKeys = Object.keys(dom);
|
||||
const keyLength = customizeKeys.length;
|
||||
for (let i = 0; i < keyLength; i++) {
|
||||
const key = customizeKeys[i];
|
||||
// 测试代码 mock 实例的全部可遍历属性都会被Object.keys方法读取到
|
||||
// children 属性被复制意味着复制了子节点,因此要排除
|
||||
if (key !== 'children') {
|
||||
cloneDom[key] = dom[key];
|
||||
}
|
||||
}
|
||||
|
||||
// 4、拿到最近的父DOM的父DOM
|
||||
const parentDomVNode = findDOMContainer(domVNode);
|
||||
const parentDom = parentDomVNode.realNode;
|
||||
|
||||
// 5、执行unmount
|
||||
let clearChild = vNode.clearChild as VNode;
|
||||
// 卸载 clearChild 和 它的兄弟节点
|
||||
while (clearChild) {
|
||||
// 卸载子vNode,递归遍历子vNode
|
||||
unmountNestedVNodes(clearChild);
|
||||
clearVNode(clearChild);
|
||||
clearChild = clearChild.next as VNode;
|
||||
}
|
||||
|
||||
// 6、在所有子项都卸载后,删除dom树中的节点
|
||||
removeChildDom(parentDom, dom);
|
||||
|
||||
// 7、插入cloneDom
|
||||
const realNodeNext = getSiblingDom(domVNode);
|
||||
insertDom(parentDom, cloneDom, realNodeNext);
|
||||
|
||||
// 8、重置realNode
|
||||
domVNode.realNode = cloneDom;
|
||||
attachRef(domVNode);
|
||||
|
||||
// 9、清理
|
||||
FlagUtils.removeFlag(vNode, Clear);
|
||||
vNode.clearChild = null;
|
||||
}
|
||||
|
||||
function submitDeletion(vNode: VNode): void {
|
||||
// 遍历所有子节点:删除dom节点,detach ref 和 调用componentWillUnmount()
|
||||
unmountDomComponents(vNode);
|
||||
|
@ -440,6 +576,7 @@ export {
|
|||
submitAddition,
|
||||
submitDeletion,
|
||||
submitClear,
|
||||
submitClear2,
|
||||
submitUpdate,
|
||||
callAfterSubmitLifeCycles,
|
||||
attachRef,
|
||||
|
|
|
@ -28,6 +28,7 @@ import {
|
|||
submitUpdate,
|
||||
detachRef,
|
||||
submitClear,
|
||||
submitClear2,
|
||||
} from './LifeCycleHandler';
|
||||
import { tryRenderFromRoot } from '../TreeBuilder';
|
||||
import { InRender, copyExecuteMode, setExecuteMode, changeMode } from '../ExecuteMode';
|
||||
|
@ -98,6 +99,7 @@ function submit(dirtyNodes: Array<VNode>) {
|
|||
submitDeletion(node);
|
||||
}
|
||||
if (isClear) {
|
||||
// submitClear2(node);
|
||||
submitClear(node);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ const PATH_DELIMITER = ',';
|
|||
* @param vNode
|
||||
*/
|
||||
export function markVNodePath(vNode: VNode) {
|
||||
vNode.path = `${vNode.parent!.path}${PATH_DELIMITER}${vNode.cIndex}`;
|
||||
// vNode.path = `${vNode.parent!.path}${PATH_DELIMITER}${vNode.cIndex}`;
|
||||
}
|
||||
|
||||
export function getPathArr(vNode: VNode) {
|
||||
|
|
|
@ -17,194 +17,152 @@
|
|||
* 虚拟DOM结构体
|
||||
*/
|
||||
import {
|
||||
TreeRoot,
|
||||
FunctionComponent,
|
||||
ClassComponent,
|
||||
ContextConsumer,
|
||||
ContextProvider,
|
||||
DomComponent,
|
||||
DomPortal,
|
||||
DomText,
|
||||
ContextConsumer,
|
||||
ForwardRef,
|
||||
SuspenseComponent,
|
||||
LazyComponent,
|
||||
DomComponent,
|
||||
Fragment,
|
||||
ContextProvider,
|
||||
Profiler,
|
||||
FunctionComponent,
|
||||
LazyComponent,
|
||||
MemoComponent,
|
||||
Profiler,
|
||||
SuspenseComponent,
|
||||
TreeRoot,
|
||||
} from './VNodeTags';
|
||||
import type { VNodeTag } from './VNodeTags';
|
||||
import type { RefType, ContextType, SuspenseState, Source } from '../Types';
|
||||
import type { Hook } from '../hooks/HookType';
|
||||
import { InitFlag } from './VNodeFlags';
|
||||
import { Observer } from '../../inulax/proxy/Observer';
|
||||
import { VNode } from '../Types';
|
||||
|
||||
export const BELONG_CLASS_VNODE_KEY = typeof Symbol === 'function' ? Symbol('belongClassVNode') : 'belongClassVNode';
|
||||
|
||||
export class VNode {
|
||||
tag: VNodeTag;
|
||||
key: string | null; // 唯一标识符
|
||||
props: any; // 传给组件的props的值,类组件包含defaultProps,Lazy组件不包含
|
||||
type: any = null;
|
||||
realNode: any; // 如果是类,则存放实例;如果是div这种,则存放真实DOM;
|
||||
export function VirtualNode(tag: VNodeTag, props: any, key: null | string, realNode) {
|
||||
this.tag = tag; // 对应组件的类型,比如ClassComponent等
|
||||
// 唯一标识符
|
||||
// if (isReactiveObj(this.key)) {
|
||||
// this.key = getReactiveValue(this.key);
|
||||
// subscribeKeyEffect(this, this.key);
|
||||
// } else {
|
||||
this.key = key;
|
||||
// }
|
||||
|
||||
this.props = props; // 传给组件的props的值,类组件包含defaultProps,Lazy组件不包含
|
||||
this.type = null;
|
||||
this.realNode = null; // 如果是类,则存放实例;如果是div这种,则存放真实DOM;
|
||||
|
||||
// 关系结构
|
||||
parent: VNode | null = null; // 父节点
|
||||
child: VNode | null = null; // 子节点
|
||||
next: VNode | null = null; // 兄弟节点
|
||||
cIndex = 0; // 节点在children数组中的位置
|
||||
eIndex = 0; // InulaElement在jsx中的位置,例如:jsx中的null不会生成vNode,所以eIndex和cIndex不一致
|
||||
this.parent = null; // 父节点
|
||||
this.child = null; // 子节点
|
||||
this.next = null; // 兄弟节点
|
||||
this.cIndex = 0; // 节点在children数组中的位置
|
||||
this.eIndex = 0; // HorizonElement在jsx中的位置,例如:jsx中的null不会生成vNode,所以eIndex和cIndex不一致
|
||||
|
||||
ref: RefType | ((handle: any) => void) | null = null; // 包裹一个函数,submit阶段使用,比如将外部useRef生成的对象赋值到ref上
|
||||
oldProps: any = null;
|
||||
this.ref = null; // 包裹一个函数,submit阶段使用,比如将外部useRef生成的对象赋值到ref上
|
||||
this.oldProps = null;
|
||||
|
||||
// 是否已经被从树上移除
|
||||
isCleared = false;
|
||||
changeList: any; // DOM的变更列表
|
||||
effectList: any[] | null; // useEffect 的更新数组
|
||||
updates: any[] | null; // TreeRoot和ClassComponent使用的更新数组
|
||||
stateCallbacks: any[] | null; // 存放存在setState的第二个参数和InulaDOM.render的第三个参数所在的node数组
|
||||
isForceUpdate: boolean; // 是否使用强制更新
|
||||
isSuspended = false; // 是否被suspense打断更新
|
||||
state: any; // ClassComponent和TreeRoot的状态
|
||||
hooks: Array<Hook<any, any>> | null; // 保存hook
|
||||
depContexts: Array<ContextType<any>> | null; // FunctionComponent和ClassComponent对context的依赖列表
|
||||
isDepContextChange: boolean; // context是否变更
|
||||
dirtyNodes: Array<VNode> | null = null; // 需要改动的节点数组
|
||||
shouldUpdate = false;
|
||||
childShouldUpdate = false;
|
||||
task: any;
|
||||
this.dirtyNodes = null; // 需要改动的节点数组
|
||||
this.shouldUpdate = false;
|
||||
this.childShouldUpdate = false;
|
||||
|
||||
// 使用这个变量来记录修改前的值,用于恢复。
|
||||
context: any;
|
||||
// 因为LazyComponent会修改tag和type属性,为了能识别,增加一个属性
|
||||
isLazyComponent: boolean;
|
||||
this.flags = InitFlag;
|
||||
this.clearChild = null;
|
||||
this.isCreated = true;
|
||||
this.oldRef = null;
|
||||
this.oldChild = null;
|
||||
|
||||
// 因为LazyComponent会修改type属性,为了在diff中判断是否可以复用,需要增加一个lazyType
|
||||
lazyType: any;
|
||||
flags = InitFlag;
|
||||
clearChild: VNode | null;
|
||||
// one tree相关属性
|
||||
isCreated = true;
|
||||
oldHooks: Array<Hook<any, any>> | null; // 保存上一次执行的hook
|
||||
oldState: any;
|
||||
oldRef: RefType | ((handle: any) => void) | null = null;
|
||||
oldChild: VNode | null = null;
|
||||
promiseResolve: boolean; // suspense的promise是否resolve
|
||||
devProps: any; // 用于dev插件临时保存更新props值
|
||||
suspenseState: SuspenseState | null;
|
||||
|
||||
path = ''; // 保存从根到本节点的路径
|
||||
|
||||
// 根节点数据
|
||||
toUpdateNodes: Set<VNode> | null; // 保存要更新的节点
|
||||
delegatedEvents: Set<string>;
|
||||
|
||||
// @ts-ignore
|
||||
[BELONG_CLASS_VNODE_KEY]: VNode | null = null; // 记录JSXElement所属class vNode,处理ref的时候使用
|
||||
|
||||
// 状态管理器InulaX使用
|
||||
isStoreChange: boolean;
|
||||
observers: Set<Observer> | null = null; // 记录这个函数组件/类组件依赖哪些Observer
|
||||
classComponentWillUnmount: ((vNode: VNode) => any) | null; // InulaX会在classComponentWillUnmount中清除对VNode的引入用
|
||||
src: Source | null; // 节点所在代码位置
|
||||
|
||||
constructor(tag: VNodeTag, props: any, key: null | string, realNode) {
|
||||
this.tag = tag; // 对应组件的类型,比如ClassComponent等
|
||||
this.key = key;
|
||||
|
||||
this.props = props;
|
||||
|
||||
switch (tag) {
|
||||
case TreeRoot:
|
||||
this.realNode = realNode;
|
||||
this.task = null;
|
||||
this.toUpdateNodes = new Set<VNode>();
|
||||
this.delegatedEvents = new Set<string>();
|
||||
this.updates = null;
|
||||
this.stateCallbacks = null;
|
||||
this.state = null;
|
||||
this.oldState = null;
|
||||
this.context = null;
|
||||
break;
|
||||
case FunctionComponent:
|
||||
this.realNode = null;
|
||||
this.effectList = null;
|
||||
this.hooks = null;
|
||||
this.depContexts = null;
|
||||
this.isDepContextChange = false;
|
||||
this.oldHooks = null;
|
||||
this.isStoreChange = false;
|
||||
this.observers = null;
|
||||
this.classComponentWillUnmount = null;
|
||||
this.src = null;
|
||||
break;
|
||||
case ClassComponent:
|
||||
this.realNode = null;
|
||||
this.updates = null;
|
||||
this.stateCallbacks = null;
|
||||
this.isForceUpdate = false;
|
||||
this.state = null;
|
||||
this.depContexts = null;
|
||||
this.isDepContextChange = false;
|
||||
this.oldState = null;
|
||||
this.context = null;
|
||||
this.isStoreChange = false;
|
||||
this.observers = null;
|
||||
this.classComponentWillUnmount = null;
|
||||
this.src = null;
|
||||
break;
|
||||
case DomPortal:
|
||||
this.realNode = null;
|
||||
this.context = null;
|
||||
this.delegatedEvents = new Set<string>();
|
||||
this.src = null;
|
||||
break;
|
||||
case DomComponent:
|
||||
this.realNode = null;
|
||||
this.changeList = null;
|
||||
this.context = null;
|
||||
this.src = null;
|
||||
break;
|
||||
case DomText:
|
||||
this.realNode = null;
|
||||
break;
|
||||
case SuspenseComponent:
|
||||
this.realNode = null;
|
||||
this.suspenseState = {
|
||||
promiseSet: null,
|
||||
didCapture: false,
|
||||
promiseResolved: false,
|
||||
oldChildStatus: '',
|
||||
childStatus: '',
|
||||
};
|
||||
this.src = null;
|
||||
break;
|
||||
case ContextProvider:
|
||||
this.src = null;
|
||||
this.context = null;
|
||||
break;
|
||||
case MemoComponent:
|
||||
this.effectList = null;
|
||||
this.src = null;
|
||||
break;
|
||||
case LazyComponent:
|
||||
this.realNode = null;
|
||||
this.stateCallbacks = null;
|
||||
this.isLazyComponent = true;
|
||||
this.lazyType = null;
|
||||
this.updates = null;
|
||||
this.src = null;
|
||||
break;
|
||||
case Fragment:
|
||||
break;
|
||||
case ContextConsumer:
|
||||
break;
|
||||
case ForwardRef:
|
||||
break;
|
||||
case Profiler:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
switch (tag) {
|
||||
case TreeRoot:
|
||||
this.realNode = realNode;
|
||||
this.task = null;
|
||||
this.toUpdateNodes = new Set<VNode>();
|
||||
this.delegatedEvents = new Set<string>();
|
||||
this.updates = null;
|
||||
this.stateCallbacks = null;
|
||||
this.state = null;
|
||||
this.oldState = null;
|
||||
this.context = null;
|
||||
break;
|
||||
case FunctionComponent:
|
||||
this.realNode = null;
|
||||
this.effectList = null;
|
||||
this.hooks = null;
|
||||
this.depContexts = null;
|
||||
this.isDepContextChange = false;
|
||||
this.oldHooks = null;
|
||||
this.isStoreChange = false;
|
||||
this.observers = null;
|
||||
this.classComponentWillUnmount = null;
|
||||
this.src = null;
|
||||
this.compRContext = null;
|
||||
break;
|
||||
case ClassComponent:
|
||||
this.realNode = null;
|
||||
this.updates = null;
|
||||
this.stateCallbacks = null;
|
||||
this.isForceUpdate = false;
|
||||
this.state = null;
|
||||
this.depContexts = null;
|
||||
this.isDepContextChange = false;
|
||||
this.oldState = null;
|
||||
this.context = null;
|
||||
this.isStoreChange = false;
|
||||
this.observers = null;
|
||||
this.classComponentWillUnmount = null;
|
||||
this.src = null;
|
||||
this.compRContext = null;
|
||||
break;
|
||||
case DomPortal:
|
||||
this.realNode = null;
|
||||
this.context = null;
|
||||
this.delegatedEvents = new Set<string>();
|
||||
this.src = null;
|
||||
break;
|
||||
case DomComponent:
|
||||
this.realNode = null;
|
||||
this.changeList = null;
|
||||
this.context = null;
|
||||
this.src = null;
|
||||
this.attrRContexts = null;
|
||||
break;
|
||||
case DomText:
|
||||
this.realNode = null;
|
||||
break;
|
||||
case SuspenseComponent:
|
||||
this.realNode = null;
|
||||
this.suspenseState = {
|
||||
promiseSet: null,
|
||||
didCapture: false,
|
||||
promiseResolved: false,
|
||||
oldChildStatus: '',
|
||||
childStatus: '',
|
||||
};
|
||||
this.src = null;
|
||||
break;
|
||||
case ContextProvider:
|
||||
this.src = null;
|
||||
this.context = null;
|
||||
break;
|
||||
case MemoComponent:
|
||||
this.effectList = null;
|
||||
this.src = null;
|
||||
break;
|
||||
case LazyComponent:
|
||||
this.realNode = null;
|
||||
this.stateCallbacks = null;
|
||||
this.isLazyComponent = true;
|
||||
this.lazyType = null;
|
||||
this.updates = null;
|
||||
this.src = null;
|
||||
break;
|
||||
case Fragment:
|
||||
break;
|
||||
case ContextConsumer:
|
||||
break;
|
||||
case ForwardRef:
|
||||
break;
|
||||
case Profiler:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ import {
|
|||
LazyComponent,
|
||||
MemoComponent,
|
||||
SuspenseComponent,
|
||||
ReactiveComponent,
|
||||
} from './VNodeTags';
|
||||
import {
|
||||
TYPE_CONTEXT,
|
||||
|
@ -41,8 +42,8 @@ import {
|
|||
TYPE_STRICT_MODE,
|
||||
TYPE_SUSPENSE,
|
||||
} from '../../external/JSXElementType';
|
||||
import { VNode } from './VNode';
|
||||
import { JSXElement, Source } from '../Types';
|
||||
import { VirtualNode } from './VNode';
|
||||
import { JSXElement, Source, VNode } from '../Types';
|
||||
import { markVNodePath } from '../utils/vNodePath';
|
||||
|
||||
const typeLazyMap = {
|
||||
|
@ -57,7 +58,7 @@ const typeMap = {
|
|||
};
|
||||
|
||||
function newVirtualNode(tag: VNodeTag, key?: null | string, vNodeProps?: any, realNode?: any): VNode {
|
||||
return new VNode(tag, vNodeProps, key as null | string, realNode);
|
||||
return new VirtualNode(tag, vNodeProps, key as null | string, realNode);
|
||||
}
|
||||
|
||||
function isClassComponent(comp: Function) {
|
||||
|
@ -106,6 +107,12 @@ export function createFragmentVNode(fragmentKey, fragmentProps) {
|
|||
return vNode;
|
||||
}
|
||||
|
||||
export function createReactiveVNode(content) {
|
||||
const vNode = newVirtualNode(ReactiveComponent, null, content);
|
||||
vNode.shouldUpdate = true;
|
||||
return vNode;
|
||||
}
|
||||
|
||||
export function createDomTextVNode(content) {
|
||||
const vNode = newVirtualNode(DomText, null, content);
|
||||
vNode.shouldUpdate = true;
|
||||
|
@ -231,32 +238,32 @@ export function onlyUpdateChildVNodes(processing: VNode): VNode | null {
|
|||
}
|
||||
|
||||
// 当跳过子树更新时,父节点path更新时,需要更新所有子树path
|
||||
if (processing.child && processing.path !== processing.child.path.slice(0, processing.path.length)) {
|
||||
// bfs更新子树path
|
||||
const queue: VNode[] = [];
|
||||
|
||||
const putChildrenIntoQueue = (vNode: VNode) => {
|
||||
const child = vNode.child;
|
||||
if (child) {
|
||||
queue.push(child);
|
||||
let sibling = child.next;
|
||||
while (sibling) {
|
||||
queue.push(sibling);
|
||||
sibling = sibling.next;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
putChildrenIntoQueue(processing);
|
||||
|
||||
while (queue.length) {
|
||||
const vNode = queue.shift()!;
|
||||
|
||||
markVNodePath(vNode);
|
||||
|
||||
putChildrenIntoQueue(vNode);
|
||||
}
|
||||
}
|
||||
// if (processing.child && processing.path !== processing.child.path.slice(0, processing.path.length)) {
|
||||
// // bfs更新子树path
|
||||
// const queue: VNode[] = [];
|
||||
//
|
||||
// const putChildrenIntoQueue = (vNode: VNode) => {
|
||||
// const child = vNode.child;
|
||||
// if (child) {
|
||||
// queue.push(child);
|
||||
// let sibling = child.next;
|
||||
// while (sibling) {
|
||||
// queue.push(sibling);
|
||||
// sibling = sibling.next;
|
||||
// }
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// putChildrenIntoQueue(processing);
|
||||
//
|
||||
// while (queue.length) {
|
||||
// const vNode = queue.shift()!;
|
||||
//
|
||||
// markVNodePath(vNode);
|
||||
//
|
||||
// putChildrenIntoQueue(vNode);
|
||||
// }
|
||||
// }
|
||||
// 子树无需工作
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -33,3 +33,4 @@ export const SuspenseComponent = 'SuspenseComponent';
|
|||
export const MemoComponent = 'MemoComponent';
|
||||
export const LazyComponent = 'LazyComponent';
|
||||
export const IncompleteClassComponent = 'IncompleteClassComponent';
|
||||
export const ReactiveComponent = 'ReactiveComponent';
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
import type { VNode } from '../Types';
|
||||
|
||||
import { DomComponent, DomPortal, DomText, TreeRoot } from './VNodeTags';
|
||||
import { DomComponent, DomPortal, DomText, TreeRoot, ReactiveComponent } from './VNodeTags';
|
||||
import { getNearestVNode } from '../../dom/DOMInternalKeys';
|
||||
import { Addition, InitFlag } from './VNodeFlags';
|
||||
import { BELONG_CLASS_VNODE_KEY } from './VNode';
|
||||
|
@ -129,15 +129,22 @@ export function clearVNode(vNode: VNode) {
|
|||
const hook = window.__INULA_DEV_HOOK__;
|
||||
hook.deleteVNode(vNode);
|
||||
}
|
||||
|
||||
if (vNode.attrRContexts) {
|
||||
vNode.attrRContexts = null;
|
||||
}
|
||||
if (vNode.compRContext) {
|
||||
vNode.compRContext = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 是dom类型的vNode
|
||||
export function isDomVNode(node: VNode) {
|
||||
return node.tag === DomComponent || node.tag === DomText;
|
||||
return node.tag === DomComponent || node.tag === DomText || node.tag === ReactiveComponent;
|
||||
}
|
||||
|
||||
// 是容器类型的vNode
|
||||
function isDomContainer(vNode: VNode): boolean {
|
||||
export function isDomContainer(vNode: VNode): boolean {
|
||||
return vNode.tag === DomComponent || vNode.tag === TreeRoot || vNode.tag === DomPortal;
|
||||
}
|
||||
|
||||
|
@ -277,3 +284,15 @@ export function findRoot(targetVNode, targetDom) {
|
|||
}
|
||||
return targetVNode;
|
||||
}
|
||||
|
||||
export function findDOMContainer(vNode: VNode): VNode {
|
||||
let parent = vNode.parent;
|
||||
while (parent !== null) {
|
||||
if (isDomContainer(parent)) {
|
||||
break;
|
||||
}
|
||||
parent = parent.parent;
|
||||
}
|
||||
|
||||
return parent as VNode;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue