V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
Trello
V2EX  ›  问与答

Flutter 中如何优雅的封装 Widget?

  •  
  •   Trello · 2022-06-11 19:47:55 +08:00 · 1131 次点击
    这是一个创建于 902 天前的主题,其中的信息可能已经有所发展或是发生改变。

    举个栗子,封装一个 Text Widget 。

    方式一

    优点:

    不用担心为了自定义某个预设样式,而导致所有预设样式丢失。

    缺点:

    这里只是暴露了 Text 控件的几个属性,实际开发中一个 Widget 可能需要暴露很多属性,后面扩展下来,属性列表可能会拉的很长。在这里极限情况就要暴露 Text 控件的所有属性给外界。

    class TextWidget extends StatelessWidget {
      final String text;
      final Color? color;
      final double? fontSize;
      final FontWeight? fontWeight;
    
      const TextWidget({
        Key? key,
        required this.text,
        this.color = Colors.black,
        this.fontSize = 16,
        this.fontWeight = FontWeight.normal,
      }) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return Text(
          text,
          style: TextStyle(
            color: color,
            fontSize: fontSize,
            fontWeight: fontWeight,
          ),
        );
      }
    }
    

    方式二

    优点:

    不用暴露太多属性,直接暴露一个 textStyle 属性,需要用到 text 的什么属性,使用者自己传入 textStyle 即可。

    缺点:

    一旦 Widget 使用者,需要传入 textStyle 自定义部分样式,会丢失预设的 textStyle 所有默认样式,所有样式都需要重新传,对于只改其中单个或少数几个样式来说,很不方便。

    这里预设了颜色、字号、字重这三个样式属性,使用者如果仅仅需要修改颜色,那在将自定义 color 传入 textStyle 的同时,还要传入字号、字重这俩属性默认的值。

    这里预设样式比较少还好。如果预设样式列表特别长,有十几个甚至更多属性,为了自定义其中一个属性的样式,需要重新传所有样式,就很难受很不友好。

    class TextWidget extends StatelessWidget {
      final String text;
      final TextStyle textStyle;
    
      const TextWidget({
        Key? key,
        required this.text,
        this.textStyle = const TextStyle(
          color: Colors.black,
          fontSize: 16,
          fontWeight: FontWeight.normal,
        ),
      }) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return Text(text, style: textStyle);
      }
    }
    
    11 条回复    2022-06-12 01:15:00 +08:00
    Trello
        1
    Trello  
    OP
       2022-06-11 20:03:54 +08:00
    Flutter 中有没有哪种方式,能兼容上述两种方式的优点?诸如像 RN 中封装组件那样,就很优雅简洁。

    方便扩展,不用传入多个样式属性,一个 style 即可。

    同时也不用担心预设样式丢失,直接使用扩展运算符增量覆盖即可。

    以下是 RN 写法。

    interface Props {
    text: String,
    textStyle?: TextStyle,
    }

    class TextComponent extends React.Component<Props> {
    render() {
    const { text, textStyle } = this.props;

    return (
    <Text
    style={{
    color: '#000',
    fontSize: 16,
    fontWeight: 'normal',
    ...textStyle
    }}
    >
    {text}
    </Text>
    )
    }
    }
    shetz163
        2
    shetz163  
       2022-06-11 20:08:26 +08:00
    TextStyle 里面有一个方法 merge, 可以合并另外一个 TextStyle
    其实可以写一个默认的 TextStyle, 然后传入想要部分修改的 TextStyle, 合并使用就好
    Trello
        3
    Trello  
    OP
       2022-06-11 20:08:42 +08:00
    友友们请不吝赐教哈
    Trello
        4
    Trello  
    OP
       2022-06-11 20:21:01 +08:00
    @shetz163 谢谢,原来还要这么个方法,那文本样式方面的不用愁了。请问 EdgeInsets 之类的有类似的合并方法吗?比如我预设了边距,后面再传一个 EdgeInsets ,以后来的为准。
    shetz163
        5
    shetz163  
       2022-06-11 20:37:30 +08:00
    edgeinsets 好像没有这样的合并方式, 可以用 ?? 来做默认的, 但如果要合并的话, 可能得自己去做一下处理, 比如判断一下 上下左右是否大于 0 来确认是否继承
    Trello
        6
    Trello  
    OP
       2022-06-11 20:44:40 +08:00
    @shetz163 好的吧,谢谢。我在寻求 Flutter 内一种通用的 Widget 传参属性扩展方式。

    在 React 内给组件传 Props 属性,如果要扩展的话,也是直接扩展运算符就可以扩充了。

    诸如有这么个 A 组件,有两个确定要传的 a 、b 属性,同时使用{...props}保留了扩展性。

    <Component a={xxx} b={xxx} {...props}>

    不知道 Flutter 中各式各样的 widget 传属性时,如果要动态扩充,有没有什么通用的办法。
    Trello
        7
    Trello  
    OP
       2022-06-11 20:47:39 +08:00
    @Trello React 中这种形式,不仅可以扩充新的属性,还能覆盖原有属性,就很方便。
    Trello
        8
    Trello  
    OP
       2022-06-11 20:51:47 +08:00
    @shetz163 我看 Stack Overflow 上的回复,好像没有这种方式,就很无奈。需要哪个属性就得写哪个属性,一点都不灵活。
    https://stackoverflow.com/questions/56934960/flutter-inherit-all-props-from-parent-widget
    shetz163
        9
    shetz163  
       2022-06-11 20:52:33 +08:00
    现有的看起来就只有自己去做继承, 也可以用 dart extension 来自己做一个继承的方法, 最新的 flutter 3 上对于 extension 支持更好了, 好像是可以不用提前导入 extension 的文件就可以直接联想出来方法, 并且自动导入
    Trello
        10
    Trello  
    OP
       2022-06-11 21:22:35 +08:00
    @shetz163 哇,那岂不是得用一个 widget 就得写一个继承类来实现合并,这也太累了呀。而且写的这个继承类还不能通用,因为不同场景下预设值不一样,比如 EdgeInsets 我写了个继承类,里面属性赋上某个场景下预设的默认边距,别的场景这个 EdgeInsets 的边距默认值又变了,那还得重写一个关于 EdgeInsets 的继承类,或者在原来写的继承类里加判断或其他命名构造函数做场景区分(这些常用的 widget 场景太多,一个继承类根本维护不下去的)。吐了呀,要崩溃。。。太反人类吧。
    shetz163
        11
    shetz163  
       2022-06-12 01:15:00 +08:00
    我的意思是说做个继承的方法 比如扩展一个 edgeinsets.merge(edgeinsets other)这样的方法然后 other 里有非 0 的值就填进去 没有就用默认的
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1220 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 23:54 · PVG 07:54 · LAX 15:54 · JFK 18:54
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.