fix(reactivity): fix for
This commit is contained in:
parent
00418eba82
commit
c7de85630e
|
@ -31,10 +31,8 @@ export function pruneUnusedBit(
|
|||
index = 1,
|
||||
bitPositionToRemoveInParent: number[] = []
|
||||
) {
|
||||
// dfs the component tree
|
||||
// To store the bitmap of the properties
|
||||
const bitMap = new Map<string, number>();
|
||||
const bitPositionToRemove: number[] = [...bitPositionToRemoveInParent];
|
||||
// dfs the component tree
|
||||
comp.variables.forEach(v => {
|
||||
if (v.type === 'reactive') {
|
||||
// get the origin bit, computed should keep the highest bit, etc. 0b0111 -> 0b0100
|
||||
|
@ -42,9 +40,7 @@ export function pruneUnusedBit(
|
|||
|
||||
if (comp.usedBit & originBit) {
|
||||
v.bit = 1 << (index - bitPositionToRemove.length - 1);
|
||||
bitMap.set(v.name, v.bit);
|
||||
if (v.dependency) {
|
||||
// 去掉最高位
|
||||
v.dependency.depMask = getDepMask(v.dependency.depBitmaps, bitPositionToRemove);
|
||||
}
|
||||
} else {
|
||||
|
@ -91,11 +87,19 @@ function pruneBitmap(depMask: Bitmap, bitPositionToRemove: number[]) {
|
|||
return parseInt(result, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the depMask by pruning the bitPositionToRemove
|
||||
* The reason why we need to get the depMask from depBitmaps instead of fullDepMask is that
|
||||
* the fullDepMask contains the bit of used variables, which is not the direct dependency
|
||||
*
|
||||
* @param depBitmaps
|
||||
* @param bitPositionToRemove
|
||||
*/
|
||||
function getDepMask(depBitmaps: Bitmap[], bitPositionToRemove: number[]) {
|
||||
// prune each dependency bitmap and combine them
|
||||
return depBitmaps.reduce((acc, cur) => {
|
||||
const a = pruneBitmap(cur, bitPositionToRemove);
|
||||
return keepHighestBit(a) | acc;
|
||||
// computed should keep the highest bit, others should be pruned
|
||||
return keepHighestBit(pruneBitmap(cur, bitPositionToRemove)) | acc;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
|
|
|
@ -56,4 +56,21 @@ describe('viewAnalyze', () => {
|
|||
expect(div.children[0].content.depMask).toEqual(0b1);
|
||||
expect(genCode(div.children[0].content.dependenciesNode)).toMatchInlineSnapshot(`"[info?.firstName]"`);
|
||||
});
|
||||
|
||||
it('should analyze for loop', () => {
|
||||
const root = analyze(/*js*/ `
|
||||
Component(({}) => {
|
||||
const unused = 0;
|
||||
const prefix = 'test';
|
||||
const list = [{name: 1}, {name: 2}, {name: 3}];
|
||||
const list2 = [{name: 4}, {name: 5}, {name: 6}];
|
||||
return <for each={[...list, ...list2]}>{
|
||||
({name}) => <div>{prefix + name}</div>
|
||||
}</for>;
|
||||
});
|
||||
`);
|
||||
const forNode = root.children![0] as any;
|
||||
expect(forNode.children[0].children[0].content.depMask).toEqual(0b111);
|
||||
expect(genCode(forNode.children[0].children[0].content.dependenciesNode)).toMatchInlineSnapshot(`"[prefix, name]"`);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -83,4 +83,15 @@ describe('jsx slice', () => {
|
|||
}"
|
||||
`);
|
||||
});
|
||||
|
||||
// TODO: Fix this test
|
||||
it.skip('should work with jsx slice in function', () => {
|
||||
// function App() {
|
||||
// const fn = ([x, y, z]) => {
|
||||
// return <div>{x}, {y}, {z}</div>
|
||||
// }
|
||||
//
|
||||
// return <div>{fn([1, 2, 3])}</div>
|
||||
// }
|
||||
});
|
||||
});
|
||||
|
|
|
@ -537,8 +537,8 @@ export class ViewParser {
|
|||
|
||||
private pareFor(node: t.JSXElement) {
|
||||
// ---- Get array
|
||||
const arrayContainer = this.findProp(node, 'array');
|
||||
if (!arrayContainer) throw new Error('Missing [array] prop in for loop');
|
||||
const arrayContainer = this.findProp(node, 'each');
|
||||
if (!arrayContainer) throw new Error('Missing [each] prop in for loop');
|
||||
if (!this.t.isJSXExpressionContainer(arrayContainer.value))
|
||||
throw new Error('Expected expression container for [array] prop');
|
||||
const array = arrayContainer.value.expression;
|
||||
|
@ -554,24 +554,31 @@ export class ViewParser {
|
|||
}
|
||||
|
||||
// ---- Get Item
|
||||
const itemProp = this.findProp(node, 'item');
|
||||
if (!itemProp) throw new Error('Missing [item] prop in for loop');
|
||||
if (!this.t.isJSXExpressionContainer(itemProp.value))
|
||||
throw new Error('Expected expression container for [item] prop');
|
||||
const item = itemProp.value.expression;
|
||||
if (this.t.isJSXEmptyExpression(item)) throw new Error('Expected [item] expression not empty');
|
||||
// ---- ObjectExpression to ObjectPattern / ArrayExpression to ArrayPattern
|
||||
this.traverse(this.wrapWithFile(item), {
|
||||
ObjectExpression: path => {
|
||||
path.node.type = 'ObjectPattern' as any;
|
||||
},
|
||||
ArrayExpression: path => {
|
||||
path.node.type = 'ArrayPattern' as any;
|
||||
},
|
||||
});
|
||||
// <for each={data}>
|
||||
// {(item, idx)=>(<Comp_$id1$item={item}idx={idx}/>)}
|
||||
// </for>
|
||||
const jsxChildren = node.children;
|
||||
if (jsxChildren.length !== 1) throw new Error('Expected 1 child');
|
||||
if (jsxChildren[0].type !== 'JSXExpressionContainer') throw new Error('Expected expression container');
|
||||
const itemFnNode = jsxChildren[0].expression;
|
||||
if (this.t.isJSXEmptyExpression(itemFnNode)) throw new Error('Expected expression not empty');
|
||||
|
||||
// ---- Get children
|
||||
const children = this.t.jsxFragment(this.t.jsxOpeningFragment(), this.t.jsxClosingFragment(), node.children);
|
||||
let children;
|
||||
if (!this.t.isFunctionExpression(itemFnNode) && !this.t.isArrowFunctionExpression(itemFnNode)) {
|
||||
throw new Error('For: Expected function expression');
|
||||
}
|
||||
// get the return value
|
||||
if (this.t.isBlockStatement(itemFnNode.body)) {
|
||||
if (itemFnNode.body.body.length !== 1) throw new Error('For: Expected 1 statement in block statement');
|
||||
if (!this.t.isReturnStatement(itemFnNode.body.body[0]))
|
||||
throw new Error('For: Expected return statement in block statement');
|
||||
children = itemFnNode.body.body[0].argument;
|
||||
} else {
|
||||
children = itemFnNode.body;
|
||||
}
|
||||
|
||||
const item = itemFnNode.params[0];
|
||||
if (!this.t.isJSXElement(children)) throw new Error('For: Expected jsx element in return statement');
|
||||
|
||||
this.viewUnits.push({
|
||||
type: 'for',
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
import { parse } from './mock';
|
||||
|
||||
|
||||
describe('ForUnit', () => {
|
||||
it('should identify for unit', () => {
|
||||
const viewUnits = parse('<for array={items} item={item}> <div>{item}</div> </for>');
|
||||
const viewUnits = parse('<for each={items}>{([x, y, z], idx) => <Comp$1 x={x} y={y} z={z} idx={idx}/>}</for>');
|
||||
expect(viewUnits.length).toBe(1);
|
||||
expect(viewUnits[0].type).toBe('for');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -40,7 +40,7 @@ export type Dependency = {
|
|||
*/
|
||||
export function getDependenciesFromNode(
|
||||
node: t.Expression | t.Statement,
|
||||
reactiveMap: Map<string, number>,
|
||||
reactiveMap: Map<string, Bitmap | Bitmap[]>,
|
||||
reactivityFuncNames: string[]
|
||||
): Dependency {
|
||||
// ---- Deps: console.log(count)
|
||||
|
@ -57,9 +57,15 @@ export function getDependenciesFromNode(
|
|||
|
||||
if (reactiveBitmap !== undefined) {
|
||||
if (isAssignmentExpressionLeft(innerPath) || isAssignmentFunction(innerPath, reactivityFuncNames)) {
|
||||
assignDepMask |= reactiveBitmap;
|
||||
assignDepMask |= Array.isArray(reactiveBitmap)
|
||||
? reactiveBitmap.reduce((acc, cur) => acc | cur, 0)
|
||||
: reactiveBitmap;
|
||||
} else {
|
||||
depBitmaps.push(reactiveBitmap);
|
||||
if (Array.isArray(reactiveBitmap)) {
|
||||
depBitmaps.push(...reactiveBitmap);
|
||||
} else {
|
||||
depBitmaps.push(reactiveBitmap);
|
||||
}
|
||||
|
||||
if (!depNodes[propertyKey]) depNodes[propertyKey] = [];
|
||||
depNodes[propertyKey].push(geneDependencyNode(innerPath));
|
||||
|
|
|
@ -319,34 +319,31 @@ export class ReactivityParser {
|
|||
* @returns ForParticle
|
||||
*/
|
||||
private parseFor(forUnit: ForUnit): ForParticle {
|
||||
const { depMask, dependenciesNode } = this.getDependencies(forUnit.array);
|
||||
const prevIdentifierDepMap = this.config.depMaskMap;
|
||||
// ---- Find all the identifiers in the key and remove them from the identifierDepMap
|
||||
// because once the key is changed, that identifier related dependencies will be changed too,
|
||||
// so no need to update them
|
||||
const keyDep = this.t.isIdentifier(forUnit.key) && forUnit.key.name;
|
||||
const { fullDepMask, dependenciesNode, depBitmaps } = this.getDependencies(forUnit.array);
|
||||
const prevMap = this.config.depMaskMap;
|
||||
|
||||
// ---- Generate an identifierDepMap to track identifiers in item and make them reactive
|
||||
// based on the dependencies from the array
|
||||
// this.config.depMaskMap = new Map([
|
||||
// ...this.config.depMaskMap,
|
||||
// ...this.getIdentifiers(this.t.assignmentExpression('=', forUnit.item, this.t.objectExpression([])))
|
||||
// .filter(id => !keyDep || id !== keyDep)
|
||||
// .map(id => [id, depMask]),
|
||||
// ]);
|
||||
// Just wrap the item in an assignment expression to get all the identifiers
|
||||
const itemWrapper = this.t.assignmentExpression('=', forUnit.item, this.t.objectExpression([]));
|
||||
this.config.depMaskMap = new Map([
|
||||
...this.config.depMaskMap,
|
||||
...this.getIdentifiers(itemWrapper).map(id => [id, depBitmaps] as const),
|
||||
]);
|
||||
|
||||
const forParticle: ForParticle = {
|
||||
type: 'for',
|
||||
item: forUnit.item,
|
||||
array: {
|
||||
value: forUnit.array,
|
||||
depBitmaps: [],
|
||||
depMask: depMask,
|
||||
depBitmaps,
|
||||
depMask: fullDepMask,
|
||||
dependenciesNode,
|
||||
},
|
||||
children: forUnit.children.map(this.parseViewParticle.bind(this)),
|
||||
key: forUnit.key,
|
||||
};
|
||||
// this.config.identifierDepMap = prevIdentifierDepMap;
|
||||
this.config.depMaskMap = prevMap;
|
||||
return forParticle;
|
||||
}
|
||||
|
||||
|
@ -427,7 +424,7 @@ export class ReactivityParser {
|
|||
private getDependencies(node: t.Expression | t.Statement) {
|
||||
if (this.t.isFunctionExpression(node) || this.t.isArrowFunctionExpression(node)) {
|
||||
return {
|
||||
depMask: 0,
|
||||
fullDepMask: 0,
|
||||
depBitmaps: [],
|
||||
dependenciesNode: this.t.arrayExpression([]),
|
||||
};
|
||||
|
|
|
@ -104,4 +104,4 @@ export interface ReactivityParserConfig {
|
|||
|
||||
// TODO: unify with the types in babel-inula-next-core
|
||||
export type Bitmap = number;
|
||||
export type DepMaskMap = Map<string, Bitmap>;
|
||||
export type DepMaskMap = Map<string, Bitmap | Bitmap[]>;
|
||||
|
|
Loading…
Reference in New Issue