License: MIT

一个受 SolidJS 启发的简单状态管理库。

该项目的目标是

  1. 简单易学
  2. 不以奇怪的解决方法对抗框架(例如 Flutter)。
  3. 不要有一个全局状态,而是将多个状态放在最合适的位置

学习

您应该了解 4 个主要概念

  1. 信号
  2. 副作用
  3. 资源
  4. Solid (仅 flutter_solidart)

信号

信号是 solidart 中响应式的基础。它们包含随时间变化的值;当您更改信号的值时,它会自动更新使用它的任何内容。

要创建信号,您必须使用 createSignal 方法

final counter = createSignal(0);

传递给 create 调用的参数是初始值,返回值是信号。

// Retrieve the current counter value
print(counter.value); // prints 0
// Increment the counter value
counter.value++;

如果您正在使用 flutter_solidart,您可以使用 SignalBuilder 小部件来自动响应信号值,例如

SignalBuilder(
  signal: counter,
  builder: (_, value, __) {
    return Text('$value');
  },
)

副作用

信号是可跟踪的值,但它们只占等式的一半。为了补充它们,还有可以被这些可跟踪值更新的观察者。副作用就是这样一个观察者;它会运行依赖于信号的副作用。

可以通过使用 createEffect 来创建副作用。副作用订阅 signals 数组中提供的任何信号,并在其中任何一个发生更改时重新运行。

因此,让我们创建一个在 counter 更改时重新运行的副作用

createEffect(() {
    print("The count is now ${counter.value}");
}, signals: [counter]);

资源

资源是专门用于处理异步加载的特殊信号。它们的目的以一种易于交互的方式包装异步值。

资源可以由一个 source 信号驱动,该信号为异步数据 fetcher 函数提供查询,该函数返回一个 Future

fetcher 函数的内容可以是任何东西。您可以命中典型的 REST 端点或 GraphQL 或任何生成 Future 的内容。资源对加载数据的方式没有意见,只是它们由 Future 驱动。

让我们创建一个资源

// The fetcher
Future<String> fetchUser() async {
    final response = await http.get(
      Uri.parse('https://swapi.dev/api/people/${userId.value}/'),
    );
    return response.body;
}

// The source
final userId = createSignal(1);

// The resource
final user = createResource(fetcher: fetchUser, source: userId);

如果您正在使用 ResourceBuilder,您可以响应资源的状态

ResourceBuilder(
  resource: user,
  builder: (_, resource) {
    return resource.on(
      // the call was successful
      ready: (data, refreshing) {
        return Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            ListTile(
              title: Text(data),
              subtitle: Text('refreshing: $refreshing'),
            ),
            ElevatedButton(
              // you can refetch if you want to update the data
              onPressed: user.refetch,
              child: const Text('Refresh'),
            ),
          ],
        );
      },
      // the call failed.
      error: (e, _) => Text(e.toString()),
      // the call is loading.
      loading: () {
        return const RepaintBoundary(
          child: CircularProgressIndicator(),
        );
      },
    );
  },
)

on 方法强制您处理资源的所有状态(readyerrorloading)。还有其他便捷方法可以仅处理特定状态。

Solid

Flutter 框架就像一个树。有祖先,也有后代。

您可能需要将信号传递到树的深处,这是不推荐的。您永远不应该将信号作为参数传递。

为了避免这种情况,我们有 Solid 小部件。

使用此小部件,您可以将信号传递到树中任何需要它的地方。

您已经看到过 Theme.of(context)MediaQuery.of(context),这个过程几乎相同。

让我们看一个例子来理解这个概念。

您将看到如何使用 Solid 构建一个切换主题功能,此示例也在此处 https://github.com/nank1ro/solidart/tree/main/examples/toggle_theme

/// The identifiers used for [Solid] signals.
enum SignalId { // [1]
  themeMode,
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    // Provide the theme mode signal to descendats
    return Solid( // [2]
      signals: {
          // the id of the signal and the signal associated.
        SignalId.themeMode: () => createSignal<ThemeMode>(ThemeMode.light),
      },
      child:
          // using a builder here because the `context` must be a descendant of [Solid]
          Builder(
        builder: (context) {
          // observe the theme mode value this will rebuild every time the themeMode signal changes.
          final themeMode = context.observe<ThemeMode>(SignalId.themeMode); // [3]
          return MaterialApp(
            title: 'Toggle theme',
            themeMode: themeMode,
            theme: ThemeData.light(),
            darkTheme: ThemeData.dark(),
            home: const MyHomePage(),
          );
        },
      ),
    );
  }
}

class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key});

  @override
  Widget build(BuildContext context) {
    // retrieve the theme mode signal
    final themeMode = context.get<Signal<ThemeMode>>(SignalId.themeMode); // [4]
    return Scaffold(
      appBar: AppBar(
        title: const Text('Toggle theme'),
      ),
      body: Center(
        child:
            // Listen to the theme mode signal rebuilding only the IconButton
            SignalBuilder( // [5]
          signal: themeMode,
          builder: (_, mode, __) {
            return IconButton(
              onPressed: () { // [6]
                // toggle the theme mode
                if (themeMode.value == ThemeMode.light) {
                  themeMode.value = ThemeMode.dark;
                } else {
                  themeMode.value = ThemeMode.light;
                }
              },
              icon: Icon(
                mode == ThemeMode.light ? Icons.dark_mode : Icons.light_mode,
              ),
            );
          },
        ),
      ),
    );
  }
}

在这个示例中,发生了许多事情,首先在 [1] 中,我们使用了一个枚举来存储所有 [SignalId]。您可以使用 Stringint 或任何您想要的东西。只需确保使用相同的 ID 来检索信号。

然后,在 [2] 中,我们使用 Solid 小部件将 themeMode 信号提供给后代。

Solid 小部件接受一个 signals 映射

  • 映射的键是信号 ID,在本例中为 SignalId.themeMode
  • 映射的值是一个返回 SignalBase 的函数。您可以创建一个信号或一个派生信号。值为函数是因为信号在首次使用时才被惰性创建,如果您从未访问该信号,它就不会被创建。

[3] 中,我们 observe 信号的值。observe 方法监听信号值并在值更改时重建小部件。它接受一个 id,即您要使用的信号标识符。此方法只能在 build 方法中调用。

[4] 中,我们使用给定的 id get 信号。这不会监听信号值。您可以在 initStatebuild 方法中使用此方法。

[5] 中,使用 SignalBuilder 小部件,我们每次信号值更改时都会重建 IconButton

最后在 [6] 中,我们更新信号值。

必须传递信号值的类型,否则会遇到错误,例如

  1. createSignal<ThemeMode>context.observe<ThemeMode>,其中 ThemeMode 是信号值的类型。
  2. context.get<Signal<ThemeMode>>,其中 Signal<ThemeMode> 是带其类型值的信号类型。

待办事项列表

  1. 添加更多单元测试
  2. 创建文档页面

示例

使用 flutter_solidart 的示例功能

展示所有 flutter_solidart 功能

学习 flutter_solidart 的所有功能,包括

  1. createSignal
  2. Show 小部件
  3. 使用 signal.select() 的派生信号
  4. 使用基本和高级用法的 Effect
  5. SignalBuilderDualSignalBuilderTripleSignalBuilder
  6. createResourceResourceBuilder
  7. Solid 及其细粒度响应式

GitHub

查看 Github