mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2025-12-11 18:42:56 +00:00
fix: 🐛 解决 react 中 jsx 出码的时候对于循环数据漏包 __$evalArray 的问题
This commit is contained in:
parent
ee65b8fa17
commit
3b9b177b05
@ -75,6 +75,7 @@
|
||||
"change-case": "^3.1.0",
|
||||
"commander": "^6.1.0",
|
||||
"debug": "^4.3.2",
|
||||
"fp-ts": "^2.11.9",
|
||||
"fs-extra": "9.x",
|
||||
"glob": "^7.2.0",
|
||||
"html-entities": "^2.3.2",
|
||||
|
||||
@ -83,6 +83,23 @@ const pluginFactory: BuilderComponentPluginFactory<PluginConfig> = (config?) =>
|
||||
`,
|
||||
linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.InsMethod]],
|
||||
});
|
||||
} else {
|
||||
// useRef 为 false 的时候是指没有组件在 props 中配置 ref 属性,但这个时候其实也可能有代码访问 this.$/$$ 所以还是加上个空的代码
|
||||
next.chunks.push({
|
||||
type: ChunkType.STRING,
|
||||
fileType: cfg.fileType,
|
||||
name: CLASS_DEFINE_CHUNK_NAME.InsMethod,
|
||||
content: ` $ = () => null; `,
|
||||
linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.InsMethod]],
|
||||
});
|
||||
|
||||
next.chunks.push({
|
||||
type: ChunkType.STRING,
|
||||
fileType: cfg.fileType,
|
||||
name: CLASS_DEFINE_CHUNK_NAME.InsMethod,
|
||||
content: ` $$ = () => []; `,
|
||||
linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.InsMethod]],
|
||||
});
|
||||
}
|
||||
|
||||
return next;
|
||||
|
||||
@ -47,9 +47,9 @@ const pluginFactory: BuilderComponentPluginFactory<PluginConfig> = (config?) =>
|
||||
// 这里会将内部的一些子上下文的访问(this.xxx)转换为 __$$context.xxx 的形式
|
||||
// 与 Rax 所不同的是,这里不会将最顶层的 this 转换掉
|
||||
const customHandlers: HandlerSet<string> = {
|
||||
expression(input: JSExpression, scope: IScope) {
|
||||
expression(input: JSExpression, scope: IScope, config) {
|
||||
return transformJsExpr(generateExpression(input, scope), scope, {
|
||||
dontWrapEval: !tolerateEvalErrors,
|
||||
dontWrapEval: !(config?.tolerateEvalErrors ?? tolerateEvalErrors),
|
||||
dontTransformThis2ContextAtRootScope: true,
|
||||
});
|
||||
},
|
||||
@ -71,6 +71,7 @@ const pluginFactory: BuilderComponentPluginFactory<PluginConfig> = (config?) =>
|
||||
const generatorPlugins: NodeGeneratorConfig = {
|
||||
handlers: customHandlers,
|
||||
tagMapping: (v) => nodeTypeMapping[v] || v,
|
||||
tolerateEvalErrors,
|
||||
};
|
||||
|
||||
if (next.contextData.useRefApi) {
|
||||
|
||||
@ -217,6 +217,7 @@ export interface HandlerSet<T> {
|
||||
export interface CompositeValueGeneratorOptions {
|
||||
handlers?: HandlerSet<string>;
|
||||
nodeGenerator?: NodeGenerator<string>;
|
||||
tolerateEvalErrors?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -15,7 +15,10 @@ export interface CodePiece {
|
||||
type: PIECE_TYPE;
|
||||
}
|
||||
|
||||
export interface AttrData { attrName: string; attrValue: CompositeValue }
|
||||
export interface AttrData {
|
||||
attrName: string;
|
||||
attrValue: CompositeValue;
|
||||
}
|
||||
// 对 JSX 出码的理解,目前定制点包含 【包装】【标签名】【属性】
|
||||
export type AttrPlugin = BaseGenerator<AttrData, CodePiece[], NodeGeneratorConfig>;
|
||||
export type NodePlugin = BaseGenerator<NodeSchema, CodePiece[], NodeGeneratorConfig>;
|
||||
@ -26,4 +29,12 @@ export interface NodeGeneratorConfig {
|
||||
attrPlugins?: AttrPlugin[];
|
||||
nodePlugins?: NodePlugin[];
|
||||
self?: NodeGenerator<string>;
|
||||
|
||||
/**
|
||||
* 是否要容忍对 JSExpression 求值时的异常
|
||||
* 默认:true
|
||||
* 注: 如果容忍异常,则会在求值时包裹 try-catch 块 -- 通过 __$$eval / __$$evalArray
|
||||
* catch 到异常时默认会抛出一个 CustomEvent 事件里面包含异常信息和求值的表达式
|
||||
*/
|
||||
tolerateEvalErrors?: boolean;
|
||||
}
|
||||
|
||||
@ -79,10 +79,11 @@ export function isJsCode(value: unknown): boolean {
|
||||
|
||||
export function generateExpression(value: any, scope: IScope): string {
|
||||
if (isJSExpression(value)) {
|
||||
const exprVal = (value as JSExpression).value;
|
||||
const exprVal = (value as JSExpression).value.trim();
|
||||
if (!exprVal) {
|
||||
return 'null';
|
||||
}
|
||||
|
||||
const afterProcessWithLocals = transformExpressionLocalRef(exprVal, scope);
|
||||
return afterProcessWithLocals;
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import _ from 'lodash';
|
||||
import { pipe } from 'fp-ts/function';
|
||||
import { NodeSchema, isNodeSchema, NodeDataType, CompositeValue } from '@alilc/lowcode-types';
|
||||
|
||||
import {
|
||||
@ -17,6 +18,7 @@ import { getStaticExprValue } from './common';
|
||||
import { executeFunctionStack } from './aopHelper';
|
||||
import { encodeJsxStringNode } from './encodeJsxAttrString';
|
||||
import { unwrapJsExprQuoteInJsx } from './jsxHelpers';
|
||||
import { transformThis2Context } from '../core/jsx/handlers/transformThis2Context';
|
||||
|
||||
function mergeNodeGeneratorConfig(
|
||||
cfg1: NodeGeneratorConfig,
|
||||
@ -255,6 +257,8 @@ export function generateReactLoopCtrl(
|
||||
next?: NodePlugin,
|
||||
): CodePiece[] {
|
||||
if (nodeItem.loop) {
|
||||
const tolerateEvalErrors = config?.tolerateEvalErrors ?? true;
|
||||
|
||||
const loopItemName = nodeItem.loopArgs?.[0] || 'item';
|
||||
const loopIndexName = nodeItem.loopArgs?.[1] || 'index';
|
||||
|
||||
@ -262,9 +266,20 @@ export function generateReactLoopCtrl(
|
||||
const subScope = scope.createSubScope([loopItemName, loopIndexName]);
|
||||
const pieces: CodePiece[] = next ? next(nodeItem, subScope, config) : [];
|
||||
|
||||
const loopDataExpr = generateCompositeType(nodeItem.loop, scope, {
|
||||
handlers: config?.handlers,
|
||||
});
|
||||
// 生成循环变量表达式
|
||||
const loopDataExpr = pipe(
|
||||
nodeItem.loop,
|
||||
// 将 JSExpression 转换为 JS 表达式代码:
|
||||
(expr) =>
|
||||
generateCompositeType(expr, scope, {
|
||||
handlers: config?.handlers,
|
||||
tolerateEvalErrors: false, // 这个内部不需要包 try catch, 下面会统一加的
|
||||
}),
|
||||
// 将 this.xxx 转换为 __$$context.xxx:
|
||||
(expr) => transformThis2Context(expr, scope, { ignoreRootScope: true }),
|
||||
// 如果要容忍错误,则包一层 try catch (基于助手函数 __$$evalArray)
|
||||
(expr) => (tolerateEvalErrors ? `__$$evalArray(() => (${expr}))` : expr),
|
||||
);
|
||||
|
||||
pieces.unshift({
|
||||
value: `(${loopDataExpr}).map((${loopItemName}, ${loopIndexName}) => ((__$$context) => (`,
|
||||
|
||||
@ -152,7 +152,7 @@ class Test$$Page extends React.Component {
|
||||
</Form.Item>
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<Button.Group>
|
||||
{["a", "b", "c"].map((item, index) =>
|
||||
{__$$evalArray(() => ["a", "b", "c"]).map((item, index) =>
|
||||
((__$$context) =>
|
||||
!!__$$eval(() => index >= 1) && (
|
||||
<Button type="primary" style={{ margin: "0 5px 0 5px" }}>
|
||||
|
||||
@ -45,6 +45,10 @@ class Aaaa$$Page extends React.Component {
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
$ = () => null;
|
||||
|
||||
$$ = () => [];
|
||||
|
||||
_defineDataSourceConfig() {
|
||||
const _this = this;
|
||||
return {
|
||||
|
||||
@ -38,6 +38,10 @@ class Test$$Page extends React.Component {
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
$ = () => null;
|
||||
|
||||
$$ = () => [];
|
||||
|
||||
componentDidMount() {}
|
||||
|
||||
render() {
|
||||
|
||||
@ -152,7 +152,7 @@ class Test$$Page extends React.Component {
|
||||
</Form.Item>
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<Button.Group>
|
||||
{["a", "b", "c"].map((item, index) =>
|
||||
{__$$evalArray(() => ["a", "b", "c"]).map((item, index) =>
|
||||
((__$$context) =>
|
||||
!!false && (
|
||||
<Button type="primary" style={{ margin: "0 5px 0 5px" }}>
|
||||
|
||||
@ -41,6 +41,10 @@ class Example$$Page extends React.Component {
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
$ = () => null;
|
||||
|
||||
$$ = () => [];
|
||||
|
||||
_defineDataSourceConfig() {
|
||||
const _this = this;
|
||||
return {
|
||||
|
||||
@ -41,6 +41,10 @@ class Index$$Page extends React.Component {
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
$ = () => null;
|
||||
|
||||
$$ = () => [];
|
||||
|
||||
_defineDataSourceConfig() {
|
||||
const _this = this;
|
||||
return {
|
||||
@ -75,16 +79,17 @@ class Index$$Page extends React.Component {
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
{__$$eval(() => this.dataSourceMap.todos.data).map((item, index) =>
|
||||
((__$$context) => (
|
||||
<div>
|
||||
<Switch
|
||||
checkedChildren="开"
|
||||
unCheckedChildren="关"
|
||||
checked={__$$eval(() => item.done)}
|
||||
/>
|
||||
</div>
|
||||
))(__$$createChildContext(__$$context, { item, index }))
|
||||
{__$$evalArray(() => this.dataSourceMap.todos.data).map(
|
||||
(item, index) =>
|
||||
((__$$context) => (
|
||||
<div>
|
||||
<Switch
|
||||
checkedChildren="开"
|
||||
unCheckedChildren="关"
|
||||
checked={__$$eval(() => item.done)}
|
||||
/>
|
||||
</div>
|
||||
))(__$$createChildContext(__$$context, { item, index }))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -238,7 +238,7 @@ class Test$$Page extends React.Component {
|
||||
width="720px"
|
||||
centered={true}
|
||||
>
|
||||
{__$$eval(() => this.state.results).map((item, index) =>
|
||||
{__$$evalArray(() => this.state.results).map((item, index) =>
|
||||
((__$$context) => (
|
||||
<AliAutoDivDefault style={{ width: "100%" }}>
|
||||
{!!__$$eval(
|
||||
@ -441,7 +441,7 @@ class Test$$Page extends React.Component {
|
||||
width: 142,
|
||||
render: (text, record, index) =>
|
||||
((__$$context) =>
|
||||
__$$eval(() => text.split(",")).map(
|
||||
__$$evalArray(() => text.split(",")).map(
|
||||
(item, index) =>
|
||||
((__$$context) => (
|
||||
<Typography.Text
|
||||
@ -470,7 +470,7 @@ class Test$$Page extends React.Component {
|
||||
render: (text, record, index) =>
|
||||
((__$$context) => (
|
||||
<Tooltip
|
||||
title={__$$eval(() => text || []).map(
|
||||
title={__$$evalArray(() => text || []).map(
|
||||
(item, index) =>
|
||||
((__$$context) => (
|
||||
<Typography.Text
|
||||
|
||||
@ -303,7 +303,7 @@ class Test$$Page extends React.Component {
|
||||
</Button>
|
||||
</AliAutoDivDefault>
|
||||
)}
|
||||
{__$$eval(() => this.state.results).map((item, index) =>
|
||||
{__$$evalArray(() => this.state.results).map((item, index) =>
|
||||
((__$$context) => (
|
||||
<AliAutoDivDefault style={{ width: "100%", marginTop: "10px" }}>
|
||||
<Typography.Text>
|
||||
@ -595,17 +595,18 @@ class Test$$Page extends React.Component {
|
||||
width: 142,
|
||||
render: (text, record, index) =>
|
||||
((__$$context) =>
|
||||
__$$eval(() => text.split(",")).map((item, index) =>
|
||||
((__$$context) => (
|
||||
<Typography.Text style={{ display: "block" }}>
|
||||
{__$$eval(() => item)}
|
||||
</Typography.Text>
|
||||
))(
|
||||
__$$createChildContext(__$$context, {
|
||||
item,
|
||||
index,
|
||||
})
|
||||
)
|
||||
__$$evalArray(() => text.split(",")).map(
|
||||
(item, index) =>
|
||||
((__$$context) => (
|
||||
<Typography.Text style={{ display: "block" }}>
|
||||
{__$$eval(() => item)}
|
||||
</Typography.Text>
|
||||
))(
|
||||
__$$createChildContext(__$$context, {
|
||||
item,
|
||||
index,
|
||||
})
|
||||
)
|
||||
))(
|
||||
__$$createChildContext(__$$context, {
|
||||
text,
|
||||
@ -621,7 +622,7 @@ class Test$$Page extends React.Component {
|
||||
render: (text, record, index) =>
|
||||
((__$$context) => (
|
||||
<Tooltip
|
||||
title={__$$eval(() => text || []).map(
|
||||
title={__$$evalArray(() => text || []).map(
|
||||
(item, index) =>
|
||||
((__$$context) => (
|
||||
<Typography.Text
|
||||
|
||||
@ -0,0 +1,58 @@
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"componentsMap": [
|
||||
{
|
||||
"package": "react-greetings",
|
||||
"version": "1.0.0",
|
||||
"componentName": "Greetings",
|
||||
"exportName": "Greetings",
|
||||
"destructuring": true
|
||||
}
|
||||
],
|
||||
"componentsTree": [
|
||||
{
|
||||
"componentName": "Page",
|
||||
"id": "node_ocl137q7oc1",
|
||||
"fileName": "test",
|
||||
"props": { "style": {} },
|
||||
"lifeCycles": {},
|
||||
"dataSource": { "list": [] },
|
||||
"state": {
|
||||
"name": "lowcode world",
|
||||
"users": null
|
||||
},
|
||||
"methods": {},
|
||||
"children": [
|
||||
{
|
||||
"componentName": "Greetings",
|
||||
"id": "node_ocl137q7oc4",
|
||||
"loop": {
|
||||
"type": "JSExpression",
|
||||
"value": "this.state.users"
|
||||
},
|
||||
"loopArgs": ["item", ""],
|
||||
"props": {
|
||||
"content": {
|
||||
"type": "i18n",
|
||||
"key": "greetings.hello",
|
||||
"params": {
|
||||
"name": {
|
||||
"type": "JSExpression",
|
||||
"value": "this.item"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"i18n": {
|
||||
"zh_CN": {
|
||||
"greetings.hello": "${name}, 你好!"
|
||||
},
|
||||
"en_US": {
|
||||
"greetings.hello": "Hello, ${name}!"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,52 @@
|
||||
import CodeGenerator from '../../src';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { ProjectSchema } from '@alilc/lowcode-types';
|
||||
import { createDiskPublisher } from '../helpers/solutionHelper';
|
||||
import { IceJsProjectBuilderOptions } from '../../src/solutions/icejs';
|
||||
|
||||
const testCaseBaseName = path.basename(__filename, '.test.ts');
|
||||
const inputSchemaJsonFile = path.join(__dirname, `${testCaseBaseName}.schema.json`);
|
||||
const outputDir = path.join(__dirname, `${testCaseBaseName}.generated`);
|
||||
|
||||
jest.setTimeout(60 * 60 * 1000);
|
||||
|
||||
test('loop should be generated link __$$evalArray(xxx).map', async () => {
|
||||
await exportProject(
|
||||
inputSchemaJsonFile,
|
||||
outputDir,
|
||||
{},
|
||||
{ inStrictMode: true, tolerateEvalErrors: true },
|
||||
);
|
||||
|
||||
const generatedPageFileContent = readOutputTextFile('demo-project/src/pages/Test/index.jsx');
|
||||
expect(generatedPageFileContent).toContain(
|
||||
'{__$$evalArray(() => this.state.users).map((item, index) =>',
|
||||
);
|
||||
});
|
||||
|
||||
function exportProject(
|
||||
importPath: string,
|
||||
outputPath: string,
|
||||
mergeSchema?: Partial<ProjectSchema>,
|
||||
options?: IceJsProjectBuilderOptions,
|
||||
) {
|
||||
const schemaJsonStr = fs.readFileSync(importPath, { encoding: 'utf8' });
|
||||
const schema = { ...JSON.parse(schemaJsonStr), ...mergeSchema };
|
||||
const builder = CodeGenerator.solutions.icejs(options);
|
||||
|
||||
return builder.generateProject(schema).then(async (result) => {
|
||||
const publisher = createDiskPublisher();
|
||||
await publisher.publish({
|
||||
project: result,
|
||||
outputPath,
|
||||
projectSlug: 'demo-project',
|
||||
createProjectFolder: true,
|
||||
});
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
function readOutputTextFile(outputFilePath: string): string {
|
||||
return fs.readFileSync(path.resolve(outputDir, outputFilePath), 'utf-8');
|
||||
}
|
||||
@ -0,0 +1,70 @@
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"componentsMap": [
|
||||
{
|
||||
"package": "react-greetings",
|
||||
"version": "1.0.0",
|
||||
"componentName": "Greetings",
|
||||
"exportName": "Greetings",
|
||||
"destructuring": true
|
||||
}
|
||||
],
|
||||
"componentsTree": [
|
||||
{
|
||||
"componentName": "Page",
|
||||
"id": "node_ocl137q7oc1",
|
||||
"fileName": "test",
|
||||
"props": { "style": {} },
|
||||
"lifeCycles": {},
|
||||
"dataSource": { "list": [] },
|
||||
"state": {
|
||||
"name": "lowcode world",
|
||||
"users": null
|
||||
},
|
||||
"methods": {},
|
||||
"children": [
|
||||
{
|
||||
"componentName": "Greetings",
|
||||
"id": "node_ocl137q7oc4",
|
||||
"loop": {
|
||||
"type": "JSExpression",
|
||||
"value": "this.state.users"
|
||||
},
|
||||
"loopArgs": ["item", ""],
|
||||
"props": {
|
||||
"content": {
|
||||
"type": "i18n",
|
||||
"key": "greetings.hello",
|
||||
"params": { "name": { "type": "JSExpression", "value": "this.item" } }
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"componentName": "Greetings",
|
||||
"id": "node_ocl137q7oc4",
|
||||
"loop": {
|
||||
"type": "JSExpression",
|
||||
"value": "this.state.messages"
|
||||
},
|
||||
"loopArgs": ["msg", ""],
|
||||
"props": {
|
||||
"content": {
|
||||
"type": "JSExpression",
|
||||
"value": "this.msg"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"i18n": {
|
||||
"zh_CN": {
|
||||
"greetings.hello": "${name}, 你好!"
|
||||
},
|
||||
"en_US": {
|
||||
"greetings.hello": "Hello, ${name}!"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,56 @@
|
||||
import CodeGenerator from '../../src';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { ProjectSchema } from '@alilc/lowcode-types';
|
||||
import { createDiskPublisher } from '../helpers/solutionHelper';
|
||||
import { IceJsProjectBuilderOptions } from '../../src/solutions/icejs';
|
||||
|
||||
const testCaseBaseName = path.basename(__filename, '.test.ts');
|
||||
const inputSchemaJsonFile = path.join(__dirname, `${testCaseBaseName}.schema.json`);
|
||||
const outputDir = path.join(__dirname, `${testCaseBaseName}.generated`);
|
||||
|
||||
jest.setTimeout(60 * 60 * 1000);
|
||||
|
||||
test('loop should be generated link __$$evalArray(xxx).map', async () => {
|
||||
await exportProject(
|
||||
inputSchemaJsonFile,
|
||||
outputDir,
|
||||
{},
|
||||
{ inStrictMode: true, tolerateEvalErrors: true },
|
||||
);
|
||||
|
||||
const generatedPageFileContent = readOutputTextFile('demo-project/src/pages/Test/index.jsx');
|
||||
expect(generatedPageFileContent).toContain(
|
||||
'{__$$evalArray(() => this.state.users).map((item, index) =>',
|
||||
);
|
||||
|
||||
expect(generatedPageFileContent).toContain(
|
||||
'{__$$evalArray(() => __$$context.state.messages).map(',
|
||||
);
|
||||
});
|
||||
|
||||
function exportProject(
|
||||
importPath: string,
|
||||
outputPath: string,
|
||||
mergeSchema?: Partial<ProjectSchema>,
|
||||
options?: IceJsProjectBuilderOptions,
|
||||
) {
|
||||
const schemaJsonStr = fs.readFileSync(importPath, { encoding: 'utf8' });
|
||||
const schema = { ...JSON.parse(schemaJsonStr), ...mergeSchema };
|
||||
const builder = CodeGenerator.solutions.icejs(options);
|
||||
|
||||
return builder.generateProject(schema).then(async (result) => {
|
||||
const publisher = createDiskPublisher();
|
||||
await publisher.publish({
|
||||
project: result,
|
||||
outputPath,
|
||||
projectSlug: 'demo-project',
|
||||
createProjectFolder: true,
|
||||
});
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
function readOutputTextFile(outputFilePath: string): string {
|
||||
return fs.readFileSync(path.resolve(outputDir, outputFilePath), 'utf-8');
|
||||
}
|
||||
@ -7,7 +7,7 @@ Object {
|
||||
"content": "
|
||||
const __$$context = this._context || this;
|
||||
const { state } = __$$context;
|
||||
return (__$$eval(() => (this.state.otherThings))).map((item, index) => ((__$$context) => (!!(__$$eval(() => (__$$context.state.something))) && (<Page><Text>Hello world!</Text></Page>)))(__$$createChildContext(__$$context, { item, index })));
|
||||
return (__$$evalArray(() => (this.state.otherThings))).map((item, index) => ((__$$context) => (!!(__$$eval(() => (__$$context.state.something))) && (<Page><Text>Hello world!</Text></Page>)))(__$$createChildContext(__$$context, { item, index })));
|
||||
",
|
||||
"fileType": "jsx",
|
||||
"linkAfter": Array [
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user