summaryrefslogtreecommitdiffstats
path: root/devtools/docs/contributor/frontend/redux.md
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /devtools/docs/contributor/frontend/redux.md
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'devtools/docs/contributor/frontend/redux.md')
-rw-r--r--devtools/docs/contributor/frontend/redux.md160
1 files changed, 160 insertions, 0 deletions
diff --git a/devtools/docs/contributor/frontend/redux.md b/devtools/docs/contributor/frontend/redux.md
new file mode 100644
index 0000000000..e090060f5d
--- /dev/null
+++ b/devtools/docs/contributor/frontend/redux.md
@@ -0,0 +1,160 @@
+
+We use [Redux](https://github.com/reactjs/redux) to manage application
+state. The [docs](http://redux.js.org/) do a good job explaining the
+concepts, so go read them.
+
+# Quick Intro
+
+Just like the [React introduction](react.md), this is a quick
+introduction to redux, focusing on how it fits into React and why we
+chose it.
+
+One of the core problems that React does not address is managing
+state. In the React intro, we talked about data flowing down and
+events flowing up. Conceptually this is nice, but you quickly run into
+awkward situations in large apps.
+
+Let's look at an example. Say you have a page with a tabbed interface.
+Here, `Tab1` is managing a list of items, so naturally it uses local
+state. `Tab2` renders different stuff.
+
+```js
+const Tab1 = React.createClass({
+ getInitialState: function() {
+ return { items: [] };
+ },
+
+ handleAddItem: function(item) {
+ this.setState({ items: [...this.state.items, item]});
+ },
+
+ render: function() {
+ /* ... Renders the items and button to add new item ... */
+ }
+});
+
+const Tab2 = React.createClass({
+ render: function() {
+ /* ... Renders other data ... */
+ }
+});
+
+// Assume `Tab1` and `Tab2` are wrapped with a factory when importing
+const Tabs = React.createClass({
+ render: function() {
+ return div(
+ { className: 'tabs' },
+ // ... Render the tab buttons ...
+ Tab1(),
+ Tab2()
+ );
+ }
+});
+```
+
+What happens when `Tab2` needs the list of items though? This scenario
+comes up all time: components that aren't directly related need access
+to the same state. A small change would be to move the `items` state
+up to the `Tabs` component, and pass it down to both `Tab1` and `Tab2`.
+
+But now `Tabs` has to implement the `handleAddItem` method to add an
+item because it's managing that state. This quickly gets ugly as the
+end result is the root component ends up with a ton of state and
+methods to manage it: a [god
+component](https://en.wikipedia.org/wiki/God_object) is born.
+
+Additionally, how do we know what data each tab needs? We end up
+passing *all* the state down because we don't know. This is not a
+modular solution: one object managing the state and every component
+receiving the entire state is like using tons of global variables.
+
+## Evolution of Flux
+
+Facebook addressed this with the
+[flux](https://facebook.github.io/flux/) architecture, which takes the
+state out of the components and into a "store". Redux is the latest
+evolution of this idea and solves a lot of problems previous flux
+libraries had (read it's documentation for more info).
+
+Because the state exists outside the component tree, any component can
+read from it. Additionally, **state is updated with
+[actions](http://redux.js.org/docs/basics/Actions.html)** that any
+component can fire. We have [guidelines](redux-guidelines) for where
+to read/write state, but it completely solves the problem described
+above. Both `Tab1` and `Tab2` would be listening for changes in the
+`item` state, and `Tab1` would fire actions to change it.
+
+With redux, **state is managed modularly with
+[reducers](http://redux.js.org/docs/basics/Reducers.html)** but tied
+together into a single object. This means a single JS object
+represents most* of your state. It may sound crazy at first, but think
+of it as an object with references to many pieces of state; that's all
+it is.
+
+This makes it very easy to test, debug, and generally think about. You
+can log your entire state to the console and inspect it. You can even
+dump in old states and "replay" to see how the UI changed over time.
+
+I said "most*" because it's perfectly fine to use both component local
+state and redux. Be aware that any debugging tools will not see local
+state at all though. It should only be used for transient state; we'll
+talk more about that in the guidelines.
+
+## Immutability
+
+Another important concept is immutability. In large apps, mutating
+state makes it very hard to track what changed when. It's very easy to
+run into situations where something changes out from under you, and
+the UI is rendered with invalid data.
+
+Redux enforces the state to be updated immutably. That means you
+always return new state. It doesn't mean you do a deep copy of the
+state each time: when you need to change some part of the tree you
+only need to create new objects to replace the ones your changing (and
+walk up to the root to create a new root). Unchanged subtrees will
+reference the same objects.
+
+This removes a whole class of errors, almost like Rust removing a
+whole class of memory errors by enforcing ownership.
+
+## Order of Execution
+
+One of best things about React is that **rendering is synchronous**. That
+means when you render a component, given some data, it will fully
+render in the same tick. If you want the UI to change over time, you
+have to change the *data* and rerender, instead of arbitrary UI
+mutations.
+
+The reason this is desired is because if you build the UI around
+promises or event emitters, updating the UI becomes very brittle
+because anything can happen at any time. The state might be updated in
+the middle of rendering it, maybe because you resolved a few promises
+which made your rendering code run a few ticks later.
+
+Redux embraces the synchronous execution semantics as well. What this
+means is that everything happens in a very controlled way. When
+updating state through an action, all reducers are run and a new state
+is synchronously generated. At that point, the new state is handed off
+to React and synchronously rendered.
+
+Updating and rendering happen in two phases, so the UI will *always*
+represent consistent state. The state can never be in the middle of
+updating when rendering.
+
+What about asynchronous work? That's where
+[middleware](http://redux.js.org/docs/advanced/Middleware.html) come
+in. At this point you should probably go study our code, but
+middleware allows you to dispatch special actions that indicate
+asynchronous work. The middleware will catch these actions and do
+something async, dispatching "raw" actions along the way (it's common
+to emit a START, DONE, and ERROR action).
+
+**Ultimately there are 3 "phases" or level of abstraction**: the async
+layer talks to the network and may dispatch actions, actions are
+synchronously pumped through reducers to generate state, and state is
+rendered with react.
+
+## Next
+
+Read the [Redux Guidelines](redux-guidelines.md) next to learn how to
+write React code specifically for the devtools.