Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Cross-Framework Patterns in State Management

Cross-Framework Patterns in State Management

These days, managing state in front end applications is increasingly difficult. Depending on the approach to this problem, how do you know which front end framework is right for you? In this session, we'll survey the landscape for managing state in front end applications, and compare state management patterns in two of the major front end frameworks, React and Vue.

Jonathan Kemp

October 19, 2018
Tweet

More Decks by Jonathan Kemp

Other Decks in Technology

Transcript

  1. IF YOU’RE WORKING WITH REACT OR VUE AND YOU’RE FRUSTRATED

    WITH STATE MANAGEMENT PROBLEMS, YOU’RE NOT ALONE.
  2. YOU’LL GAIN MORE INSIGHT INTO PROBLEMS WITH STATE MANAGEMENT AND

    HOW THEY CAN BE SOLVED IN DIFFERENT WAYS.
  3. A COMMON BEST PRACTICE IS TO ONLY PUT DATA INTO

    STATE THAT IS REPRESENTED IN THE VIEWS.
  4. HOW TO MANAGE STATE, WHICH IS THE DATA REPRESENTED IN

    THE VIEW LAYER OF THE APPLICATION
  5. class Clock extends React.Component { constructor(props) { super(props); this.state =

    {date: new Date()}; } render() { return ( <div> <h1>Hello, world!</h1> <h2>It is {this.state.date.toLocaleTimeString()}.</h2> </div> ); } }
  6. class TodoApp extends React.Component { constructor(props) { super(props); ... this.state

    = { items: [], text: '' }; } onChange(e) { this.setState({text: e.target.value}); }
  7. SUMMARY OF REACT STATE •Data used for the visual output

    is stored in state •Unidirectional Data Flow •Performance, Easier to Reason about, Reduce Bugs
  8. DATA FLOW •Views send actions to the dispatcher. •The dispatcher

    sends actions to every store. •Stores send data to the views.
  9. THREE PRINCIPLES OF REDUX 1. Single source of truth 2.

    State is read-only 3. Changes are made with pure functions
  10. PURE FUNCTIONS •Given the same input, will always return the

    same output. •Produces no side effects (does not alter or depend on any external state).
  11. function counter(state = 0, action) { switch (action.type) { case

    'INCREMENT': return state + 1; case 'DECREMENT': return state - 1; default: return state; } } let store = createStore(counter); store.subscribe(() => console.log(store.getState()) );
  12. export default theDefaultReducer = (state = 0, action) => state;

    export const firstNamedReducer = (state = 1, action) => state; export const secondNamedReducer = (state = 2, action) => state; const rootReducer = combineReducers({ theDefaultReducer, firstNamedReducer, secondNamedReducer }); const store = createStore(rootReducer); console.log(store.getState()); // {theDefaultReducer : 0, firstNamedReducer : 1, secondNamedReducer : 2}
  13. import produce from "immer" const baseState = [ { todo:

    "Learn typescript”, done: true }, { todo: "Try immer”, done: false } ] const nextState = produce(baseState, draftState => { draftState.push({todo: "Tweet about it"}) draftState[1].done = true })
  14. CONTEXT PROVIDES A WAY TO PASS DATA THROUGH THE COMPONENT

    TREE WITHOUT HAVING TO PASS PROPS DOWN THROUGH EVERY LEVEL
  15. class App extends React.Component { render() { return <Toolbar theme="dark"

    />; } } function Toolbar(props) { return ( <div> <ThemedButton theme={props.theme} /> </div> ); } function ThemedButton(props) { return <Button theme={props.theme} />; }
  16. const ThemeContext = React.createContext('light'); class App extends React.Component { render()

    { return ( <ThemeContext.Provider value="dark"> <Toolbar /> </ThemeContext.Provider> ); } }
  17. // A component in the middle doesn't have to //

    pass the theme down explicitly anymore. function Toolbar(props) { return ( <div> <ThemedButton /> </div> ); } function ThemedButton(props) { return ( <ThemeContext.Consumer> {theme => <Button {...props} theme={theme} />} </ThemeContext.Consumer> ); }
  18. UNSTATED IS DESIGNED TO BUILD ON TOP OF THE PATTERNS

    ALREADY SET OUT BY REACT COMPONENTS AND CONTEXT.
  19. import React from 'react'; import { render } from 'react-dom';

    import { Provider, Subscribe, Container } from 'unstated'; class CounterContainer extends Container { state = { count: 0 }; increment() { this.setState({ count: this.state.count + 1 }); } decrement() { this.setState({ count: this.state.count - 1 }); } }
  20. function Counter() { return ( <Subscribe to={[CounterContainer]}> {counter => (

    <div> <button onClick={() => counter.decrement()}>-</button> <span>{counter.state.count}</span> <button onClick={() => counter.increment()}>+</button> </div> )} </Subscribe> ); } render( <Provider> <Counter /> </Provider>, document.getElementById('root') );
  21. class ObservableTodoStore { @observable todos = []; constructor() { mobx.autorun(()

    => console.log(this.report)); } addTodo(task) { this.todos.push({ task: task, completed: false, assignee: null }); } } const observableTodoStore = new ObservableTodoStore();
  22. @observer class TodoList extends React.Component { render() { const store

    = this.props.store; return ( <ul> {store.todos.map(todo => ( <li key={todo.id}>{todo.text}</li> ))} </ul> ); } } ReactDOM.render(<TodoApp store={ observableTodoStore } />, mountNode);
  23. IT IS A BEST PRACTICE THOUGH TO KEEP YOUR STATE

    MUTATIONS MORE EXPLICIT WITH ACTIONS
  24. class ObservableTodoStore { @observable todos = []; constructor() { mobx.autorun(()

    => console.log(this.report)); } @action.bound addTodo(task) { this.todos.push({ task: task, completed: false, assignee: null }); } } const observableTodoStore = new ObservableTodoStore();
  25. MORE ON ACTIONS •Actions should always be used on functions

    that modify state. •Using actions yields performance benefits •action provides useful debugging information with devtools. •Strict mode enforces all state modifications are done by an action.
  26. var app = new Vue({ el: '#app', data: { list:

    [] }, methods: { addTodo: function () { var input = document.getElementById('update'); this.list = this.list.concat(input.value); } } });
  27. A GLOBAL EVENT BUS ALLOWS US TO EMIT AN EVENT

    IN ONE COMPONENT AND LISTEN FOR THAT EVENT IN ANOTHER
  28. var eventHub = new Vue() // NewTodoInput methods: { addTodo:

    function () { eventHub.$emit('add-todo', { text: this.newTodoText }) this.newTodoText = '' } } // Todos created: function () { eventHub.$on('add-todo', this.addTodo) eventHub.$on('delete-todo', this.deleteTodo) },
  29. CAN BE CONVENIENT FOR VERY SIMPLE CASES, BUT ARE NOT

    APPROPRIATE FOR MOST APPLICATIONS
  30. SIMPLE STATE MANAGEMENT CAN BE PERFORMED BY CREATING A STORE

    PATTERN THAT INVOLVES SHARING A DATA STORE BETWEEN COMPONENTS
  31. THE STORE CAN MANAGE THE STATE OF OUR APPLICATION AS

    WELL AS THE METHODS THAT ARE RESPONSIBLE IN CHANGING THE STATE
  32. export const store = { state: { numbers: [1, 2,

    3] }, addNumber(newNumber) { this.state.numbers.push(newNumber); } };
  33. <template> <div> <h2>{{ storeState.numbers }}</h2> </div> </template> <script> import {

    store } from "../store.js"; export default { name: “NumberDisplay", data() { return { storeState: store.state }; } }; </script>
  34. import { store } from "../store.js"; export default { name:

    "NumberSubmit", data() { return { numberInput: 0 }; }, methods: { addNumber(numberInput) { store.addNumber(Number(numberInput)); } } };
  35. THANKS TO VUE’S REACTIVITY, WHENEVER DATA IN STORE STATE GETS

    CHANGED, THE RELEVANT DOM THAT DEPENDS ON THIS VALUE AUTOMATICALLY UPDATES
  36. INTEGRATES WITH VUE •Official devtools extension to provide advanced features

    •Zero-config time-travel debugging •State snapshot export/import
  37. THE ONLY WAY TO ACTUALLY CHANGE STATE IN A VUEX

    STORE IS BY COMMITTING A MUTATION
  38. COMPARISON WITH REDUX •Unlike Redux, Vuex mutates the state •Rather

    than actions, the only way to change the state tree in Vuex is with Mutations. •Instead of breaking down state logic with specialized reducers, Vuex is able to organize its state logic with stores called modules.