by Justin Falcone
賈斯汀·法爾科內(Justin Falcone)
Redux有何優點? (What’s So Great About Redux?)
Redux elegantly handles complex state interactions that are hard to express with React’s component state. It is essentially a message-passing system, like the kind seen in Object-Oriented programming, but implemented as a library instead of in the language itself1. As in OOP, Redux inverts the responsibility of control from caller to receiver — the UI doesn’t directly manipulate the state but rather sends it a message for the state to interpret.
Redux優雅地處理了復雜的狀態交互,這些交互很難用React的組件狀態來表達。 它本質上是一個消息傳遞系統,類似于在面向對象的編程中看到的那種,但是實現為庫而不是使用語言本身1 。 與在OOP中一樣,Redux將控制的責任從調用者轉變為接收者-UI并不直接操縱狀態,而是向其發送消息以供狀態解釋。
Through this lens, a Redux store is an object, reducers are method handlers, and actions are messages. store.dispatch({ type: "foo", payload: "bar" })
is equivalent to Ruby's store.send(:foo, "bar")
. Middleware are used in much the same way Aspect-Oriented Programming (e.g. Rails' before_action
) and React-Redux's connect
is dependency injection.
從這個角度來看,Redux存儲是一個對象,reduce是方法處理程序,而action是消息。 store.dispatch({ type: "foo", payload: "bar" })
等同于Ruby的store.send(:foo, "bar")
。 中間件的使用方式與面向方面的編程(例如,Rails的before_action
)和React-Redux的connect
是依賴注入相同。
為什么這是可取的? (Why is this desirable?)
- The inversion of control described above ensures that the UI doesn’t need to be updated if the implementation of state transitions changes. Adding complex features like logging, undo or even time travel debugging are almost trivial. Integration tests are just a matter of testing that the right action is dispatched; the rest can be unit-tested. 上面所述的控制反轉可確保在狀態轉換的實現發生更改時,無需更新UI。 添加諸如日志記錄,撤消甚至時間旅行調試之類的復雜功能幾乎是微不足道的。 集成測試只是測試是否已分派正確的操作; 其余的可以進行單元測試。
- React’s component state is pretty clunky for state that touches multiple parts of your app, such as user info and notifications. Redux gives you a state tree thats independent of your UI to handle these cross cutting concerns. Furthermore, having your state live outside of the UI makes things like persistence easier — you only need to deal with serializing to localStorage or URLs in a single place. 對于涉及應用程序多個部分的狀態,例如用戶信息和通知,React的組件狀態非常笨拙。 Redux為您提供了獨立于您的UI的狀態樹,以處理這些交叉問題。 此外,將狀態保留在UI之外還使持久性變得更容易-您只需要在一個地方處理序列化到localStorage或URL的問題。
Redux’s titular “reducers” provide incredible flexibility for handling actions — composition, multiple dispatch, even
method_missing
-style parsing.Redux的名義上的“減少器”為處理動作提供了令人難以置信的靈活性,包括組合,多重調度甚至
method_missing
樣式的解析。
這些都是不尋常的情況。 普通情況如何? (These are all unusual cases. What about the common cases?)
Well, there’s the problem.
好吧,這里有問題。
An action could be interpreted as a complex state transition, but most of them set a single value. Redux apps tend to end up with a bunch of actions that set a single value; there’s a distinct reminder of manually writing setter functions in Java.
動作可以解釋為復雜的狀態轉換,但是大多數動作都設置一個值。 Redux應用程序最終會產生一系列設置單個值的操作。 有一個特別的提醒,那就是用Java手動編寫setter函數。
A fragment of state could be used all over your app, but most state maps 1:1 with a single part of the UI. Putting that state in Redux instead of component state just adds indirection without abstraction.
狀態片段可以在您的整個應用程序中使用,但是大多數狀態與UI的一部分是1:1映射的。 將該狀態置于Redux中而不是組件狀態中只會添加間接而無需抽象 。
A reducer function could do all sorts of metaprogramming weirdness, but in most cases it’s just single-dispatch on the action’s type field. This is fine in languages like Elm and Erlang, where pattern matching is terse and highly expressive, but rather clunky in JavaScript with
switch
statements.reducer函數可以完成各種元編程怪異,但是在大多數情況下,它只是對動作的type字段進行一次分派。 這在Elm和Erlang這樣的語言中很好,在這些語言中模式匹配簡潔且表達能力強,但在帶有
switch
語句JavaScript中相當笨拙。
But the really insidious thing is that when you spend all your time doing the boilerplate for common cases, you forget that better solutions for the special cases even exist. You encounter a complex state transition and solve it with a function that dispatches a dozen different value-setting actions. You duplicate state in the reducer rather than distributing a single state slice across the app. You copy and paste switch cases across multiple reducers instead of abstracting it into shared functions.
但是真正的陰險之處在于,當您花費所有時間來處理常見情況時,您會忘記甚至存在針對特殊情況的更好的解決方案。 您會遇到一個復雜的狀態轉換,并使用一個分派許多不同的值設置操作的函數來解決它。 您可以在化簡器中復制狀態,而不是在應用程序中分配單個狀態片。 您可以跨多個化簡器復制和粘貼切換案例,而不是將其抽象為共享函數。
It’s easy to dismiss this as mere “Operator Error” — they didn’t RTFM, A Poor Craftsman Blames His Tools — but the frequency of these problems should raise some concerns. What does it say about a tool if most people are using it wrong?
僅僅將其視為“操作員錯誤”就很容易了-他們沒有RTFM,可憐的工匠責備他的工具-但是這些問題的發生頻率應該引起一些擔憂。 如果大多數人使用錯誤,對工具有何評價?
所以我應該只在常見情況下避免Redux并在特殊情況下保存它嗎? (So should I just avoid Redux for the common cases and save it for the special ones?)
That’s the advice the Redux team will give you — and that’s the advice I give to my own team members: I tell them to avoid it until using setState becomes truly untenable. But I can’t bring myself to follow my own rules, ’cause there’s always some reason you want to use Redux. You might have a bunch of set_$foo
actions, but setting any value also updates the URL, or resets some more transient value. Maybe you have a clear 1:1 mapping of state to UI, but you also want to have logging or undo.
這就是Redux團隊會給您的建議—這就是我給自己的團隊成員的建議:我告訴他們避免使用它,直到使用setState真正站不住腳。 但是我無法讓自己遵循自己的規則,因為您總是有某些理由要使用Redux。 您可能有一堆set_$foo
操作,但是設置任何值也會更新URL,或重置一些更臨時的值。 也許您有清晰的1:1狀態映射到UI,但您也想進行日志記錄或撤消操作。
The truth is that I don’t know how to write, much less teach, “good Redux.” Every app I’ve worked on is full of these Redux antipatterns, either because I couldn’t think of a better solution myself or because I couldn’t convince my teammates to change it. If the code of a Redux “expert” is mediocre, what hope does a novice have? If anything, I’m just trying to counterbalance the prevailing “Redux all the things!” approach in the hope that they will be able to understand Redux on their own terms.
事實是,我不知道該怎么寫,更不用說教 “好Redux”了。 我開發的每個應用程序都充滿了這些Redux反模式,或者是因為我自己想不到更好的解決方案,或者因為我無法說服隊友進行更改。 如果Redux“專家”的代碼中等,那么新手會有什么希望? 如果有的話,我只是想抵消當時流行的“全部還原!” 希望他們能夠以自己的方式理解Redux。
那么在那種情況下我該怎么辦? (So what do I do in that case?)
Fortunately, Redux is flexible enough that third-party libraries can integrate with it to handle the common cases — Jumpstate for example. And to be clear, I don’t think it’s wrong for Redux to focus on the low-level stuff. But outsourcing these basic features to third parties creates an additional cognitive load and opportunity for bikeshedding — each user needs to essentially build their own framework from these parts.
幸運的是,Redux具有足夠的靈活性,可以與第三方庫集成以處理常見情況(例如Jumpstate) 。 需要明確的是,我認為Redux專注于低層次的東西并沒有錯。 但是,將這些基本功能外包給第三方會帶來額外的認知負擔和騎車脫落的機會-每個用戶實際上都需要從這些部分構建自己的框架。
有些人喜歡這種事情。 (Some people are into that sort of thing.)
And I’m one of ‘em! But not everybody is. I personally love Redux and use it for just about everything that I do, but I also love trying out new Webpack configurations. I am not representative of the general population. I’m empowered by the flexibility to write my own abstractions on top of Redux, but how empowered am I by the abstractions written by some senior engineer who never documented them and quit six months ago?
我是他們之一! 但不是每個人都是。 我個人很喜歡Redux并將其用于我所做的幾乎所有事情,但是我也喜歡嘗試新的Webpack配置。 我不代表一般人口。 我被靈活地寫我自己的終極版之上的抽象權力 ,但如何授權由一些資深工程師誰沒有記載他們不干半年前寫的抽象我是誰?
It’s quite possible to never encounter the hard problems that Redux is particularly good at handling, particularly if you’re a junior on a team where those tickets go to the more senior engineers. Your experience of Redux is “that weird library everyone uses where you have to write everything three times.” Redux is simple enough that you can use it mechanically, without deep understanding, but that’s a joyless and unrewarding experience.
很有可能永遠不會遇到Redux特別擅長處理的棘手問題,特別是如果您是團隊中的初級人員,而這些票會交給高級工程師。 您對Redux的經驗是“每個人都在使用怪異的庫來編寫所有內容三遍。” Redux非常簡單,您可以在沒有深入了解的情況下機械地使用它,但這是一種無聊又無益的體驗。
This brings me back to a question I raised earlier: what does it say about a tool if most people are using it wrong? A quality hand tool isn’t just useful and durable — it feels good to use. The most comfortable place to hold it is the correct place to hold it. It is designed not just for its task but also its user. A quality tool reflects the toolsmith’s empathy for the crafter.
這使我回到我先前提出的一個問題:如果大多數人錯誤地使用工具,它對工具有什么看法? 優質的手動工具不僅有用且耐用,而且使用起來感覺很好。 握住它的最舒適的地方是正確的地方。 它不僅針對其任務而設計,而且還針對其用戶而設計。 優質的工具反映了工具匠對Craft.io師的同情。
Where is our empathy? Why is “you’re doing it wrong” our reaction, and not “we could make this easier to use”?
我們的同情心在哪里? 為什么我們的React是“您做錯了”,而不是“我們可以使其更易于使用”?
There’s a related phenomenon in functional programming circles I like to call the Curse of the Monad Tutorial: explaining how they work is trivial, but explaining why they are valuable is surprisingly difficult.
我喜歡將函數式編程圈子中的一個相關現象稱為“ Monad教程的詛咒” :解釋它們的工作原理是微不足道的,但是解釋為什么它們很有價值卻令人驚訝地困難。
您是否認真地在本文中間刪除了monad教程? (Are you seriously dropping a monad tutorial in the middle of this post?)
Monads are a common pattern in Haskell that’s used for working with a wide range of computation — lists, error handling, state, time, IO. There’s syntactic sugar, in the form of do
notation, that allows you to represent sequences of monadic operations in a way that looks kind of like imperative code, much like how generators in javascript can make asynchronous code look synchronous.
Monad是Haskell中的一種常見模式,用于處理各種計算-列表,錯誤處理,狀態,時間,IO。 有一種形式為do
表示法的語法糖,它允許您以某種看起來像命令式代碼的方式表示單子運算的序列,就像javascript中的生成器如何使異步代碼看起來是同步的一樣。
The first problem is that describing monads in terms of what they’re used for is inaccurate. Monads were introduced to Haskell to handle side effects and sequential computation, but monads as an abstract concept have nothing to do with side effects or sequences; they’re a set of rules for how a pair of functions should interact and have no inherent meaning. The concept of associativity applies to arithmetic and set operations and list concatenation and null propagation but it exists fully independent of them.
第一個問題是,根據單子的用途來描述單子是不準確的。 Monad被引入Haskell來處理副作用和順序計算 ,但是monad作為一個抽象概念與副作用或序列無關。 它們是一對函數如何交互且沒有內在含義的一組規則。 關聯性的概念適用于算術和集合運算以及列表級聯和空傳播,但它完全獨立于它們而存在。
The second problem is that any bite-sized example of a monadic approach to X is more verbose — and therefore at least visually more complex — than the imperative approach. Explicit option types a la Maybe
are safer than checking for implicit null
but result in more, uglier code. Error handling with Either
types is often simpler to follow than code that can throw
from anywhere, but throwing is certainly more concise than manually propagating values. And side effects — state, IO, etc. — are trivial in an imperative language. Functional programming enthusiasts (myself included) would argue that side effects are too easy in these languages but convincing someone that any kind of programming is too easy is a hard sell.
第二個問題是,比起命令式方法,對X的單子方法的任何一口大小的示例都更加冗長-因此至少在視覺上更為復雜。 顯式選項類型Maybe
比檢查隱式null
更安全,但會導致代碼更丑陋。 與可以從任何地方throw
代碼相比,使用Either
類型的錯誤處理通常更容易遵循,但是拋出肯定比手動傳播值更簡潔。 在命令式語言中,副作用(狀態,IO等)微不足道。 函數式編程愛好者(包括我本人)會認為,這些語言的副作用太容易了 ,但是要說服某人任何類型的編程都太容易了,這是一件很難的事。
The real value is only visible at the macro scale — not just that any one of these use cases follows the monad laws, but that all of them follow the same laws. A set of operations that works in one case can work in every case: zipping a pair of lists into a list of pairs is “the same thing” as merging a pair of promises into a single promise that completes with a pair of results.
真正的價值僅在宏觀尺度上可見-不僅這些用例中的任何一個都遵循monad定律,而且所有用例都遵循相同的定律。 在每種情況下都可以使用一組操作在每種情況下都可以工作:將一對列表壓縮成對列表是“同一件事”,就像將一對promise合并為一個帶有一對結果的promise一樣。
這要去哪里嗎 (Is this going somewhere?)
The point is that Redux has the same problem — it’s difficult to teach not because it’s difficult but rather because it’s so simple. Understanding is not a matter of having knowledge so much as trusting the core idea in such a way that we can derive everything else through induction.
關鍵是Redux也有同樣的問題-很難教,不是因為難,而是因為它是如此簡單 。 理解并不僅僅是擁有知識,而是以一種可以通過歸納推導出其他一切的方式來信任核心思想。
It’s hard to share this understanding because the core ideas are banal truisms (avoid side effects) or abstract to the point of meaninglessness ((prevState, action) => nextSt
ate). Any single concrete example doesn't help; they showcase Redux's verbosity without demonstrating its expressivity.
很難分享這種理解,因為核心思想是平庸的無私(避免副作用)或抽象到毫無意義的程度( (prevState, action) => nextSt
ate)。 任何一個具體的例子都無濟于事。 他們展示了Redux的冗長性,卻沒有表現出它的表現力。
Once we are ?enlightened? a lot of us immediately forget what it felt like beforehand. We forget that our enlightenment came only through our own repeated failures and misunderstandings.
一旦“開悟”了,我們很多人會立即忘記事先的感覺。 我們忘記了,我們的啟蒙只是來自我們自己反復的失敗和誤解。
那你有什么建議呢? (So what do you propose?)
I would like us to admit we have a problem. Redux is simple, but it is not easy. This is a valid design choice, but it is nevertheless a tradeoff. Many people would benefit from a tool that traded some of the simplicity for ease-of-use. But large chunks of the community won’t even acknowledge that a tradeoff has been made!
我希望我們承認我們有問題。 Redux很簡單,但并不容易 。 這是一個有效的設計選擇,但仍然是一個折衷方案。 許多人將受益于將一些簡單性換成易用性的工具。 但是社區的大部分人甚至都不承認已經進行了權衡!
I think it’s interesting to contrast React and Redux because while React is a vastly more complicated piece of software and has a significantly larger API surface, it somehow feels easier to use and understand. The only absolutely necessary API features of react are React.createElement
and ReactDOM.render
— state, component lifecycle, even DOM events could have been handled elsewhere. Building these features into React made it more complicated, but they also made it better.
我認為將React和Redux進行對比很有趣,因為盡管React是一款非常復雜的軟件,并且具有更大的API界面,但從某種程度上來說,它更易于使用和理解。 React.createElement
唯一絕對必要的API功能是React.createElement
和ReactDOM.render
狀態,組件生命周期,甚至DOM事件也可以在其他地方處理。 將這些功能構建到React中使它變得更加復雜,但是它們也使它變得更好 。
“Atomic state” is an abstract concept that can inform your work once you understand it, but setState
is a method you can call on a React component that does atomic state management on your behalf, whether you understand it or not. It’s not a perfect solution — it’s less efficient than replacing state outright or mutating and forcing an update, and it has some footguns when it’s called asynchronously — but React is vastly better with setState
as a callable method rather than a vocabulary term.
“原子狀態”是一個抽象概念,可以在您理解工作后通知您,但setState
是一種方法,您可以調用React組件來代表您執行原子狀態管理,無論您是否理解。 這不是一個完美的解決方案-它效率不如直接替換狀態或更改并強制更新有效,并且在異步調用時具有一些先發優勢-但是React使用setState
作為可調用方法而不是詞匯術語要好得多。
Both the Redux team and the community are strongly opposed to expanding Redux’s API surface area, but the current approach of gluing a bunch of tiny libraries together is tedious even for experts and incomprehensible for beginners. If Redux cannot expand to have built-in support for the common cases, it needs a “blessed” framework to take that place. Jumpsuit could be a good start — it reifies the concepts of “actions” and “state” into callable functions while still preserving their many-to-many nature — but the actual library doesn’t matter as much as the act of blessing itself.
Redux團隊和社區都強烈反對擴大Redux的API表面積 ,但是當前將一堆小庫粘合在一起的方法即使對于專家來說也是乏味的,對于初學者來說也是難以理解的。 如果Redux無法擴展以提供對常見情況的內置支持,則它需要一個“有福的”框架來代替。 連身褲可能是一個好的開始-它將“動作”和“狀態”的概念重新定義為可調用函數,同時仍保留它們的多對多性質-但是實際的庫與祝福自己的行為無關緊要。
The irony in all this is that Redux’s raison d’etre is “Developer Experience”: Dan built Redux because he wanted to understand and recreate Elm’s time-traveling debugger. But as it developed its own identity — as it grew into the React ecosystem’s de facto OOP runtime — it gave up some of that focus on DX in exchange for configurability. This allowed the Redux ecosystem to bloom, but there’s a conspicuous absence where a humane, curated framework should be. Are we, the Redux community, ready to create it?
具有諷刺意味的是,Redux的存在理由是“開發人員體驗”:Dan創建Redux是因為他想了解并重新創建Elm的時間旅行調試器。 但是隨著它發展出自己的身份-成長為React生態系統的事實上的OOP運行時-它放棄了對DX的某些關注,以換取可配置性。 這使Redux生態系統得以蓬勃發展,但是顯然應該缺少人性化,精心策劃的框架。 我們Redux社區是否已準備好創建它?
Thanks to Matthew McVickar, a pile of moss, Eric Wood, Matt DuLeone, and Patrick Thomson for review.
感謝Matthew McVickar , 一堆苔蘚 , Eric Wood , Matt DuLeone和Patrick Thomson的審查。
Footnotes:
腳注:
[1] Why do you make a distinction between react / JS and object oriented programming? JavaScript IS object oriented, just not class-based.
[1]為什么您要區分React / JS和面向對象的編程? JavaScript是面向對象的,只是不是基于類的。
Object-Oriented programming, like functional programming, is a methodology, not a language feature. Some languages support this style better than others, or have a standard library that’s designed for the style, but if you’re sufficiently dedicated to the task, you can write in an object-oriented style in any language.
像功能編程一樣,面向對象的編程是一種方法,而不是語言功能。 某些語言比其他語言更好地支持此樣式,或者具有針對該樣式設計的標準庫,但是如果您對這項任務有足夠的投入,則可以使用任何語言以面向對象的樣式編寫。
JavaScript has a data structure it calls an Object, and most values in the language can be treated like objects, in the sense that there are methods you can call on every value except for null
and undefined
. But before Proxies came in ES6, every "method" call on an object was a dictionary lookup; foo.bar
is always going to find a property named "bar" on foo or its prototype chain. Contrast this with a language like Ruby, where foo.bar
sends the message :bar
to foo -- this message can be intercepted and interpreted, it doesn't have to be a dictionary lookup.
JavaScript具有一個稱為對象的數據結構,該語言中的大多數值都可以像對象一樣對待,從某種意義上說,除了null
和undefined
之外,您可以對每個值調用方法。 但是在Proxies出現在ES6中之前,對對象的每個“方法”調用都是字典查找。 foo.bar
總是會在foo或其原型鏈上找到名為“ bar”的屬性。 將此與Ruby之類的語言進行對比,其中foo.bar
將消息:bar
發送到foo -該消息可以被攔截和解釋 ,而不必是字典查找。
Redux is essentially a slower but more sophisticated object system on top of JavaScript’s existing one, where reducers and middleware act as interpreters and interceptors around the JavaScript object that actually holds the state. [back]
Redux本質上是JavaScript現有系統之上的一個較慢但更復雜的對象系統,在其中,化簡器和中間件充當了實際上持有狀態JavaScript對象周圍的解釋器和攔截器。 [ 返回 ]
翻譯自: https://www.freecodecamp.org/news/whats-so-great-about-redux-ac16f1cc0f8b/