You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter Widget扩展:如何优雅传递任意命名参数?

扩展Flutter Widget时传递任意属性的优化方案

需求说明

在扩展Flutter Widget(比如封装DataTable)时,希望找到一种无需在自定义Widget中预先声明原Widget的所有属性、也不用每次原Widget新增属性就修改自定义Widget的方式,能直接将任意属性传递给原Widget。

原有繁琐实现方式

之前的实现需要在自定义Widget中重复声明原Widget的所有属性,代码示例如下:

class DataTableFromMap extends StatelessWidget {
  final List tableData;
  final int? sortColumnIndex;
  final bool sortAscending;
  final ValueSetter<bool?>? onSelectAll;
  final Decoration? decoration;
  final MaterialStateProperty<Color?>? dataRowColor;
  // 还有其他所有属性...

  const DataTableFromMap({
    super.key,
    required this.tableData,
    this.sortColumnIndex,
    this.sortAscending,
    this.onSelectAll,
    this.decoration,
    this.dataRowColor,
    // 还有其他所有属性...
  });

  @override
  Widget build(BuildContext context) {
    return DataTable(
      columns: _createColumns(tableData.first),
      rows: _createRows(tableData),
      sortColumnIndex: sortColumnIndex,
      sortAscending: sortAscending,
      onSelectAll: onSelectAll,
      decoration: decoration,
      dataRowColor: dataRowColor,
      // 还有其他所有属性...
    );
  }
}

期望的调用方式

希望能通过传入一个包含所需属性的Map(比如tableProps),像这样调用原Widget:

DataTable(
  columns: _createColumns(tableData.first),
  rows: _createRows(tableData),
  ...tableProps
);

已尝试的方案及弊端

尝试通过传入Map类型的tableProps,再用Function.apply结合Symbol转换的方式实现:

class DataTableFromMap extends StatelessWidget {
  final List tableData;
  final Map tableProps;

  const DataTableFromMap({
    super.key,
    required this.tableData,
    this.tableProps = const {},
  });

  @override
  Widget build(BuildContext context) => createDataTableFromMap(tableData);

  DataTable createDataTableFromMap(tableData) {
    // 将属性键转换为Symbol,用于Function.apply的命名参数
    final props = symbolizeKeys({
      "columns": _createColumns(tableData.first),
      "rows": _createRows(tableData),
      ...tableProps,
    });

    return Function.apply(DataTable.new, [], props);
  }
}

调用时只需传入需要的属性:

final tableProps = {
  "headingRowColor": MaterialStateColor.resolveWith((states) {
    return const Color.fromRGBO(200, 100, 50, 1);
  }),
  "horizontalMargin": 48.0
};

// 使用自定义Widget
DataTableFromMap(tableData: testData, tableProps: tableProps),

这个方案可以正常运行,但存在明显弊端:

  • 属性名必须用字符串,失去了IDE的自动补全和类型检查能力,容易出错
  • 有开发者指出Function.apply(DataTable.new)可能会生成冗余的JS代码,影响性能

寻求优化建议

目前已经遵循了官方建议:优先使用Widget而非辅助方法,继承StatelessWidget而非原Widget类,但想了解是否可以打破这些规则来实现需求,或者有其他更优的实现方案。

完整代码示例

import 'package:flutter/material.dart';

void main() {
  runApp(BundleOptParamsTest());
}

class BundleOptParamsTest extends StatelessWidget {
  BundleOptParamsTest({super.key});

  final testData = [
    {'name': 'Babes', 'number': 123},
    {'name': 'Smokes', 'number': 7890},
    {'name': 'Rocks', 'number': 9871234}
  ];

  final tableProps = {
    "headingRowColor": MaterialStateColor.resolveWith((states) {
      return const Color.fromRGBO(200, 100, 50, 1);
    }),
    "horizontalMargin": 48.0
  };

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        home: Scaffold(
      appBar: AppBar(
        title: const Text('Bundling Optional Parameters to Class Test'),
      ),
      body: Center(
        child: DataTableFromMap(tableData: testData, tableProps: tableProps),
      ),
    ));
  }
}

class DataTableFromMap extends StatelessWidget {
  final List tableData;
  final Map tableProps;

  const DataTableFromMap({
    super.key,
    required this.tableData,
    this.tableProps = const {},
  });

  @override
  Widget build(BuildContext context) => createDataTableFromMap(tableData);

  DataTable createDataTableFromMap(tableData) {
    // 将属性键转换为Symbol,用于Function.apply的命名参数
    final props = symbolizeKeys({
      "columns": _createColumns(tableData.first),
      "rows": _createRows(tableData),
      ...tableProps,
    });
    print(props);

    return Function.apply(DataTable.new, [], props);
  }

  List<DataColumn> _createColumns(sampleRow) {
    return [
      for (final e in sampleRow.entries)
        DataColumn(label: Text(e.key.toString()), numeric: (e.value is num))
    ];
  }

  List<DataRow> _createRows(tableData) {
    return [
      for (final row in tableData)
        DataRow(cells: [
          for (final v in row.values)
            DataCell(Text(v is String ? v : v.toString().asThousands))
        ])
    ];
  }
}

/// 数字字符串千分位格式化
/// 示例:-123456.toString().asThousands 返回 -123,456
extension on String {
  String get asThousands {
    final numberDigits = List.from(split(''));
    int index = numberDigits.length - 3;
    while (index > 0) {
      numberDigits.insert(index, ',');
      index -= 3;
    }
    return numberDigits.join();
  }
}

// 将Map的键转换为Symbol,用于Function.apply的命名参数
Map<Symbol, dynamic> symbolizeKeys(Map<String, dynamic> map) {
  return map.map((k, v) => MapEntry(Symbol(k), v));
}

内容的提问来源于stack exchange,提问作者Chema

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 23:30:56