November 1, 2023
By: Kevin

Clojrue+Dart的使用手册

  1. 命令行界面 (CLI)
  2. 使用 Dart 包
  3. 类型和别名
  4. 类型和可空性
  5. 参数化类型
  6. 属性访问
  7. 对象解构: :flds
  8. 构造函数
  9. 理解 Dart 的函数签名
  10. 命名参数
  11. 可选参数(命名或非命名)
  12. 枚举
  13. 常量表达式
  14. 创建类
  15. 操作符
  16. Getters/Setters
  17. 必须的库
  18. f/run [& widget-body]
  19. f/widget [& widget-body]
  20. .child-threading
  21. :let 指令
  22. :key 指令
  23. :watch 指令
  24. 可监视对象(watchables)
  25. Cells
  26. :managed 指令
  27. :bind 指令
  28. :get 指令
  29. :context 指令
  30. :vsync 指令
  31. 参考连接

dart

这是一份<ClojureDart 对 Clojurists 的备忘单>的全文翻译.

本文涵盖了使用 ClojureDart 进行 Dart 编程的多个方面, 包括命令行工具, Dart包的使用, 类型和可空性, 属性访问, 构造函数, Dart的函数签名, 枚举, 类的创建和 Flutter 的使用等.

命令行界面 (CLI)

项目的管理命令.

  • clj -M:cljd init: 初始化项目
  • clj -M:cljd flutter: 项目启动, hotload由按回车(return)触发
  • clj -M:cljd compile: 编译
  • clj -M:cljd clean: 清理, 当出现问题时使用
  • clj -M:cljd upgrade: 保持 CLJD 更新

使用 Dart 包

在 ns 表单中, 像往常一样使用 :require, 但用字符串代替符号.

示例: (ns my.project (:require ["package:flutter/material.dart" :as m]))

类型和别名

不同于 Clojure/JVM, 可以使用别名前缀类型. 例如, m/ElevatedButton 指的是别名为m的包中的ElevatedButton.

如果是JVM, 只能是使用全名了:

(new com.example.package1.MyClass)
(new com.example.package2.MyClass)

类型和可空性

  • 在 ClojureDart 中, ^String x 表示 x 不能为 nil.
  • 使用 ^String? x 来允许 nil 值.

参数化类型

与 Clojure/JVM 不同, 泛型也存在于运行时, 除了单类型提示比如^String又增加了一种新的范型提示. ^#/(List Map) x, 用来表示List<Map> x.

DartClojureDart
ListList
List<Map>#/(List Map)
List?<Map>#/(List? Map)
List<Map?>#/(List Map?)

属性访问

  • 实例获取: (.-prop obj), 赋值: (.-prop! obj x)(set! (.-prop obj) x)
  • 静态属性: m/Colors.purple, 颜色比较特殊, 可以取色阶m/Colors.purple.shade900, 也可以在色阶上设置透明度 (m/Colors.purple.shade900.withAlpha 128).

对象解构: :flds

新引入的对象解构方法, 类似于对一个map进行解构的时候, 可以使用 :keys, :strs:syms.

对一个Dart对象, 我们可以使用:flds进行解构.

示例: (let [{:flds [height width]} size] ...) 等同于解构 height 和 width 属性.

(let [height (.-height size)
      width (.-width size)]
  ...)

构造函数

  • 在Dart中, 构造函数不总是分配新实例, 有时会返回现有实例. 因此, ClojureDart 中没有 new 关键字或末尾的点(String. ...)这种语法.
  • 默认构造函数用类名直接调用, 如 (StringBuffer "hello").
  • 命名构造函数类似于静态方法调用, 如 (List/empty .growable true).

理解 Dart 的函数签名

  • writeAll(Iterable objects, [String separator = ""]) 表示一个类型为Iterable位置参数:objects和一个可选的位置参数``separator 类型为String, 默认值为"".
  • RegExp(String source, {bool multiLine = false, bool caseSensitive = true}) 表示一个类型为String位置参数:source和两个可选的命名布尔参数``multiLine(默认为false)和caseSensitive(默认为true).
  • any(bool test(E element)), any作为一个函数, 接受函数test为参数, test函数返回bool, 而且接受一个类型为E位置参数:element.

命名参数

一些 Dart 函数和方法需要命名参数, 在 ClojureDart 中使用 .argname.

示例: (m/Text "Hello world" .maxLines 2 .softWrap true .overflow m/TextOverflow.fade)

可选参数(命名或非命名)

实现 Dart 可选参数的函数或方法时的语法.

  • [a b c .d .e] 表示 3 个位置参数和 2 个命名参数:de.
  • [a b c ... d e] 5 个位置参数, 3个固定2个可选.
  • [.a 42 .e] 两个命名参数 ab, a有默认值42.
  • [... a 42 b] 2 个可选的位置参数ab, b的默认值是42.

枚举

枚举值是类型上的静态属性. 例如, m/TextAlign.left.

常量表达式

  • Dart会在编译时进行常量去重(Constant Deduplication)和常量折叠(Constant Folding), 特别是使用常量构造函数的时候.
  • ClojureDart 最大程度地推断常量表达式. 在某些罕见情况下(如创建哨兵或令牌), 必须将表达式标记为唯一: ^:unique (Object),否则, 总是会得到相同的实例.

创建类

  • reify, deftypedefrecord 可以用于类型的创建
  • :extends 用于扩展类, 甚至是抽象类.
  • Mixins 使用 ^:mixin 标记.

操作符

Dart 支持操作符重载, 但不是所有操作符都是有效的 Clojure symbol(比如[]=). 所以, 可以在ClojureDart 中使用字符串作为方法名

  • list[i] = 42 对应 (. list "[]=" i 42), 设置list中i对应的值为42.
  • map[k] = v对应 (. map "[]=" k v) 设置map中k对应的v.
  • list[i] 对应 (. list "[]" i) 获取list中下标为i的元素.
  • map[k]改写为(. map "[]" k)获取map中keyk对应的value.

Getters/Setters

  • Dart 的属性properties不全是字段fields, 大多数是 getters/setter. 它们的行为类似于字段, 可以通过 (.-prop obj) 获取和 (set! (.-prop obj) 42)(.-prop! obj 42) 设置.
  • getterssetters是定义在reify/deftype/defrecord中的普通函数, getter的需要一个参数[this], 而setter需要俩[this v].
  • 如果定义在上级class或者接口中, 我们直接使用就好, 但是如果是自己定义的函数, 则需要增加^:getter or ^:setter.

必须的库

cljd.flutter 不是一个框架, 而是一个实用程序库, 旨在减少样板代码, 使 Flutter 更适合 Clojurists 的口味. 必须引入的两个库

  • [cljd.flutter :as f]
  • ["package:flutter/material.dart" :as m].

f/run [& widget-body]

  • main函数调用, 启动应用程序(一个 Widget). 其主体按照 f/widget 解释执行. 使应用程序的根部分可重载.

f/widget [& widget-body]

  • 所有宏的妈. 这个macro求值为一个Widget. 其主体由交错的表达式(epxression)指令(directive)组成.
  • 指令总是以关键字开头, 后跟一个表达式. 指令范围从日常的 :let 到特定的 :vsync.
  • 表达式通过命名参数 .child threading macro展开.

.child-threading

  • 在 "widget-body"中, 表达式通过名为 .child 的命名参数进行threading macro展开.
    (f/widget
     m/Center
     (m/Text "hello"))
    

    等价于

    (m/Center .child (m/Text "hello"))
    

    当两个表达式由带点的符号分隔时, 使用该符号进行threading.

    m/MaterialApp
    .home
    m/Scaffold
    .body
    (m/Text "Don't stop it now!" )
    

:let 指令

  • (f/widget :let [some bindings] ...) 重写为 (let [some bindings] (f/widget ...)).

:key 指令

  • 用于识别列表中的兄弟小部件, 避免状态混乱.

:watch 指令

:let不同, 表达式必须是可监视的, 绑定形式将依次绑定到由其可监视对象产生的值.

:watch [v an-atom]
(m/Text (str v))

an-atom改变时, :watch之后的所有内容将使用v绑定到an-atom的当前值进行更新.

此外, :watch支持:as操作符, 以暴露watchable本身.

:watch [mode (atom m/ThemeMode.system)
        :as mode-atom]
;; 后面可以使用`swap!` `reset!`去修改 mode-atom

可监视对象(watchables)

  • nil, atoms, cells, Streams, Futures, ListenablesValueListenables 以及任何 Subscribable 协议的扩展.

Cells

Cells非常适合维护和重用派生状态; 提示: 尝试通过继承绑定(参见 :bind)而不是函数参数来共享它们.

(f/$ " cache" ) ; 创建一个 cell

(f/$ expr), 类似于电子表格单元, 每当依赖项变化时更新其值.

依赖项可以是任何可监视对象, 包括其他 cells. 使用 f/<!(" take" )读取依赖项. f/<! 可以在直接或间接由 cell 调用的任何函数中使用.

:managed 指令

用于自动管理 *Controllers 等对象的生命周期. :managed 接受一个绑定向量, 类似于 :let, 但表达式必须生成需要被释放的对象(默认使用.dispose方法).

:bind 指令

沿着组件树而建立的动态绑定. :bind {:k v}, 建立一个从:kv的继承绑定, 对所有子级组件可见.

所有的子组件都可以使用:get拿到这些绑定的变量.

:get 指令

  • 检索通过 :bind 绑定的值.:get [:k1 :k2], 通过 :bind 检索绑定到 :k1:k2 的值.
  • 获取能够通过context获得的值:
    • :get [m/Navigator], 检索由 (m/Navigator.of context) 返回的实例.
    • :get [m/Theme] 通过 (m/Theme.of context) 获取当前应用的主题ThemeData, 用于访问颜色, 字体样式等主题属性.
    • :get [m/MediaQuery] 通过 (m/MediaQuery.of context) 获取当前设备的媒体信息(如屏幕尺寸, 像素密度, 文本缩放因子), 用于适配不同屏幕.
    • :get [m/Scaffold] 通过 (m/Scaffold.of context) 获取ScaffoldState, 可以管理当前页面的Scaffold(如显示SnackBar, 打开Drawer等).
    • :get [m/Form] 通过 (m/Form.of context) 获取FormState, 用于在表单中进行验证, 保存或重置操作.
    • :get [m/Localizations] 通过 (m/Localizations.of context, AppLocalizations) 获取本地化资源(如文本, 格式化信息等), 适用于支持多语言的应用.
    • :get [m/DefaultTabController] 通过 (m/DefaultTabController.of context) 获取TabController, 用于管理TabBar和TabView的控制和切换.
    • :get [m/Overlay] 通过 (m/Overlay.of context) 获取OverlayState, 用于在页面上动态添加或移除悬浮组件.
    • :get [m/ModalRoute] 通过 (m/ModalRoute.of context) 获取当前的Route, 通常用于访问页面的参数(如settings.arguments).

:context 指令

当Flutter请求更多上下文时使用, 比如:context ctx, 将ctx绑定到一个BuildContext实例.

在Flutter中, BuildContext是一个非常重要的概念. 它代表了构建Widget树时的上下文信息, 是Flutter中许多功能的关键.

BuildContext是Flutter用来在Widget树中定位和管理Widget的工具, 它包含了Widget树的层次结构信息. 以下是BuildContext的几个核心用途:

  1. 获取父Widget信息
  2. Widget树的构建和重建
  3. 与InheritedWidget通信
  4. 在StatelessWidget和StatefulWidget中使用
  5. 访问路由, 媒体查询, 主题等信息

:vsync 指令

绑定到 TickerProvider, 通常用于动画.

参考连接

本文翻译自ClojureDart-Cheatsheet.pdf

Tags: clojure flutter dart