KnOwledge RoCk SOLiD - SDD Conferencesddconf.com/brands/sdd/library/react-flux.pdf · © 2017 RoCk...
Transcript of KnOwledge RoCk SOLiD - SDD Conferencesddconf.com/brands/sdd/library/react-flux.pdf · © 2017 RoCk...
© 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 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 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 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 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} /> };
};