Ce diaporama a bien été signalé.
Nous utilisons votre profil LinkedIn et vos données d’activité pour vous proposer des publicités personnalisées et pertinentes. Vous pouvez changer vos préférences de publicités à tout moment.
Indeed My Jobs
A case study in ReactJS and Redux
Gaurav Mathur
Software Engineer
I help
people
get jobs.
What happens next?
Following up
Scheduling an interview
Preparing an interview
Negotiating your offer
Making a decision
Why React?
Virtual DOM
JSX
Data flow
MainDisplay
ViewList
JobTable
JobRow
MainDisplay
ViewListJobTable
JobRow
MainDisplay
State: jobs, counts
ViewListJobTable
JobRow
Props: counts
MainDisplay
State: jobs, counts
ViewListJobTable
JobRow
MainDisplay
State: jobs, counts
ViewListJobTable
JobRow
Props: jobs, updateHandler
MainDisplay
State: jobs, counts
ViewListJobTable
JobRow
Props: job, updateHandler
JobRow
MainDisplay
ViewListJobTable
this.props.handleUpdate(job, state)
this.props.handleUpdate(job, state)
JobRow
MainDisplay
ViewListJobTable
this.setState({jobs:newJobs, counts:newCounts});
JobRow
MainDisplay
ViewListJobTable
this.render(); //automatically fired
JobRow
MainDisplay
ViewListJobTable
Component
Component
Component
Component
this.props.handleUpdate(job, state)
this.props.handleUpdate(job, state)
this.props...
Flux
View
View
Action
View
Dispatcher
Action
View
Dispatcher
StoreAction
View
Dispatcher
StoreAction
Transition the job
Transition Job
Job Row
Action = {
type: JobActions.APPLY,
job: job
};
Dispatch the action
MyJobsDispatcher.handleAction({
type: JobActions.APPLY,
job: job
});
Transition Job
Dispatcher
Job Row
function handleChange(action) {
...
switch (action.type) {
case JobActions.APPLY:
job.set('state', States.APPLIED);
break;...
function handleChange(action) {
MyJobsDispatcher.waitFor(
[JobStore.dispatchToken]
);
...
JobCountsStore.emitChange();
}
U...
ListenableStore.prototype = {
emitChange() {
for (let i = 0; i < this.listeners.length; i++) {
this.listeners[i]();
}
},
....
const changeAggregator = new ChangeAggregator(
MyJobsDispatcher, storesToListenTo
);
const MainDisplay = React.createClass...
const MainDisplay = React.createClass({
...
update() {
this.setState({
jobs: JobStore.getJobs(),
counts: JobCountStore.get...
Shortcomings
Boilerplate
ListenableStore
ChangeAggregator
MyjobsDispatcher
Best Practices & Testability
// TODO: Should this be in a different place..?
window.scrollTo(0, 0);
ViewStore.emitChange()...
Borrowed from: https://twitter.com/denisizmaylov/status/672390003188703238
Jing Wang
Software Engineer
I help
people
get jobs.
Borrowed from: https://twitter.com/denisizmaylov/status/672390003188703238
Single source of truth
Application State Tree
State is read only
Dispatcher & Actions
Pure functions
Reducers
Redux Store
Single source of truth
const createStore = (reducer) => {
let state = {};
const getState = () => state;
let listeners = []...
State is read only
const createStore = (reducer) => {
let state = {};
const getState = () => state;
let listeners = [];
co...
Changes are made with pure functions
const createStore = (reducer) => {
let state = {};
const getState = () => state;
let ...
Emit changes
const createStore = (reducer) => {
let state = {};
const getState = () => state;
let listeners = [];
const su...
Single reducer
const createStore = (reducer) => {
let state = {};
const getState = () => state;
let listeners = [];
const ...
const myjobsReducer(state = new AppState(), action) => {
state = state.set('jobs', jobsReducer(state.jobs, action));
state...
const myjobsStore = createStore(myjobsReducer);
<MainDisplay
store={myjobsStore}
...
/>
Store as an explicit prop
const myjobsStore = createStore(myjobsReducer);
<MainDisplay
store={myjobsStore}
...
/>
<ViewList
store={this.props.myjobs...
const myjobsStore = createStore(myjobsReducer);
<MainDisplay
store={myjobsStore}
...
/>
<ViewList
store={this.props.myjobs...
react-redux Provider
const myjobsStore = createStore(myjobsReducer);
<Provider store={myjobsStore}>
<MainDisplay />
</Prov...
const myjobsStore = createStore(myjobsReducer);
<Provider store={myjobsStore}>
<MainDisplay />
</Provider>
react-redux Pro...
connect(mapStateToProps, mapDispatchToProps, ...) {
}
react-redux connect
mapStateToProps
function mapStateToProps(state) {
return {
jobsState: state.jobs
};
}
const mapDispatchToProps = (dispatch) => {
return {
handleAction: (action) => {
dispatch(action);
}
};
}
mapDispatchToProps
connect(mapStateToProps, mapDispatchToProps, ...) {
...
return function wrapWithConnect(WrappedComponent) {
class Connect ...
react-redux connect
Connect
constructor(props, context) {
this.store = props.store || context.store;
this.state = { this.s...
Connect
...
componentDidMount() {
this.store.subscribe(this.handleChange.bind(this));
}
handleChange() {
this.setState({ t...
Connect
...
render() {
this.renderedElement = createElement(
WrappedComponent, this.mergedProps
);
return this.renderedEle...
Connect
...
render() {
this.renderedElement = createElement(
WrappedComponent, this.mergedProps
);
return this.renderedEle...
import { connect } from 'react-redux';
...
const matchStateToProps => (state) {
return {
jobsState: state.jobs
};
}
module...
JobRow.js
import { connect } from 'react-redux';
...
module.exports = connect()(JobRow);
Connect
Connect Connect
Connect
JobRow
MainDisplay
ViewListJobTable
const createStore = (reducer) => {
...
const dispatch = (action) => {
state = reducer(state, action);
listeners.forEach(li...
const createStore = (reducer) => {
...
const dispatch = (action) => {
state = reducer(state, action);
listeners.forEach(li...
const createStore = (reducer) => {
...
const dispatch = (action) => {
state = reducer(state, action);
listeners.forEach(li...
const createStore = (reducer) => {
...
const dispatch = (action) => {
state = reducer(state, action);
listeners.forEach(li...
jobReducerJobStore
Flux stores to Redux reducers
...
Flux Redux
jobCountsReducerJobCountsStore
...
store.subscribe(() => {
if (view !== newView) {
window.scrollTo(0, 0);
}
});
Side effects in separate handlers
Less boilerplate code
createStore
ListenableStore
ChangeAggregator
MyJobsDispatcher
Flux ReduxFlux Redux
Less boilerplate code
connect
addChangeListener()
JobStore.getJobs()
Flux Redux
Less boilerplate code
combineReducerswaitFor()
Flux Redux
Easier to unit test
Well unit tested state transition logic
Less code to maintain
Code easier to follow
Results
engineering.indeed.com
www.indeed.jobs
Indeed My Jobs: A case study in ReactJS and Redux (Meetup talk March 2016)
Indeed My Jobs: A case study in ReactJS and Redux (Meetup talk March 2016)
Indeed My Jobs: A case study in ReactJS and Redux (Meetup talk March 2016)
Indeed My Jobs: A case study in ReactJS and Redux (Meetup talk March 2016)
Indeed My Jobs: A case study in ReactJS and Redux (Meetup talk March 2016)
Indeed My Jobs: A case study in ReactJS and Redux (Meetup talk March 2016)
Indeed My Jobs: A case study in ReactJS and Redux (Meetup talk March 2016)
Indeed My Jobs: A case study in ReactJS and Redux (Meetup talk March 2016)
Indeed My Jobs: A case study in ReactJS and Redux (Meetup talk March 2016)
Indeed My Jobs: A case study in ReactJS and Redux (Meetup talk March 2016)
Indeed My Jobs: A case study in ReactJS and Redux (Meetup talk March 2016)
Prochain SlideShare
Chargement dans…5
×

Indeed My Jobs: A case study in ReactJS and Redux (Meetup talk March 2016)

Indeed engineers Gaurav Mathur and Jing Wang presented about how we built Indeed's My Jobs functionality using ReactJS and Redux.

  • Soyez le premier à commenter

Indeed My Jobs: A case study in ReactJS and Redux (Meetup talk March 2016)

  1. 1. Indeed My Jobs A case study in ReactJS and Redux
  2. 2. Gaurav Mathur Software Engineer
  3. 3. I help people get jobs.
  4. 4. What happens next?
  5. 5. Following up
  6. 6. Scheduling an interview
  7. 7. Preparing an interview
  8. 8. Negotiating your offer
  9. 9. Making a decision
  10. 10. Why React?
  11. 11. Virtual DOM
  12. 12. JSX
  13. 13. Data flow
  14. 14. MainDisplay
  15. 15. ViewList
  16. 16. JobTable
  17. 17. JobRow
  18. 18. MainDisplay ViewListJobTable JobRow
  19. 19. MainDisplay State: jobs, counts ViewListJobTable JobRow
  20. 20. Props: counts MainDisplay State: jobs, counts ViewListJobTable JobRow
  21. 21. MainDisplay State: jobs, counts ViewListJobTable JobRow Props: jobs, updateHandler
  22. 22. MainDisplay State: jobs, counts ViewListJobTable JobRow Props: job, updateHandler
  23. 23. JobRow MainDisplay ViewListJobTable this.props.handleUpdate(job, state)
  24. 24. this.props.handleUpdate(job, state) JobRow MainDisplay ViewListJobTable
  25. 25. this.setState({jobs:newJobs, counts:newCounts}); JobRow MainDisplay ViewListJobTable
  26. 26. this.render(); //automatically fired JobRow MainDisplay ViewListJobTable
  27. 27. Component Component Component Component this.props.handleUpdate(job, state) this.props.handleUpdate(job, state) this.props.handleUpdate(job, state)
  28. 28. Flux
  29. 29. View
  30. 30. View Action
  31. 31. View Dispatcher Action
  32. 32. View Dispatcher StoreAction
  33. 33. View Dispatcher StoreAction
  34. 34. Transition the job Transition Job Job Row Action = { type: JobActions.APPLY, job: job };
  35. 35. Dispatch the action MyJobsDispatcher.handleAction({ type: JobActions.APPLY, job: job }); Transition Job Dispatcher Job Row
  36. 36. function handleChange(action) { ... switch (action.type) { case JobActions.APPLY: job.set('state', States.APPLIED); break; ... } JobStore.emitChange(); } JobStore.dispatchToken = MyJobsDispatcher.register(handleChange); Update Job Store Transition Job Dispatcher Job Store Job Row
  37. 37. function handleChange(action) { MyJobsDispatcher.waitFor( [JobStore.dispatchToken] ); ... JobCountsStore.emitChange(); } Update Job Counts Transition Job Dispatcher Job StoreJob Count Store Job Row
  38. 38. ListenableStore.prototype = { emitChange() { for (let i = 0; i < this.listeners.length; i++) { this.listeners[i](); } }, ... } Listen for changes
  39. 39. const changeAggregator = new ChangeAggregator( MyJobsDispatcher, storesToListenTo ); const MainDisplay = React.createClass({ componentDidMount() { changeAggregator.addChangeListener(this.update); } ... }); Collect changes
  40. 40. const MainDisplay = React.createClass({ ... update() { this.setState({ jobs: JobStore.getJobs(), counts: JobCountStore.getCounts(), ... }); } }); Transition Job Dispatcher Job StoreJob Count Store Job Row View List Job Table Update views
  41. 41. Shortcomings
  42. 42. Boilerplate ListenableStore ChangeAggregator MyjobsDispatcher
  43. 43. Best Practices & Testability // TODO: Should this be in a different place..? window.scrollTo(0, 0); ViewStore.emitChange(); ViewStore.js
  44. 44. Borrowed from: https://twitter.com/denisizmaylov/status/672390003188703238
  45. 45. Jing Wang Software Engineer
  46. 46. I help people get jobs.
  47. 47. Borrowed from: https://twitter.com/denisizmaylov/status/672390003188703238
  48. 48. Single source of truth
  49. 49. Application State Tree
  50. 50. State is read only
  51. 51. Dispatcher & Actions
  52. 52. Pure functions
  53. 53. Reducers
  54. 54. Redux Store
  55. 55. Single source of truth const createStore = (reducer) => { let state = {}; const getState = () => state; let listeners = []; const subscribe = (listener) => { listeners.push(listener); } const dispatch = (action) => { state = reducer(state, action); listeners.forEach(listener => listener()); }; return {getState, dispatch, subscribe}; }
  56. 56. State is read only const createStore = (reducer) => { let state = {}; const getState = () => state; let listeners = []; const subscribe = (listener) => { listeners.push(listener); } const dispatch = (action) => { state = reducer(state, action); listeners.forEach(listener => listener()); }; return {getState, dispatch, subscribe}; }
  57. 57. Changes are made with pure functions const createStore = (reducer) => { let state = {}; const getState = () => state; let listeners = []; const subscribe = (listener) => { listeners.push(listener); } const dispatch = (action) => { state = reducer(state, action); listeners.forEach(listener => listener()); }; return {getState, dispatch, subscribe}; }
  58. 58. Emit changes const createStore = (reducer) => { let state = {}; const getState = () => state; let listeners = []; const subscribe = (listener) => { listeners.push(listener); } const dispatch = (action) => { state = reducer(state, action); listeners.forEach(listener => listener()); }; return {getState, dispatch, subscribe}; }
  59. 59. Single reducer const createStore = (reducer) => { let state = {}; const getState = () => state; let listeners = []; const subscribe = (listener) => { listeners.push(listener); } const dispatch = (action) => { state = reducer(state, action); listeners.forEach(listener => listener()); }; return {getState, dispatch, subscribe}; }
  60. 60. const myjobsReducer(state = new AppState(), action) => { state = state.set('jobs', jobsReducer(state.jobs, action)); state = state.set('jobCounts', jobCountsReducer(state.jobCounts, action, state.jobs)); ... return state; } Combining reducers
  61. 61. const myjobsStore = createStore(myjobsReducer); <MainDisplay store={myjobsStore} ... /> Store as an explicit prop
  62. 62. const myjobsStore = createStore(myjobsReducer); <MainDisplay store={myjobsStore} ... /> <ViewList store={this.props.myjobsStore} ... /> Store as an explicit prop
  63. 63. const myjobsStore = createStore(myjobsReducer); <MainDisplay store={myjobsStore} ... /> <ViewList store={this.props.myjobsStore} ... /> <JobTable store={this.props.myjobsStore} ... /> Store as an explicit prop
  64. 64. react-redux Provider const myjobsStore = createStore(myjobsReducer); <Provider store={myjobsStore}> <MainDisplay /> </Provider> MainDisplay Provider myjobsStore
  65. 65. const myjobsStore = createStore(myjobsReducer); <Provider store={myjobsStore}> <MainDisplay /> </Provider> react-redux Provider JobRow MainDisplay ViewListJobTable Provider myjobsStore Provided store: {myjobsStore}
  66. 66. connect(mapStateToProps, mapDispatchToProps, ...) { } react-redux connect
  67. 67. mapStateToProps function mapStateToProps(state) { return { jobsState: state.jobs }; }
  68. 68. const mapDispatchToProps = (dispatch) => { return { handleAction: (action) => { dispatch(action); } }; } mapDispatchToProps
  69. 69. connect(mapStateToProps, mapDispatchToProps, ...) { ... return function wrapWithConnect(WrappedComponent) { class Connect extends Component { ... return Connect; }; } } react-redux connect
  70. 70. react-redux connect Connect constructor(props, context) { this.store = props.store || context.store; this.state = { this.store.getState() }; };
  71. 71. Connect ... componentDidMount() { this.store.subscribe(this.handleChange.bind(this)); } handleChange() { this.setState({ this.store.getState() }) } react-redux connect
  72. 72. Connect ... render() { this.renderedElement = createElement( WrappedComponent, this.mergedProps ); return this.renderedElement; } react-redux connect
  73. 73. Connect ... render() { this.renderedElement = createElement( WrappedComponent, this.mergedProps ); return this.renderedElement; } mergedProps: { ...parentProps, ...mapStateToProps, ...mapDispatchToProps } react-redux connect
  74. 74. import { connect } from 'react-redux'; ... const matchStateToProps => (state) { return { jobsState: state.jobs }; } module.exports = connect(matchStateToProps)(JobTable); JobTable.js
  75. 75. JobRow.js import { connect } from 'react-redux'; ... module.exports = connect()(JobRow);
  76. 76. Connect Connect Connect Connect JobRow MainDisplay ViewListJobTable
  77. 77. const createStore = (reducer) => { ... const dispatch = (action) => { state = reducer(state, action); listeners.forEach(listener => listener()); }; } myjobsStore {type:"APPLY",jobKey:"7b14...",...} Job Row
  78. 78. const createStore = (reducer) => { ... const dispatch = (action) => { state = reducer(state, action); listeners.forEach(listener => listener()); }; } Job Row myjobsStore myjobsReducer state
  79. 79. const createStore = (reducer) => { ... const dispatch = (action) => { state = reducer(state, action); listeners.forEach(listener => listener()); }; } Job Row myjobsStore myjobsReducer state newState
  80. 80. const createStore = (reducer) => { ... const dispatch = (action) => { state = reducer(state, action); listeners.forEach(listener => listener()); }; } JobTable Job Row myjobsStore myjobsReducer state newState
  81. 81. jobReducerJobStore Flux stores to Redux reducers ... Flux Redux jobCountsReducerJobCountsStore
  82. 82. ... store.subscribe(() => { if (view !== newView) { window.scrollTo(0, 0); } }); Side effects in separate handlers
  83. 83. Less boilerplate code createStore ListenableStore ChangeAggregator MyJobsDispatcher Flux ReduxFlux Redux
  84. 84. Less boilerplate code connect addChangeListener() JobStore.getJobs() Flux Redux
  85. 85. Less boilerplate code combineReducerswaitFor() Flux Redux
  86. 86. Easier to unit test
  87. 87. Well unit tested state transition logic Less code to maintain Code easier to follow Results
  88. 88. engineering.indeed.com www.indeed.jobs

×