a journey with react

54
A JOURNEY WITH REACT TASVEER SINGH @tazsingh

Upload: fitc

Post on 16-Mar-2018

719 views

Category:

Internet


0 download

TRANSCRIPT

A J O U R N E Y W I T H R E A C T

TA S V E E R S I N G H @ t a z s i n g h

3 A P P S

– M I K E H O U R A H I N E

“You can learn trigonometry, physics, C++, and DirectX to make a video game. Or you can make a video game and learn DirectX, C++, physics, and

trigonometry.”

0

100

Number of times you’ve built a similar app

1 2 3 4 5 6 7 ...

Quality

Mistakes Made

A P P # 1 - R E A C T A S A T E M P L AT I N G E N G I N E

• Server side application framework

• Nashorn JavaScript engine for Java 8

• Vert.x 2.3 - Polyglot Event-Driven Messaging

• Innovative Router

• React to describe HTML

A P P # 2 - F L U X

M

C

C

C

C

V

V

V

V

M

M

C

C

C

C

V

V

V

V

M

A P P # 2 - F L U X

• My first iteration relied on my experience from MVC

• Business logic resides in Models/Stores

• Fat models, skinny controllers

• Models/Stores make AJAX calls

• Actions were very light weight notifications to the Stores

function signInUser(username, password) { Dispatcher.dispatch({ type: ActionTypes.USER_SIGN_IN , username: username , password: password }); }

class UserStore extends EventEmitter { currentUser = null; dispatchToken = Dispatcher.register((action) => { switch(action.type) { case ActionTypes.USER_SIGN_IN: fetchUser(action.username, action.password).then( (response) => { this.currentUser = response.user; this.emit(“change”); }); } }); }

function getState() { return { currentUser: UserStore.currentUser } } class SignInComponent extends React.Component { state = getState(); handleStateChange() { this.setState(getState()); } componentDidMount() { UserStore.on(“change”, this.handleStateChange); } componentWillUnmount() { UserStore.removeListener(“change”, this.handleStateChange); } handleAuth(event) { signInUser(prompt(“What’s your username?”), prompt(“What’s your password?”)); } render() { if(!this.state.currentUser) return <a onClick={this.handleAuth}>Click here to sign in</a>; else return <p>You’re signed in as {this.state.currentUser.name}</p>; } }

A P P # 2 - F L U X - F I R S T I T E R AT I O N

• Facebook’s Dispatcher does not allow you to dispatch from a dispatch

• Difficult to compose application flow

• How do you handle errors?

• Relies on the Global State of ActionTypes

• Views worked great!

N E X T I T E R AT I O N

T R Y C O M P O S I N G A C T I O N S

class SignInUser extends BaseAction { constructor(username, password, alreadySignedInUser) { this.username = username; this.password = password; this.alreadySignedInUser = alreadySignedInUser; if(!this.alreadySignedInUser) this.authUser(); } authUser() { fetchUser(this.username, this.password).then( (newUser) => { new SignInSuccess(newUser); } , (errors) => { new SignInFailure(errors); } ); }

class SignInUser extends BaseAction { constructor(username, password, alreadySignedInUser) { this.username = username; this.password = password; this.alreadySignedInUser = alreadySignedInUser; if(!this.alreadySignedInUser) this.authUser(); } authUser() { fetchUser(this.username, this.password).then( (newUser) => { new SignInSuccess(newUser); } , (errors) => { new SignInFailure(errors); } ); }

class SignInSuccess extends BaseAction { constructor(newUser) { this.newUser = newUser; this.dispatch(); } }

class SignInFailure extends BaseAction { constructor(errors) { this.errors = errors; this.dispatch();

new FlashMessage(“Could not sign in”); } }

class UserStore extends EventEmitter { currentUser = null; dispatchToken = Dispatcher.register((action) => { switch(action.constructor) { case SignInSuccess: this.currentUser = action.newUser; this.emit(“change”); } }); }

A P P # 2 - F L U X - 2 N D I T E R AT I O N

• Eliminates the “dispatching from a dispatch” problem

• Stores are synchronous

• Very easy to compose the flow of your application by composing actions

• Eliminated Global State of ActionTypes

• Extremely scalable solution

• Easy to test

• In a later iteration, dropped in WebPack

A P P # 3 - B R O C H U R E W E B S I T E

A P P # 3 - B R O C H U R E W E B S I T E

• SEO is paramount

• AJAX is bad for SEO

• Performant on Desktop and Mobile

• Server Side Rendered

• Incremental Loading

• Introduce server side developers to client side technologies

• ES6/ES2015 via Babel

• React + Flux + WebPack

A P P # 3 - B R O C H U R E W E B S I T E

• Server side React + Server side Flux

• WebPack manages front end assets, inlining, and chunking

• Incremental loading by chunking

• Reduce number of web requests by inlining

• Koa.js serves the application and handles routing

0

100

Number of times you’ve built a similar app

1 2 3 4 5 6 7 ...

Quality

Mistakes Made

0

100

Number of times you’ve built a similar app

1 2 3 4 5 6 7 ...

Quality

Mistakes Made

0

100

Number of times you’ve built a similar app

1 2 3 4 5 6 7 ...

Quality

Mistakes Made

G R I F F I N . J S

G R I F F I N . J S

• Includes everything I’ve learnt and more

• Facebook’s Dispatcher

• React Views

• Griffin Actions - Same as previous examples

• Redux Stores w/ Griffin Connector to Views

class UserStore extends EventEmitter { currentUser = null; dispatchToken = Dispatcher.register((payload) => { switch(payload.constructor) { case SignInSuccess: this.currentUser = payload.newUser; this.emit(“change”); } }); }

class UserStore extends GriffinStore { reducer(state = null, action) => { switch(action.constructor) { case SignInSuccess: return payload.newUser; default: return state; } }); }

function getState() { return { currentUser: UserStore.currentUser } } class SignInComponent extends React.Component { state = getState(); handleStateChange() { this.setState(getState()); } componentDidMount() { UserStore.on(“change”, this.handleStateChange); } componentWillUnmount() { UserStore.removeListener(“change”, this.handleStateChange); } handleAuth(event) { signInUser(prompt(“What’s your username?”), prompt(“What’s your password?”)); } render() { if(!this.state.currentUser) return <a onClick={this.handleAuth}>Click here to sign in</a>; else return <p>You’re signed in as {this.state.currentUser.name}</p>; } }

@connect({ currentUser: UserStore })

class SignInComponent extends React.Component {

handleAuth(event) { signInUser(prompt(“What’s your username?”), prompt(“What’s your password?”)); }

render() {

if(!this.props.currentUser)

return <a onClick={this.handleAuth}>Click here to sign in</a>;

else

return <p>You’re signed in as {this.props.currentUser.name}</p>;

}

}

R E A C T D E P E N D E N C Y I N J E C T I O N

• Using props to pass in external data is similar to Angular’s dependency injection

• Only use state to manage internal component state

• <SignInComponent currentUser={{name: “Mock User!”}} />

• <SignInComponent currentUser={null} />

R E A C T R O U T E R

R E A C T R O U T E R

• Amazing routing solution - Inspired heavily by Ember’s router

• Ember has an excellent router

• Uses JSX or JSON to describe routes and nested routes

• React Component will be loaded by a Route

• Version 1.0 has lazy loading of routes and components

• Better Server Side Rendering

React.render(( <Router> <Route path="/" component={App}> <Route path="about" component={About} /> <Route path="inbox" component={Inbox}> <Route path="messages/:id" component={Message} /> </Route> </Route> </Router> ), document.body)

. ├── AppComponent.js => / ├── AboutComponent.js => /about ├── InboxComponent.js => /inbox └── MessageComponent.js => /inbox/messages/:id

. ├── AppComponent.js => / ├── AboutComponent.js => /about ├── InboxComponent.js => /inbox └── inbox    └── MessageComponent.js => /inbox/messages/:id

. ├── index.js => / ├── about │   └── index.js => /about └── inbox    ├── index.js => /inbox    └── messages    └── @id    └── index.js => /inbox/messages/:id

. ├── index.js => / ├── about │   └── index.js => /about └── inbox    ├── DeleteMessage.js    ├── GetMessages.js    ├── MessageStore.js    ├── index.js => /inbox    └── messages    └── @id    └── index.js => /inbox/messages/:id

React.render(( <Router> <Route path="/" component={App}> <Route path="about" component={About} /> <Route path="inbox" component={Inbox}> <Route path="messages/:id" component={Message} /> </Route> </Route> </Router> ), document.body)

G R I F F I N F I L E S Y S T E M B A S E D R O U T I N G

• Easy to initialize your app

• import myApp from “griffin!.”;myApp({ /* any griffin options here */});

• Easy to mount other apps

• import anotherApp from “griffin!/path/to/app”;anotherApp({ /* griffin options */});

U N I V E R S A L A P P S W I T H W E B PA C K

• Everything goes through WebPack

• Based on James Long’s excellent “Backend Apps with WebPack” posts

• On the server, it ignores CSS, images, etc.

• WebPack enables lazy-loading of application chunks

• Incrementally load Griffin routes

• Include common modules with initial payload

G R I F F I N . J S S TAT U S

• Almost ready for release

• Planning to release in the next few months

• I’m currently rewriting the WebPack incremental file-system based routing

• Last core item remaining before release

0

100

Number of times you’ve built a similar app

1 2 3 4 5 6 7 ...

Quality

Mistakes Made

T H A N K Y O U

@ t a z s i n g h