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
相关产品推荐
相关产品推荐

