“Functional UI programming”React + Redux
React
› Smallest possible react application:
ReactDOM.render(
<h1>Hello, world!</h1>,
document.getElementById('root')
);
This is a React
Element, created
using JSX syntax
“Virtual DOM”
React DOM compares the element and its children to the previous one, and only applies the DOM updates necessary to bring
the DOM to the desired state.
React only updates what’s necessary
function tick() {
const element = (
<div>
<h1>Hello, world!</h1>
<h2>It is {new Date().toLocaleTimeString()}.</h2>
</div>
);
ReactDOM.render(
element,
document.getElementById('root')
);
}
setInterval(tick, 1000);
Components
› Simplest possible React component:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
Properties argument,
contains application
data
Returns a React
Element
JSX
› XML-like syntax extension to JavaScript
› Requires pre-processor
• Babel (JSX, ES6)
• Typescript (TSX)
<div>
<h1>Hello, world!</h1>
<h2>It is {new Date().toLocaleTimeString()}.</h2>
</div>
Compose with components
function SplitPane(props) {
return (<div className="SplitPane">
<div className="SplitPane-left">
{props.left}
</div>
<div className="SplitPane-right">
{props.right}
</div>
</div>);
}
function App() {
return (<SplitPane
left={ <Contacts/> }
right={<Chat/> }
/>);
}
State flows downwards (callbacks to go up)function FormattedDate(props) {
return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
}
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = { date: new Date() };
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<FormattedDate date={this.state.date} />
<button onClick={() => this.setState({date: new Date() })}>
Reset clock
</button>
</div>
);
}
}
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
Clock
Formatted
Date button
date onClick()
date
Clock
Redux
Redux
Bare-bones Redux with RxJS: demo
RxJS-based middleware for Redux
Redux-Observable
Epics
const suggestOnInputChangedEpic =
(action$: ActionsObservable<Actions>,
store: MiddlewareAPI<SearchState>,
services: IServices): Rx.Observable<Actions> => {
return action$
.actionsOfType<InputChangedAction>(InputChangedActionType)
.map(a => a.payload)
.debounceTime(SuggestDelay, services.scheduler)
.distinctUntilChanged()
.map(s => suggest(s, false));
};
IServices injected into Epic
export interface IServices {
readonly search: ISearchService;
readonly scheduler: IScheduler;
}
const services = {
search: new SomeSearchService(),
scheduler: Rx.Scheduler.async // setup the default rx scheduler for
epics as the async scheduler, to be overriden in tests
};
const epicMiddleware = createEpicMiddleware(rootEpic, { dependencies:services });
Marble-testing epics with virtual timetest("suggestEpic debounces input", () => {
// Create test scheduler
const testScheduler = createTestScheduler();
// Mock services
const servicesMock = TypeMoq.Mock.ofType<IServices>();
servicesMock.setup(m => m.scheduler).returns(() => testScheduler);
const services = servicesMock.object;
// Define marble values
const inputActions = {
a: inputChanged("rr"),
b: inputChanged("rx"),
c: inputChanged("rxjs")
};
const outputActions = {
b: suggest("rx", false),
c: suggest("rxjs", false)
};
Marble-testing epics with virtual time
// Define marble diagrams
const inputMarble = "-ab---------------------c";
const outputMarble = "----------------------b---------------------c";
// Mock input actions stream
const action$ = createTestAction$FromMarbles<Actions>(testScheduler, inputMarble,
inputActions);
// Apply epic on actions observable
const outputAction$ = suggestOnInputChangedEpic(action$, store, services);
// Assert on the resulting actions observable
testScheduler.expectObservable(outputAction$).toBe(outputMarble, outputActions);
// Run test
testScheduler.flush();
});