简体   繁体   English

嵌套 AppState 的 Flutter 中减速器的最佳实践是什么

[英]What are the best practices for reducers in Flutter with nested AppState

Im wondering about the best practice for the AppState reducer, if the AppState holds Lists or other complex objects.如果 AppState 包含列表或其他复杂对象,我想知道 AppState 减速器的最佳实践。

So an example:举个例子:

Setting: Let's say, im writing a weight tracking app and for every weight entry, there's a date and comment attached.设置:假设我正在编写一个体重跟踪应用程序,并且对于每个体重条目,都附有日期和评论。 I want to display various things regarding the weight entries of the user, so i decided to lift them into the appstate.我想显示有关用户体重条目的各种信息,所以我决定将它们提升到 appstate 中。 I decided to seperate them from the user data and hold them in a seperate list, to make the appstate as flat as possible and to seperate different aspects (user data, settings, weight entries).我决定将它们与用户数据分开并将它们保存在一个单独的列表中,以使 appstate 尽可能平坦,并将不同的方面(用户数据、设置、权重条目)分开。

Code:代码:

AppState:应用状态:

class AppState{
  //username, birthdate, etc.
  List<Entry> entries;
}

Entry:入口:

class Entry{
  String userId;
  double weight;
  DateTime date;
  String comment;
}

2 Actions to modify/add an entry: 2 修改/添加条目的操作:

class UpdateCommentOnEntry{
  int index;
  String comment;
}

class AddEntry{
  Entry entry;
}

Task: What is the best way to handle this List in the AppState Reducer wrt.任务:在 AppState Reducer 中处理此列表的最佳方法是什么? the 2 Actions? 2 行动?

Concerns: Performance and Memory Management on a mobile device.关注点:移动设备上的性能和 Memory 管理。 Basically i want to copy as least as possible.基本上我想尽可能少地复制。

Possible Solutions:可能的解决方案:

-1) As i understand, this solution is wrong/violates the Redux Standard, because it mutates the current state: -1)据我所知,这个解决方案是错误的/违反了 Redux 标准,因为它改变了当前的 state:

AppState reducer(AppState state, dynamic action) {
  if(action is UpdateCommentOnEntry){
    state.entries[action.index].comment=action.comment;
  } else if(action is AddEntry){
    state.entries.add(action.entry);
  }
  return state;
}

1) This solution copies the whole list 1)此解决方案复制整个列表

AppState reducer(AppState state, dynamic action) {
  if(action is UpdateCommentOnEntry){
    List<Entry> newList;
    for(int i = 0; i<state.entries.length; i++){
      if(i!=action.index){  
        newList.add(state.entries[i]);
      } else{
        newList.add(state.entries[i].copyWith(comment: action.comment));
      }
    } 
    return state.copyWith(entries = newList);
  } else if(action is AddEntry){
    List<Entry> newList = List<Entry>.from(state.entries);
    newList.entries.add(action.entry);
    return state.copyWith(entries: newList);
  } else {
    return state;
  }
}

2) This solution doesn't copy the list, but does copy the AppState, but it still references on the same list. 2)这个解决方案不复制列表,但复制了AppState,但它仍然在同一个列表中引用。 And this List does get mutated, so is this also against the Redux Standard?而且这个列表确实发生了变异,所以这是否也违反了 Redux 标准?

AppState reducer(AppState state, dynamic action) {
  if(action is UpdateCommentOnEntry){
    state.entries[i] = state.entries[i].copyWith(comment: action.comment);
    return state.copyWith(entries: state.entries);
  } else if(action is AddEntry){
    List<Entry> newList = List<Entry>.from(state.entries);
    newList.entries.add(action.entry);
    return state.copyWith(entries: newList);
  } else {
    return state;
  }
}

I'm just hesitating to copy the whole List every time i want to change a single entry.每次我想更改单个条目时,我都会犹豫是否复制整个列表。 What is your solution, what are best practices here?您的解决方案是什么,这里的最佳实践是什么? Im thankfull for any advice.我很感谢任何建议。

In Redux, every part of your state should be immutable, but in dart lists are mutable by default.在 Redux 中,state 的每个部分都应该是不可变的,但在 dart 中,列表默认是可变的。

So every time you pass a list to something, you will defensively copy your list to prevent accidental mutation of the list, won't you?所以每次你将一个列表传递给某个东西时,你都会防御性地复制你的列表以防止列表的意外突变,不是吗? ;-) ;-)

If your state would contain only immutable lists / sets / maps you could pass them around safely without copying them them every time you grant access to this list.如果您的 state 仅包含不可变列表/集合/映射,则您可以安全地传递它们,而无需在每次授予对该列表的访问权限时复制它们。

But I want do add something to this stupid immutable list, Well, you can't!但是我想在这个愚蠢的不可变列表中添加一些东西,好吧,你不能! It's immutable!它是不变的!

So you create a whole new immutable list that contains a copy of the old list plus the new item.因此,您创建了一个全新的不可变列表,其中包含旧列表的副本和新项目。 But because this new list is immutable, you don't need to copy it every time you pass it around!但是因为这个新列表是不可变的,所以你不需要每次传递它时都复制它! That is what rescues the performance of your app!这就是拯救应用程序性能的原因! Copy once , pass it around without copying every time you want to use it.复制一次,传递它,而不是每次你想使用它时复制

Dart doesn't have a great way to create or work with immutable lists by its own. Dart 本身没有创建或使用不可变列表的好方法。 But - like for many other common problem - there is a great package for this purpose:但是 - 就像许多其他常见问题一样 - 为此目的有一个很棒的 package:

fast_immutable_collections

I think, the name describes perfectly, what is does!我想,这个名字完美地描述了,它是做什么的!

Every list method, that would mutate the original list , instead returns a new immutable list .每个 list 方法都会改变原始 list ,而是返回一个新的不可变 list

Instead of List<MyType> myList you use IList<MyType> .您使用IList<MyType>而不是List<MyType> myList

Instead of代替

state.entries[action.index].comment = action.comment;

You use你用

state.copyWith(entries: state.entries.replace(action.index, action.entry)

Instead of代替

state.entries.add(action.entry);

You use你用

state.copyWith(entries: state.entries.add(action.entry)

Creation of an IList is easy: [2, 5, 6].lock , for example, creates an IList containing the values [2, 5, 6] .创建 IList 很容易:例如, [2, 5, 6].lock会创建一个包含值[2, 5, 6]的 IList。

Immutability might seem to cost some performance, but usually it doesn't and even if it does, it is really worth it, Don't worry about any accidental mutation: just pass your immutable objects around to every thing that needs it, NO defensive copying any more.不可变性似乎会消耗一些性能,但通常不会,即使这样做,也确实值得再复制。 Less problems with race conditions in asynchronous code.异步代码中的竞争条件问题较少。 ... ...

Ok, you will need a copyWith method for your immutable objects, but if you use redux, you will need this for your state any way!好的,您将需要一个用于不可变对象的copyWith方法,但如果您使用 redux,您的 state 将需要此方法!

And yes, there is a great package for immutable objects and generation of copyWith methods: freezed是的,有一个很棒的 package 用于不可变对象和 copyWith 方法的生成: freezed

You just need to define your properties inside a constructor and freezed will do the job for you.您只需要在构造函数中定义您的属性,并且 freezed 将为您完成这项工作。

It creates your fields, a copyWith method, equality operator + hashCode , toString method and even json serialization if you need it.如果需要,它会创建您的字段、 copyWith方法、相等运算符 + hashCodetoString方法,甚至 json 序列化。

In addition, it gives you a great way to create and work with union types .此外,它为您提供了一种创建和使用联合类型的好方法。 Nice for state and action classes (or event classes, if you use bloc ).非常适合 state 和操作类(或事件类,如果您使用bloc )。

redux + freezed + fast_immutable_collections = perfect match redux + 冻结 + fast_immutable_collections = 完美匹配

(in my honest opinion!) (以我的诚实观点!)

By the way: if you want to use mutable lists somewhere in your application, instead of copying every item in a for-loop, you can just write顺便说一句:如果你想在应用程序的某个地方使用可变列表,而不是复制 for 循环中的每个项目,你可以写

myListCopy = [...myList];

And if you need a mutable copy of your IList myIList you can use myIList.unlock .如果您需要IList myIList的可变副本,您可以使用myIList.unlock

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM