搜索插件、代码仓与 APP 分类

插件详情v1.2.0

functional_data

用于由 `function_data_generator` 包生成的代码的镜头注解和辅助工具。

AndroidiOSWebmacOSWindowsLinux
近 30 天下载
16.9万
喜欢数
19
Pub 分数
150

安装插件

flutter pub add functional_data

插件介绍

基于插件公开信息、社区数据与项目使用情况整理的选型参考。

FunctionalData 是一个 Dart 包,提供注解和实用工具帮助创建透镜——一种用于访问和修改嵌套数据结构的函数式编程构造。它与 `function_data_generator` 代码生成器配合使用,可自动生成无样板、不可变的数据类,并具备强大的更新功能。该包允许对深层嵌套对象进行安全、可组合的更新,而无需修改原对象,从而在 Flutter 和 Dart 应用中推广函数式编程原则。非常适合状态管理和数据转换工作流。

适合场景

  • 不可变状态更新
  • 嵌套数据操作
  • Flutter 应用中的状态管理
  • 函数式编程模式
  • 数据类的代码生成

主要特点

  • 自动镜头生成
  • 不可变性支持
  • 深度嵌套更新
  • 类型安全的转换
  • 与代码生成器的集成

适合对象

  • 使用函数式编程的开发者
  • 具有复杂状态的 Flutter 应用
  • 注重不可变性的团队
  • 代码生成工作流
  • 数据密集型应用

使用建议

  • 需要 `function_data_generator`
  • 镜片的学习曲线
  • 有限的运行时反射
  • 不适用于简单数据模型
  • 需要额外的构建步骤

原始 README

以下为关联源码仓库的 README 原文快照,最新内容请访问源码仓库。

Functional Data

Build

Simple and non-intrusive code generator for boilerplate of data types. The package generates a simple mixin with operator==, hashCode, copyWith, toString, as well as lenses.

Boiler plate

Because the boiler plate is generated as a mixin, it is minimally intrusive on the interface of the class. You only have to provide a constructor with named arguments for all fields and extend the generated mixin.

@FunctionalData()
class Person extends $Person {
  final String name;
  final int age;

  const Person({this.name, this.age});

  const Person.anonymous() : this(name: "John Doe", age: null);

  int get ageInDays => numberOfDaysInMostYears * age;

  static const numberOfDaysInMostYears = 356;
}

Because of this design, you have complete control over the class. You can, for example, add named constructors or methods to the class like you're used to.

Lenses

For every class, lenses are generated for all fields which allow viewing a field or creating a new instance of the classes with that field modified in some way. For example, the lens of Person's name is Person$.name. To focus a lens on a specific instance use its of method:

final teacher = Person(name: "Arthur", age: 53);

print(Person$.name.of(teacher).value);
// -> "Arthur"

print(Person$.age.of(teacher).update(60);
// -> Person(name: "Arthur", age: 60)

print(Person$.name.of(teacher).map((name) => name.toUpperCase()));
// -> Person(name: "ARTHUR", age: 53)

This isn't very exciting yet. The power of lenses comes to light when you combine them. It allows you to easily create a copy of a large nested data structure with one of the fields in a leaf modified. Two lenses can be chained using then.

class Course extends $Course {
  final String name;
  final List<Person> students;

  const Course({this.students});
}

final programming = Course(name: "Programming 101", students: [Person(name: "Jane", age: 21), Person(name: "Tom", age: 20)]);

final firstStudentsName = Course$.students.then(List$.first<Person>()).then(Person$.name);

print(firstStudentsName.of(programming).update("Marcy"));
// -> Course(students: [Person(name: "Marcy", age: 21), Person(name: "Tom", age: 20)]

Compare this with the alternative:

final firstStudent = programming.students.first;
final updatedFirstStudent = Person(name: "Marcy", age: firstStudent.age);
final updatedStudents = [updatedFirstStudent] + programming.students.skip(1);
final updatedCourse = Course(name: programming.name, students: updatedStudents);

This is much less readable and error prone. Imagine what happens when one of the classes gains a field.

Full example:
// lens.dart
import 'package:collection/collection.dart';

import 'package:functional_data/functional_data.dart';

part 'lens.g.dart';

// Only requirement is that it has a constructor with named arguments for all fields
@FunctionalData()
class Foo extends $Foo {
  final int number;
  final String name;

  const Foo({this.number, this.name});
}

@FunctionalData()
class Bar extends $Bar {
  final Foo foo;

  @CustomEquality(DeepCollectionEquality())
  final List<Foo> foos;

  final String driver;

  const Bar({this.foo, this.foos, this.driver});
}

void main() {
  final foo = Foo(number: 42, name: "Marvin");
  final bar = Bar(foo: foo, foos: [Foo(number: 101, name: "One"), Foo(number: 102, name: "Two")], driver: "One");

  final fooName = Bar$.foo.then(Foo$.name);
  // print(fooName.map((name) => name.toUpperCase(), bar));
  print(fooName.of(bar).map((name) => name.toUpperCase()));
  // Bar(foo: Foo(number: 42, name: MARVIN), foos: [Foo(number: 101, name: One), Foo(number: 102, name: Two)], driver: One)

  final firstFooName = Bar$.foos.then(List$.atIndex<Foo>(0)).then(Foo$.name);
  // print(firstFooName.update(bar, "Twee"));
  print(firstFooName.of(bar).update("Twee"));
  // Bar(foo: Foo(number: 42, name: Marvin), foos: [Foo(number: 101, name: Twee), Foo(number: 102, name: Two)], driver: One)

  final nameOfFooNamedTwo = Bar$.foos.then(List$.where<Foo>((foo) => foo.name == "Two")).then(Foo$.name);
  print(nameOfFooNamedTwo.update(bar, "Due"));
  // Bar(foo: Foo(number: 42, name: Marvin), foos: [Foo(number: 101, name: One), Foo(number: 102, name: Due)], driver: One)

  final driversNumber =
      Bar$.foos.thenWithContext((bar) => List$.where<Foo>((foo) => foo.name == bar.driver).then(Foo$.number));
  print(driversNumber.of(bar).value);
  // 101
}

Code generation configuration

Class level configuration

To specify which features should be generated for the class, you can send arguments to @FunctionalData generator.

Example:

@FunctionalData(
  generateCopy: false,
  generateLenses: false,
)
class Foo extends _$Foo {}
Project level configuration

To specify which features should be generated for you whole project, create a file called functional_data_options.yaml in the root of your project. Class specific arguments will override the project level configuration.

Example with all possible configurations:

generateCopyWith: false
generateCopyUsing: false
generateLenses: false

Contributing

Setup

This project uses melos to manage all the packages inside this repo.

  1. Install melos via the top level workspace dart pub get.
  2. Setup melos to point to the dependencies in your local folder: melos bootstrap

Testing the Code generator:

  • Either run melos generate:dart in the top level folder, or dart run build_runner build in the example folder.

所属分类

依赖

依赖清单 2 项

源码仓库

使用此插件的项目