Baobab

Human Talks @ Nantes, mardi 12 janvier 2016

Alexis Jacomy

(twitter|github|slides).com/jacomyal

Flux, en bref

Le contexte

"Applications web monopage"

Des données dynamiques

Pleins de vues

Le MVC

Modèle

Vue

Contrôleur

Action

Modèle

Modèle

Vue

Vue

d'après Facebook

Leur solution : Flux

Store

Vue

Dispatcher

Action

Action

Action

Les bénéfices

Pas un framework mais

une méthode

Pousse les devs web à s'ouvrir

à d'autres paradigmes

Plusieurs outils concurrents

disponibles

Un pas plus loin avec Baobab

développé par

Guillaume Plique

publié en MIT

pour ne plus gérer de dépendances entre les stores

Un arbre

pour stocker l'état

import Baobab from 'baobab';

const state = new Baobab({
  some: {
    nested: {
      objects: [
        1, 2, 3
      ]
    }
  }
});

state.get();
// { some: { nested: { objects: [ 1, 2, 3 ] } } }

state.get('some', 'nested');
// { objects: [ 1, 2, 3 ] }

state.set(['some', 'more', 'data'], 42);
// Renvoie la valeur : 42

state.get();
// { some: { nested: { objects: [ 1, 2, 3 ] }, more: { data: 42 } } }

Un arbre

pour stocker l'état

pour éviter les effets de bords

Un état immuable

import Baobab from 'baobab';

const state = new Baobab({
  some: {
    data: [
      1, 2, 3
    ]
  }
});

const value = state.get();
// { some: { nested: { objects: [ 1, 2, 3 ] } } }

Object.isFrozen(value);
// true

value.toto = 42;
// - Sans "use strict", renvoie la valeur 42
// - Avec "use strict", lance une erreur :
//   "TypeError: Can't add property toto, object is not extensible"

value.toto;
// undefined

Un état immuable

avec délégation

des événements

grâce aux curseurs

Un état observable

import Baobab from 'baobab';

const state = new Baobab({
  some: {
    data: [
      1, 2, 3
    ]
  }
});

// Création d'un curseur :
const data = state.select('some', 'data');

// Écoute des updates :
data.on(
  'update',
  () => console.log(data.get())
);

// Mise à jour des données :
data.push(4);
// Log : [ 1, 2, 3, 4 ]

Un état observable

Gérer les vues

avec React

via une mixin ou

un composant à hériter

des helpers pour aider à écouter

des curseurs depuis des composants

import React from 'react';
import { branch } from 'baobab-react/mixins';

export default Checkbox = React.createComponent({
  mixins: [ branch ],
  cursors: {
    flag: [ 'some', 'path' ]
  },

  handleToggle(e) {
    this.cursors.flag.set(e.target.value);
  },

  render() {
    return (
      <input type="checkbox"
             value={ this.state.flag }
             onChange={ this.handleChange } />
    );
  }
});

Checkbox.jsx

import React from 'react';
import { root } from 'baobab-react/mixins';

import Checkbox from './Checkbox.jsx';

export default App = React.createComponent({
  mixins: [ root ],

  render() {
    return (
      <div>
        <Checkbox />
      </div>
    );
  }
});

App.jsx

import React from 'react';
import ReactDOM from 'react-dom';
import Baobab from 'baobab';

import App from './App.jsx';

const state = new Baobab({
  some: {
    path: {
      flag: false
    }
  }
});

ReactDOM.render(
  <App tree={ state } />,
  document.getElementById('root'),
);

index.js

Exemple :

La TodoList

import Baobab from 'baobab';

export default new Baobab({
  _id: 0,
  tasks: [],
  message: ''
});

state.js

import state from './state.js';

export default {
  setMessage(message) {
    state.set('message', message);
  },

  addTask(message) {
    const id = '' + state.set('_id', state.get('_id') + 1);
    state.select('tasks')
         .push({ id, message });
    state.set('message', '');
  },

  removeTask(id) {
    state.select('tasks')
         .unset({ id });
  }
};

actions.js

import React from 'react';
import { branch } from 'baobab-react/mixins';
import actions from './actions.js';

export default React.createClass({
  mixins: [ branch ],
  cursors: { tasks: [ 'tasks' ] },

  handleClick(e) {
    actions.removeTask(e.target.getAttribute('data-id'));
  },

  render() {
    return (
      <ul>{
        (this.state.tasks || []).map(task => (
          <li key={ task.id }
              data-id={ task.id }
              onClick={ this.handleClick }>{
            task.message
          }</li>
        ))
      }</ul>
    );
  }
});

List.jsx

import React from 'react';
import { branch } from 'baobab-react/mixins';
import actions from './actions.js';

export default React.createClass({
  mixins: [ branch ],
  cursors: { message: [ 'message' ] },

  handleSubmit(e) {
    e.preventDefault();
    actions.addTask(e.target.message.value);
  },
  handleChange(e) {
    actions.setMessage(e.target.value);
  },

  render() {
    return (
      <form onSubmit={ this.handleSubmit }>
        <input type="text"
               name="message"
               onChange={ this.handleChange }
               value={ this.state.message } />
        <button type="submit">Submit</button>
      </form>
    );
  }
});

Form.jsx

import React from 'react';
import { root } from 'baobab-react/mixins';

import Form from './Form.jsx';
import List from './List.jsx';

export default React.createClass({
  mixins: [ root ],

  render() {
    return (
      <div>
        <Form />
        <List />
      </div>
    );
  }
});

TodoList.jsx

import React from 'react';
import ReactDOM from 'react-dom';

import state from './state.js';
import TodoList from './TodoList.jsx';

ReactDOM.render(
  <TodoList tree={ state } />,
  document.getElementById('todolist')
);

export default {
  state
};

index.js

Pour aller plus loin

Les Monkeys

Cacher dynamiquement des vues sur les données

Des petits plus

Validation de l'arbre

Historique des modifications

Hot Module Replacement

Merci beaucoup !

slides disponibles à l'adresse :

slides.com/jacomyal/baobab

Crédits :

Baobab.js - Gérer son état applicatif avec un arbre immutable et centralisé

By Alexis Jacomy

Baobab.js - Gérer son état applicatif avec un arbre immutable et centralisé

Human Talks @ Nantes, mardi 12 janvier 2016

  • 2,399