Redux
una breve intro
Redux implementa un flusso dello stato basato sul modello flux, dove il flusso degli eventi segue un ordine unidirezionale
action "descriptor"
reducer
selector
connect/view
Descrive attraverso un oggetto i dettagli dell'azione da eseguire sullo stato
Produce un nuovo stato a partire da quello precedente e dall'action descriptor
Ritorna porzioni dello stato a partire dallo stato globale
Renderizza lo stato passandolo ai componenti come props
{type: "ACTION_NAME"}
(state, action) => newState
state => partialState
connect(mapStateToProps, mapDispatch)
Ma come collego tutti questi pezzi?
createStore(reducer, initialState)
const store = createStore(reducer, initialState)
store.getState() // leggo lo state store.dispatch(actionDescriptor) // avvio una azione
Il flusso di cambiamento dello stato è "top-down", viene scatenato ad ogni azione, e traversa l'intero albero dello stato.
E' nostro onere indirizzare l'action descriptor verso il reducer corretto evitando perdite di performance per cicli su parti dello stato non interessate
Implementiamo un semplice todo!
{type: "ADD_TODO", payload: {name: "", done: false}
const initialState = { todos: [] };
function todoReducer(state = initialState, action) {
if (!action) return state;
switch (action.type) {
case "ADD_TODO":
return { todos: state.todos.concat([action.payload]) };
default:
return state;
}
}
function getTodosLength(state) {
return state.todos.length;
}
function mapStateToProps(state) {
return {
todos: state.todos,
totalCount: getTodosLength(state)
};
}
function bindDispatchToProps(dispatch) {
return bindActionCreators({ addTodo }, dispatch);
}
const App = connect(
mapStateToProps,
bindDispatchToProps
)(({ todos, totalCount, addTodo }) =>
const initialState = { todos: [] };
function todoReducer(state = initialState, action) {
if (!action) return state;
switch (action.type) {
case "ADD_TODO":
return { todos: state.todos.concat([action.payload]) };
default:
return state;
}
}
- Produce nuovi oggetti, la funzione è pura e immutabile, ottima per il testing!
- Beh, guardiamo la lunghezza, ho detto tutto.
function mapStateToProps(state) {
return {
todos: state.todos,
totalCount: getTodosLength(state)
};
}
function bindDispatchToProps(dispatch) {
return bindActionCreators({ addTodo }, dispatch);
}
const App = connect(
mapStateToProps,
bindDispatchToProps
)(({ todos, totalCount, addTodo }) =>
La funzione mapStateToProps è molto rischiosa; ad ogni esecuzione ritorna un nuovo oggetto, risultando in un rerendering del componente.
Oltre a redux, dobbiamo introdurre librerie di memoizing o oggetti immutabili
E per i side effects?
e.g. al cambio della voce attualmente selezionata devo scatenare una richiesta http.
L'architettura Redux non offre una soluzione al problema.
Demo!
MobX
una breve intro
I concetti di base di MobX
I dati della tua app
modifiche dei dati
dati che possono essere calcolati
effetti collaterali
Uno dei software più conosciuti e longevo
La struttura dati è il modo più naturale in cui ragionare
I concetti di base di MobX in Excel
celle dei dati
modifica dell'utente
formula
rendering a schermo
...che in JavaScript sarebbero?
{ name: "work", done: false }
todo.done = true
get totalCount(){
return this.todos.length
}
???
con MobX!
observable({ name: "work", done: false })
todo.done = true
extendObservable(todo, {get totalCount(){...} })
observer/reaction!
MobX e decorator magic!
class Todo {
name = "";
done = false;
}
class TodoStore {
todos = [];
get totalCount() {
return this.todos.length;
}
addTodo(data) {
this.todos.push(data);
}
}
@observable
@observable
@observable
@computed
@action
Come funziona questa magia?
observable({ name: "work", done: false })
Per ogni oggetto/array/properità viene creato un "Atom"
Atom@name
Atom@done
observable({ todos: [] })
Atom@todos
Quando accedo ad un computed, oltre a calcolarlo MobX registra all'interno degli Atom quali computed dipendono da lui
Atom@name
Atom@done
Atom@todos
get totalCount(){ return this.todos.length }
Il valore del computed viene memoizato per questioni di performance
Quando modifico un valore, l'Atom avviserà tutti i computed in suo ascolto, il loro valore verrà ricalcolato
Atom@name
Atom@done
Atom@todos
get totalCount(){ return this.todos.length }
.push(.....)
Il valore del campo computato viene scongelato, ricalcolato, e ricongelato
La propagazione dei cambiamenti in MobX è "value-based",
a partire dal cambiamento di un nodo, verranno aggiornati automaticamente tutti coloro che ne dipendono.
E per i side effect?
Posso definire delle reaction che verranno rieseguite quando il valore varierà!
reaction( () => store.selectedItemId, itemId => fetch("http://localhost/" + item.id) )
E per i side effect?
...oppure in ogni caso se qualcosa cambia usando autorun.
autorun( () => console.log("something changed!") )
MobX vs Redux
MobX vs Redux
- Definisce una architettura rigida per la nostra applicazione
- E' unopinionated
- Il pacchetto npm consiste principalmente in ottimizzazioni e check
- "implementa" il pattern FLUX
Redux
MobX vs Redux
- E' una libreria che offre oggetti osservabili
- E' opinionated
- Può essere usato con FLUX, MVC, MVVM, MV*
MobX
MobX vs Redux
oggetti serializzati
aggiornamenti granulari
struttura ad albero
struttura a grafo
semplice reidratazione da server
co-location delle azioni
time-travelling
leggibilità delle azioni
riferimento per valore
riferimento per reference
Riassumendo
Redux
MobX
mobx-state-tree
the mutable meets the immutable
import {types, getSnapshot} from "mobx-state-tree"
const Type = types.model(properties, actions)
const instance = Type.create(snapshot) instance.someAction()
const snapshot = getSnapshot(instance)
import {types, getSnapshot} from "mobx-state-tree"
const Book = types.model({
title: types.string,
price: types.number
}, {
setPrice(newPrice) {
this.price = newPrice
}
})
const mst = Book.create({title: "MST", price: 10.50})
onSnapshot(mst, snapshot => console.log(snapshot)) mst.setPrice(5.50)
> {title: "MST", price: 5.50}
applySnapshot(mst, {title: "MST", price: 9.90}) console.log(mst.price)
> 9.90
mobx-state-tree
oggetti serializzati
aggiornamenti granulari
struttura ad albero
struttura a grafo
semplice reidratazione da server
co-location delle azioni
time-travelling
leggibilità delle azioni
riferimento per valore
riferimento per reference
Riassumendo
Redux
MobX
MST
console.log(mst)
> ObservableObject
MobX-State-Tree crea oggetti osservabili di MobX!
mobx-state-tree
oggetti serializzati
aggiornamenti granulari
struttura ad albero
struttura a grafo
semplice reidratazione da server
co-location delle azioni
time-travelling
leggibilità delle azioni
riferimento per valore
riferimento per reference
Riassumendo
Redux
MobX
MST
const Store = types.model({ books: types.array(Book) }) const store = Store.create({ books: [{ title: "MST", price: 9.90 }] })
E come posso comporlo?
mobx-state-tree
oggetti serializzati
aggiornamenti granulari
struttura ad albero
struttura a grafo
semplice reidratazione da server
co-location delle azioni
time-travelling
leggibilità delle azioni
riferimento per valore
riferimento per reference
Riassumendo
Redux
MobX
MST
import {
onSnapshot, applySnapshot
} from "mobx-state-tree"
const history = []
onSnapshot(store, snapshot => {
history.push(snapshot)
})
replay(index) {
applySnapshot(store, history[index])
}
E il time travelling?
import {
onSnapshot, applySnapshot
} from "mobx-state-tree"
const history = []
onAction(store, snapshot => {
history.push(snapshot)
})
replay(index) {
applyAction(store, history[index])
}
E il time travelling basato sulle azioni?
import {
onSnapshot, applySnapshot
} from "mobx-state-tree"
const history = []
onPatch(store, snapshot => {
history.push(snapshot)
})
replay(index) {
applyPatch(store, history[index])
}
E il time travelling basato sulle JSON-patch?
mobx-state-tree
oggetti serializzati
aggiornamenti granulari
struttura ad albero
struttura a grafo
semplice reidratazione da server
co-location delle azioni
time-travelling
leggibilità delle azioni
riferimento per valore
riferimento per reference
Riassumendo
Redux
MobX
MST
Thanks!
MobX o Redux?
By mattiamanzati
MobX o Redux?
- 1,688