+ All Categories
Transcript

© 2017 RoCk SOLid KnOwledge 1

RoCkKnOwledge

SOLiD

http://www.rocksolidknowledge.com

Managing Data in an SPA using the Flux Pattern

© 2017 RoCk SOLid KnOwledge 2

Motivation

2-way data binding can lead to complex and unpredictable data flows

Leads to sluggish UI

Leads to code that is difficult to reason about and debug

© 2017 RoCk SOLid KnOwledge 3

Solution

React

UI framework

Better performance

Encourages uni-directional data flows

Flux

Uni-directional data flow pattern

© 2017 RoCk SOLid KnOwledge 4

React

Component-based

JSX

Virtual DOM

Promotes uni-directional data flow

© 2017 RoCk SOLid KnOwledge 5

Component definition & JSX

import * as React from 'react'

export class MyComponent extends React.Component<any, any> {render() { return (<div>

<span id="foo" className="bar">Hello from React</span></div>

)};};

React.createElement(

"div",

null,

React.createElement(

"span",

{ id: "foo", className: "bar" },

"Hello from React"));

© 2017 RoCk SOLid KnOwledge 6

Composition

import * as React from 'react'import {Header} from './components/Header'import {Footer} from './components/Footer'

export class App extends React.Component<any, any> {render() { return<div><Header>A header</Header><span id="foo" className="bar">Hello from React</span><Footer someFooterAttribute="someValue">A footer</Footer>

</div>};

};

© 2017 RoCk SOLid KnOwledge 7

Demo

React ‘hello world’

© 2017 RoCk SOLid KnOwledge 8

Client-side routing

React router

Declarative, nested routes

Default route

Not found route etc

Route parameters

Links

Programmatic redirect

© 2017 RoCk SOLid KnOwledge 9

Client-side route configuration

import * as React from 'react'import {BrowserRouter as Router, Route} from 'react-router-dom'import {List} from './ListController'import {Details} from './DetailsController'

export class App extends React.Component<any, any> {render() { return (<Router><div><Route path="/" exact component={ListController}/><Route path="/:id" component={DetailsController}/>

</div></Router>

)};};

© 2017 RoCk SOLid KnOwledge 10

Demo

Client-side routing with React router

© 2017 RoCk SOLid KnOwledge 11

Props v State

Properties (props)

Like HTML attributes

Passed down by parent

Immutable

State

Mutable

© 2017 RoCk SOLid KnOwledge 12

Promoting ‘one way data flow’

Often state becomes props

‘Smart’ components at top of component hierarchy acquire all the state that is needed

State converted to properties passed down through the hierarchy to ‘dumb’ components

© 2017 RoCk SOLid KnOwledge 13

Smart component

import * as React from 'react'import {Meeting} from '../api/Meeting'import {getMeetings} from '../api/MeetingFunctions'import {List} from './List'

interface IListControllerState {meetings: Meeting[];

};export class ListController extends React.Component<any,

IListControllerState> {constructor() {super();this.state = {meetings: getMeetings()};

}render() { return (<List meetings={this.state.meetings} />

)};};

© 2017 RoCk SOLid KnOwledge 14

Dumb component

import * as React from 'react'import {Meeting} from '../api/Meeting'import {Link} from 'react-router-dom'

interface IListProps {meetings: Meeting[];

};

const CreateRow = (meeting: Meeting)=>{return ( <li key={meeting.Id}><Link to={"/"+meeting.Id}>{meeting.Title}</Link>

</li>)

};export let List = (props:IListProps)=><ul>{props.meetings.map(CreateRow)}</ul>

© 2017 RoCk SOLid KnOwledge 15

Demo

Smart and dumb components

© 2017 RoCk SOLid KnOwledge 16

Smart/dumb component interaction

Dumb components often need their properties to change

But their properties are immutable

Smart components pass event handlers to dumb components

Dumb components fire events upwards

Smart components handle, change their state and re-render dumb components with new props

Event handlers passed as far down hierarchy as needed

© 2017 RoCk SOLid KnOwledge 17

Smart component

interface IDetailsControllerState {meeting: Meeting;

};export class DetailsController extendsReact.Component<RouteComponentProps<any>, IDetailsControllerState> {constructor(props: RouteComponentProps<any>) {super(props);this.state = {meeting: getMeeting(+this.props.match.params.id)};

};onChange(event: React.ChangeEvent<any>){var title = event.target.value;this.setState({meeting: {Id: this.state.meeting.Id, Title: title}});

}; render() { return (<Details meeting={this.state.meeting}

onChange={this.onChange.bind(this)} />)};

};

© 2017 RoCk SOLid KnOwledge 18

Dumb component

interface IDetailsProps {meeting: Meeting;onChange: (event: React.ChangeEvent<any>)=>void;

};

export let Details = (props: IDetailsProps) =><div>…<input id="title" value={props.meeting.Title}

onChange={props.onChange}/>…

</div>

© 2017 RoCk SOLid KnOwledge 19

Demo

Smart/dumb component interaction

© 2017 RoCk SOLid KnOwledge 20

Flux

Uni-directional data flow pattern

Many implementations

E.g. Flux, Redux, …

User interactions create actions

Actions dispatched to stores

Stores update and notify UI

© 2017 RoCk SOLid KnOwledge 21

Uni-directional data flow

Action creator

UIDispatcher

Store

5. query

4. notify

3. dispatch

1

2

© 2017 RoCk SOLid KnOwledge 22

Store

import {EventEmitter} from "events";

class MeetingFluxStoreImpl extends EventEmitter {putMeetings(meetings: Meeting[]) {// save meetingsthis.emit("meeting-flux-store-change";)

};get meetings(): Meeting[] {// return saved meetings

};

// putMeeting & deleteMeeting methods

addChangeListener(callback:()=>void) {this.on(this.changeEvent, callback);

};removeChangeListener(callback:()=>void) {this.removeListener(this.changeEvent, callback);

};};export let MeetingFluxStore = new MeetingFluxStoreImpl();

© 2017 RoCk SOLid KnOwledge 23

Actions

export enum ActionType {PutMeetings,PutMeeting,DeleteMeeting

};

export interface Action<T> {type: ActionType;payload?: T;

};

© 2017 RoCk SOLid KnOwledge 24

Action creators

import {Meeting} from "../api/Meeting";import {ActionType} from "./ActionTypes";

export function putMeetings(meetings: Meeting[]) {return {type: ActionType.PutMeetings, payload: meetings};

};

export function putMeeting(meeting: Meeting) {return {type: ActionType.PutMeeting, payload: meeting};

};

export function deleteMeeting(id: number) {return {type: ActionType.DeleteMeeting, payload: id};

};

© 2017 RoCk SOLid KnOwledge 25

Flux dispatcher

import {Dispatcher} from 'flux';

export let AppDispatcher = new Dispatcher();

© 2017 RoCk SOLid KnOwledge 26

Acquiring state & dispatching actions

import {AppDispatcher} from "../dispatcher/Dispatcher";import {putMeetings} from "../api/MeetingFunctions";

export function loadMeetings() {const meetings = getMeetings();AppDispatcher.dispatch(putMeetings(meetings));

};

© 2017 RoCk SOLid KnOwledge 27

Handling dispatched actions

import {AppDispatcher} from "../dispatcher/Dispatcher";import {ActionType, Action} from "../actions/ActionTypes";import {MeetingFluxStore} from "../stores/MeetingFluxStore";

export function initDispatcherListener() {AppDispatcher.register((action: Action<any>)=>{switch(action.type) {case ActionType.PutMeetings:MeetingFluxStore.putMeetings(action.payload);break;

case ActionType.PutMeeting:MeetingFluxStore.putMeeting(action.payload);break;

case ActionType.DeleteMeeting:MeetingFluxStore.deleteMeeting(action.payload);break;

default:break;

}});

};

© 2017 RoCk SOLid KnOwledge 28

Wiring up the dispatcher listener

import { loadMeetings } from "./actions/MeetingFunctions";import { initDispatcherListener } from"../stores/DispatcherListener";

initDispatcherListener();loadMeetings();render( <App/>, document.getElementById("app"));

© 2017 RoCk SOLid KnOwledge 29

Smart component

import {MeetingFluxStore} from "../stores/MeetingFluxStore";

export class ListController extendsReact.Component<any, IListControllerState> {constructor() {super();this.state = {meetings: MeetingFluxStore.meetings};MeetingFluxStore.addChangeListener(this.handleUpdate);

};componentWillUnmount() {MeetingFluxStore.removeChangeListener(this. handleUpdate);

};private onUpdate() {this.setState({meetings: MeetingFluxStore.meetings});

};private handleUpdate = this.onUpdate.bind(this));render() { return <MeetingList meetings={this.state.meetings} /> };

};

© 2017 RoCk SOLid KnOwledge 30

Demo

Flux

© 2017 RoCk SOLid KnOwledge 31

Summary

2-way data binding can lead to complex and unpredictable data flows

React & Flux enforce a uni-directional data flow pattern

Redux & Reselect also worth look


Top Related