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,705