ReactJS Day Verona 2017
Mattia Manzati - @mattiamanzati
Michel Weststrate - @mweststrate
setState
Redux
MobX
jquery.data + refs
redux === immutable
mobx === mutable
Mutable and Immutable are for sure the current opposite paradigms, each one with its pros and its cons.
As developers we always try to find the ultimate solution, and the quest for that solution sometimes make us forget about what we learnt and approach new ways of doing things
So maybe instead of approaching a completely new paradigm, we should look behind, learn from our journey, and see if we can get the best of what we learnt
Mutable data structures are perfect for continuosly-changing values; forms for example are an example of highly-continuosly-changing data structures, that would require lot of new immutable objects to be allocated in memory as we interact with our forms.
On the other hand, Immutable structures are awesome, they allow easy testing and when they only contains serializable data they make easy to de/rehydrate our application state.
const initialState = {name: "", done: false}function todoReducer(state = initialState, action){ if(!action) return state switch(action.type){ case TOGGLE_TODO: return ({...state, done: !state.done}) default: return state }}
In Redux the reducer could be considered the core concept of data modeling, and that's predictable. Since we are dealing with immutable data, we could not change an instance, so the focus of our code will always be the data transformation rather than it's shape. Due to that, our data shape is kinda lost in all of that code.
const TOGGLE_TODO = 'todo/TOGGLE_TODO'export function toggleTodo(){ return { type: TOGGLE_TODO }}
And while we are at it, we could note that reducer and actions are'nt colocated, and that IMHO could potentially make our code less readable.
class Todo { @observable name = "" @observable done = false @action toggle(){ this.done != this.done }}
Most of the people using MobX up to now used ES6 classes up to now, and that's nice. It lets you write clean code that express both data shape and actions in the same place making it highly readable.
MobX
Data shape is explicit
Redux
Data shape is implicit
const serverData = { name: "Visit the opera!", done: true}
const newState = todoReducer(serverData, toggleTodo())// newState.done === false
Since Redux uses immutable objects, they won't contain any application business logic like actions, computeds, etc... So it is really easy to rehydrate data onto our store, you just need to pass it onto your reducer.
class Todo { // ... @computed get snapshot(){ return { name: this.name, done: this.done } } @action applySnapshot(snapshot){ this.name = snapshot.name this.done = snapshot.done }}
Unfortunately, MobX uses classes, so it won't accept our server data, we need to define a property and a method to manage de/serialization.
MobX
(Usually) classes. Unlimited data shape. De/serialization is hard
Redux
Plain objects. Tree structure only. Serialization is done in actions
Declare data shape
De/serialization becomes free
redux || mobx
true
mobx-state-tree
+ →
Static demo app, just json
Letś type this
Collection types ================ model map array |
Primitive types =============== string literal boolean number Date reference frozen undefined null |
Higher Order Types ================== enumeration compose union optional maybe refinement identifier late |
Connect demo to app store
Introduce actions
Connect observer
Add computed fields
Use filteredTodos
observer
FTWadd local storage
Like a git commit
Describes the entire state at a specific moment in time
Free: in perf costs, but also in effor: no need to produce a new state tree
import { onSnapshot, applySnapshot } from "mobx-state-tree"const store = SomeModel.create()const states = []onSnapshot(store, snapshot => { states.push(snapshot)})function undo() { applySnapshot(store, states.pop())}
add time traveller
const TimeTraveller = types.model({ history: types.optional(types.array(types.frozen), []), undoIdx: -1, targetPath: types.string })
.actions(self => { let targetStore, snapshotDisposer
return { afterCreate() { targetStore = resolvePath(self, self.targetPath) snapshotDisposer = onSnapshot(targetStore, todos => { self.history.push(snapshot) self.undoIdx++ }) },
undo() { applySnapshot(targetStore, self.history[--self.undoIdx]) },
beforeDestroy() { snapshotDisposer() } } })
Frozen type
local state
lifecycle hooks
Add timeout stuff
Add undo / redo manager
Like a git patch
Describes the modifications from one commit to the next
// ... like Time TravellerafterCreate() { const undoRedoMiddleware = createActionTrackingMiddleware({ onStart: call => recordPatches(call.tree), onResume: (call, recorder) => recorder.resume(), onSuspend: (call, recorder) => recorder.stop(), onSuccess: (call, recorder) => { self.history.push({ patches: recorder.patches, inversePatches: recorder.inversePatches }) }, onFail: (call, recorder) => recorder.undo() }) addMiddleware(undoRedoMiddleware, targetStore)}
function undo() { applyPatch(targetStore, self.history[--self.undoIdx].inversePatches)}
state
→ snapshotsubscribe
→ onSnapshot
dispatch
→ applyAction
connect
→ observer
import {asReduxStore} from 'mobx-state-tree/middleware/redux'const reduxStore = asReduxStore(store)
connect devtools
![]() |
![]() |
npm install mobx-state-tree
@mweststrate - @mattiamanzati
Keyboard shortcuts
↑, ←, Pg Up, k | Go to previous slide |
↓, →, Pg Dn, Space, j | Go to next slide |
Home | Go to first slide |
End | Go to last slide |
b / m / f | Toggle blackout / mirrored / fullscreen mode |
c | Clone slideshow |
p | Toggle presenter mode |
t | Restart the presentation timer |
?, h | Toggle this help |
Esc | Back to slideshow |