redux ngrx
by Andrey Goncharov
通过安德烈·贡恰洛夫(Andrey Goncharov)
另一个减少Redux(NGRX)应用程序样板的指南 (Yet another guide to reduce boilerplate in your Redux (NGRX) app)
我们在这里要覆盖什么? (What are we gonna cover here?)
In this article, we’re gonna discuss several ways/tips/tricks/ancient black magic rituals to reduce boilerplate in our overwhelmed-with-boilerplate Redux (and NGRX!) apps. I’ve come up with these over the years from first-hand production experience.
在本文中,我们将讨论几种方法/技巧/技巧/古老的黑魔法仪式,以减少我们不堪重负的Redux(和NGRX!)应用程序中的样板。 这些年来,我已经从第一手生产经验中提出了这些建议。
Let me be honest with you all. I wanted to speak just about my new micro-library flux-action-class at first. But it seems like sometimes tech blogs look more and more like Twitter lately…and maybe you want some more meaningful long read. So I thought: “What the heck? I got some experience and best practices of my own which I spilled some sweat and blood over. Maybe, it could help some people out there. Maybe, people out there could help me to improve some of it.”
让我对你们所有人诚实。 首先,我只想谈谈我的新微库通量作用课程 。 但是似乎有时候高科技博客最近看起来越来越像Twitter……也许您想要一些更有意义的长期阅读。 所以我想:“到底是什么? 我获得了一些自己的经验和最佳实践,这让我不知所措。 也许,它可以帮助一些人。 也许,在那里的人可以帮助我改善其中的一些。”
识别样板 (Identifying boilerplate)
Let’s take a look at a typical example of how to make AJAX requests in Redux. In this particular case let’s imagine we wanna get a list of cats from the server.
让我们看一下如何在Redux中进行AJAX请求的典型示例。 在这种特殊情况下,让我们想象一下我们想要从服务器中获取猫的列表。
If you’re wondering why I have selector factories (makeSelector…) take a look here
如果您想知道为什么我有选择器工厂(makeSelector ...),请看这里
I’m leaving out side effect handling on purpose. It’s a topic for a whole different article full of teenager’s anger and criticism for the existing ecosystem :D
我没有故意处理副作用。 这是整个不同文章的主题,充满了青少年对现有生态系统的愤怒和批评:D
This code has several weak spots:
这段代码有几个弱点:
- Action creators are unique objects themselves but we still need action types for serialization purposes. Could we do better? 动作创建者本身就是唯一的对象,但是出于序列化的目的,我们仍然需要动作类型。 我们可以做得更好吗?
As we add entities we keep duplicating the same logic for flipping
loading
flag. Actual server data and the way we want to handle it may change, but logic forloading
is always the same. Could we get rid of it?当我们添加实体时,我们将重复相同的逻辑以翻转
loading
标志。 实际的服务器数据以及我们要处理的数据方式可能会发生变化,但是loading
逻辑始终是相同的。 我们可以摆脱它吗?- Switch statement is O(n) (which is not a solid argument by itself because Redux is not very performant anyway). Redux requires a couple extra lines of code for each case and switches can not be easily combined. Could we figure out something more performant and readable? Switch语句为O(n)(它本身不是一个可靠的参数,因为Redux并不是很有效)。 Redux在每种情况下都需要额外的几行代码,并且开关无法轻松组合。 我们能找出一些更高性能和可读性的东西吗?
- Do we really need to keep an error for each entity separately? 我们真的需要为每个实体单独保留一个错误吗?
- Using selectors is a good idea. This way we have an abstraction over our store and can change its shape without breaking the whole app by just adjusting our selectors. Yet we have to create a factory for each selector due to how memoizaion works. Is there any other way? 使用选择器是一个好主意。 这样,我们可以对商店进行抽象处理,并且可以通过调整选择器来更改其形状而不会破坏整个应用程序。 然而,由于记忆的工作原理,我们必须为每个选择器创建一个工厂。 还有其他办法吗?
提示1:摆脱动作类型 (Tip 1: Get rid of action types)
Well, not really. But we can make JS generate them for us!
好吧,不是真的。 但是我们可以让JS为我们生成它们!
Let’s take a minute here to think why we even need action types. Of course, to help the reducer somehow differentiate between incoming actions and change our state accordingly. But does it really have to be a string? If only we had a way to create objects (actions) of certain types… Classes to the rescue! We most definitely could use classes as action creators and do switch
by type. Like this:
让我们在这里花点时间思考为什么我们甚至需要动作类型。 当然,为了帮助减速器以某种方式区分进来的动作并相应地更改我们的状态。 但这真的必须是字符串吗? 如果只有一种方法可以创建某些类型的对象(动作),那么就可以救援了! 我们绝对可以将类用作动作创建者,并按类型进行switch
。 像这样:
All good, but here’s a thing… We can no longer serialize and deserialize our actions. They are no longer simple objects with a prototype of Object. All have unique prototypes which actually makes switching over action.constructor
work. Dang, I liked the idea of serializing my actions to a string and attaching it to bug reports. So could we do even better?
一切都很好,但是这里有件事……我们不能再序列化和反序列化我们的操作。 它们不再是带有对象原型的简单对象。 它们都有独特的原型,实际上使切换action.constructor
起作用。 Dang,我喜欢将操作序列化为字符串并将其附加到错误报告的想法。 那我们还能做得更好吗?
Actually, yes! Luckily each class has a name, which is a string, and we could utilize them. So for the purposes of serialization, each action needs to be a simple object with field type
(please, take a look here to learn what else any self-respecting action should have). We could add getter type
to each one of our classes which would use class's name.
其实,是! 幸运的是,每个类都有一个名称,它是一个字符串,我们可以利用它们。 因此,出于序列化的目的,每个动作都必须是一个具有字段type
的简单对象(请在这里看看,以了解其他任何自重动作应该具有的内容)。 我们可以将getter type
添加到我们每个使用类名称的类中。
It would work, but this way we can not prefix our action types as this great proposal suggests (actually, I like its successor even more). To work around prefixing we should stop using class’ name directly and create another getter for it. This time a static one.
它将起作用,但是通过这种方式,我们不能像这个好建议所建议的那样在操作类型前加上前缀(实际上,我更喜欢它的后继者 )。 要解决前缀问题,我们应该停止直接使用类的名称,并为其创建另一个getter。 这次是静态的。
Let’s polish it a little to avoid code duplication and add one more assumption to reduce boilerplate even further. If action is an error action payload
must be an instance of Error
.
让我们对其进行完善,以避免代码重复,并增加一个假设以进一步减少样板。 如果action是一个错误操作,那么payload
必须是Error
一个实例。
At this point, it works perfectly with NGRX. Redux is complaining about dispatching non-plain objects (it validates the prototype chain). Fortunately, JS allows us to return an arbitrary value from the constructor and we do not really need our actions to have a prototype.
此时,它可以与NGRX完美配合。 Redux抱怨调度非普通对象(它验证了原型链)。 幸运的是,JS允许我们从构造函数中返回任意值,并且我们实际上不需要采取行动就可以拥有原型。
Not to make you guys copy-paste ActionStandard
class and worry about its reliability, I created a small library called flux-action-class, which already has all that code covered with tests with 100% code coverage, written in TypeScript for TypeScript and JavaScript projects.
为了ActionStandard
你们复制粘贴ActionStandard
类并担心它的可靠性,我创建了一个名为flux-action-class的小型库 ,该库已经包含了用100%代码覆盖率进行测试的所有代码,并使用TypeScript为TypeScript和JavaScript编写项目。
提示2:组合减速机 (Tip 2: Combine your reducers)
The idea is simple: use combineReducers not only for top level reducers, but for combining reducers for loading
and other stuff. Let the code speak for itself:
这个想法很简单:不仅可以将CombineReducers用于顶级减速器,而且可以将减速器组合用于loading
和其他东西。 让代码说明一切:
提示3:切换开关 (Tip 3: Switch away from switch)
Use objects and pick from them by key instead! Picking a property of an object by key is O(1) and it looks much cleaner if you ask me. Like this:
使用对象并通过键从中选择! 通过键选择对象的属性为O(1),如果您问我,它看起来更干净。 像这样:
I suggest we refactor reducerLoading
a little bit. With the introduction of reducer maps, it makes sense to return a reducer map from reducerLoading
. We could extend it if needed (unlike switches).
我建议我们重构reducerLoading
一点。 通过引入reducer映射,从reducerLoading
返回一个reducer映射是reducerLoading
。 如果需要,我们可以扩展它(与开关不同)。
Redux’s official documentation mentions this, but for some reason, I saw lots of people still using switch-cases. There’s already a library for createReducer
. Do not hesitate to use it.
Redux的官方文档中提到了这一点 ,但是由于某些原因,我看到很多人仍在使用开关盒。 已经有一个createReducer
库 。 不要犹豫,使用它。
提示4:拥有全局错误处理程序 (Tip 4: Have a global error handler)
It’s not necessary to keep an error for each entity. In most cases, we need to display an error dialog or something. The same error dialog for all them!
不必为每个实体都保留一个错误。 在大多数情况下,我们需要显示错误对话框或其他内容。 他们所有的错误对话框都一样!
Create a global error handler. In the most simple case it could look like this:
创建一个全局错误处理程序。 在最简单的情况下,它可能看起来像这样:
Then in your side-effect’s catch
block dispatch ErrorInit
. It could look like this with redux-thunk:
然后在副作用的catch
块中调度ErrorInit
。 使用redux-thunk可能看起来像这样:
Then you could stop providing a reducer for error
part of cats' state and CatsGetError
just to flip loading
flag.
然后,您可以停止为猫的状态和CatsGetError
error
部分提供化CatsGetError
,以翻转loading
标志。
提示5:停止记忆所有内容 (Tip 5: Stop memoizing everything)
Let’s take a look at a mess we have with selectors one more time. I omitted makeSelectorCatsError
because of what we discovered in the previous section.
让我们再来看一次与选择器的混乱情况。 由于我们在上一节中发现了什么,所以我省略了makeSelectorCatsError
。
Why would we create memoized selectors for everything? What’s there to memoize? Picking an object’s field by key (which is exactly what’s happening here) is O(1). Just write a regular non-memoized function. Use memoization only when you want to change the shape of the data in your store in a way that requires non-constant time before returning it to your component.
为什么我们要为所有内容创建记忆选择器? 有什么要记住的? 通过键选择对象的字段(这正是此处发生的情况)为O(1)。 只需编写一个常规的非记忆函数即可。 仅当您要更改存储中数据的形状而又需要非常长时间才能将其返回到组件之前,才使用备忘录。
Memoization could make sense only if computed some derived data. For this example let’s imagine that each cat is an object with field name
and we need a string containing names of all cats.
只有计算了一些派生数据,记忆化才有意义。 对于此示例,我们假设每只猫都是一个具有字段name
的对象,并且我们需要一个包含所有猫的名称的字符串。
结论 (Conclusion)
Let’s take a look at what we started with:
让我们看一下我们的开始:
And what the result is:
结果是:
Hopefully, you found something useful for your project. Feel free to communicate your feedback to me! I most certainly appreciate any criticism and questions.
希望您发现了一些对您的项目有用的东西。 随时向我传达您的反馈! 我当然很感谢任何批评和疑问。
翻译自: https://www.freecodecamp.org/news/yet-another-guide-to-reduce-boilerplate-in-your-redux-ngrx-app-3794a2dd7bf/
redux ngrx