Hacking Workbench2 » History » Version 5
Peter Amstutz, 01/07/2020 09:51 PM
1 | 1 | Peter Amstutz | h1. Hacking Workbench2 |
---|---|---|---|
2 | |||
3 | 3 | Peter Amstutz | h2. References |
4 | |||
5 | "Redux":https://redux.js.org/api/api-reference |
||
6 | "Material UI":https://material-ui.com/getting-started/installation/ |
||
7 | |||
8 | h2. Startup |
||
9 | |||
10 | 1 | Peter Amstutz | The application logically begin in @workbench2/src/index.tsx@ with a call to fetchConfig() and then (when the promise is fulfilled) the constructs the major pieces of the application architecture: |
11 | |||
12 | 2 | Peter Amstutz | h2. Services |
13 | 1 | Peter Amstutz | |
14 | 2 | Peter Amstutz | "services" interact with external resources. These classes define methods for reading and writing to various types of backend (as far as I can tell, they don't hold any state themselves, but talk to external stateful services). Services defined include a service for each the API server endpoint that workbench uses, WebDAV server, workbench2 configuration like vocabulary and file viewers, and preferences kept in the browser local store. |
15 | |||
16 | h2. Store |
||
17 | |||
18 | 1 | Peter Amstutz | "store" is a "Redux":https://redux.js.org/ state container. State is divided into a groups. Each group defines a "reducer". A reducer is a function that takes (current state, action) and "reduces" it to a new state. At start, reducers are executed with an undefined state to get the initial starting state. After that, state is updated by "dispatching" an action to the state container. |
19 | |||
20 | 2 | Peter Amstutz | h2. App |
21 | |||
22 | The "App" is the root react component. It is invoked to regenerate the page, producing a "virtual DOM". ReactDOM merges the virtual DOM changes into the actual browser DOM. |
||
23 | |||
24 | 5 | Peter Amstutz | h2. Split implementation |
25 | |||
26 | Many functional program components (for example, the "process" panel for displaying a container request) consist of both actions and/or thunks (and their associated reducers or middleware) in the @store/@ tree as well as the React component that lives in the @views/@ tree. For example, @loadProcess@ in @store/workbench/workbench-actions@ is responsible for initializing the state that will be used to create and update the @ProcessPanel@ DOM tree. |
||
27 | |||
28 | 2 | Peter Amstutz | h2. Interaction loop |
29 | |||
30 | # Render the DOM (based on state from redux store) |
||
31 | # User clicks on something |
||
32 | # onClick handler dispatches an action |
||
33 | # reducer produces a new state based on the action |
||
34 | # subscribers are notified that the state has changed |
||
35 | |||
36 | 'react-redux' links the data store to components use those state items. As part of defining a react component, call @connect@ with a function that extracts only the state required to render the component from the data store. This returns a function, call this with the actual react component definition. This optimizes re-rendering by only updating components that are potentially affected by a given state change. |
||
37 | |||
38 | h2. Defining new state items |
||
39 | |||
40 | @workbench2/src/store/store.ts@ |
||
41 | |||
42 | @createRootReducer@ defines the reducer groups. Pick one which is relevant, or define a new one. A reducer group "xyz" is defined in @workbench2/src/store/xyz/xyz-reducer.ts@ |
||
43 | |||
44 | Add the state item to @interface XyzState@ and @initialState: XyzState@ for the group. |
||
45 | |||
46 | h2. Defining new actions |
||
47 | |||
48 | Actions types are declared in @xyzActions@ in @workbench2/src/store/xyz/xyz-actions.ts@. |
||
49 | |||
50 | Actions are implemented in @xyzReducer@ in @workbench2/src/store/xyz/xyz-reducer.ts@ |
||
51 | |||
52 | The reducer for the action reduces (current state, action) to (new state). The new state must have all the state items. There is a shorthand syntax for this: @{ ...state, open: false }@ which means "construct a new object with all the items from 'state', except those declared explicitly" |
||
53 | |||
54 | A common pattern seems to be to define functions that can be called or used as event handlers that implement high level behaviors by calling services and dispatching actions. |
||
55 | 3 | Peter Amstutz | |
56 | h2. Middleware and thunks |
||
57 | |||
58 | Redux "middleware":https://redux.js.org/api/applymiddleware is a chain of functions that the action is passed through before it gets to the store. |
||
59 | |||
60 | Middleware enables these three things: |
||
61 | |||
62 | * Execute code that should happen before an action modifies store |
||
63 | * Modify or suppress actions before they get to the store |
||
64 | * Execute code in response to an action after it has modified the store |
||
65 | |||
66 | If you read the redux documentation, you'll notice that it only talks about dispatching actions, not functions. Workbench2 uses a library called "redux-thunk":https://github.com/reduxjs/redux-thunk . The redux-thunk middleware intercepts dispatched functions and calls them with (dispatch, getState, services). They don't go to the store. You can use a thunk to invoke an asynchronous function that will proceed independently from the caller. |
||
67 | |||
68 | As a design principal, use a thunk when you need to do some asynchronous action before *putting* data into the store. For example, making an API call, and then updating the store with the result. |
||
69 | |||
70 | Middleware should be *acting* on data in the store. For example, synchronizing browser local storage. |
||
71 | |||
72 | 4 | Peter Amstutz | h2. DataExplorer |
73 | |||
74 | The DataExplorer uses middleware. |
||
75 | |||
76 | # An event callback dispatches to a thunk, such as "loadCollection" or "loadProcess" |
||
77 | # The thunk's call tree calls REQUEST_ITEMS |
||
78 | # The middleware responds to REQUEST_ITEMS by starting an asynchronous API request. |
||
79 | # When the API response comes back, the handler uses SET_ITEMS to put data into the table. |