Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Relay Deep Dive

Relay Deep Dive

Relay is an open source data-fetching framework for React applications invented and used at Facebook. The Relay Deep Dive explores some aspects of its design and internal implementation. It will show how Relay turns declarative descriptions of data-fetching requirements into imperative operations behind the scenes, and how these aim to provide efficiency, performance, predictability, and consistency.

https://facebook.github.io/relay/

Part of a series of tech talks at Facebook HQ on April 19 2016. Greg Hurrell is a member of the Relay team at Facebook.

Video:

https://www.youtube.com/watch?v=oPSuvaYmXBY

Greg Hurrell

April 19, 2016
Tweet

More Decks by Greg Hurrell

Other Decks in Programming

Transcript

  1. What we'll cover • React <-> Relay <-> GraphQL •

    How Relay integrates with the view layer • Transform (Babel plug-in) • Data-fetching pipeline • Traversals • Query modeling • End-to-end flow from fetch-to-render
  2. GraphQL query StoryQuery { node(id: 1) { actor { name

    } createdAt updatedAt title } } { "node": { "actor": { "name": "Greg" }, "createdAt": "2015-10-26", "updatedAt": "2015-10-26", "title": "Relay Deep Dive" } }
  3. GraphQL query StoryQuery { node(id: 1) { actor { name

    } createdAt updatedAt title } } { "node": { "actor": { "name": "Greg" }, "createdAt": "2015-10-26", "updatedAt": "2015-10-26", "title": "Relay Deep Dive" } }
  4. Relay colocates your data fetching queries with your components Fragment

    Fragment Fragment Fragment Fragment Fragment Fragment Fragment
  5. Each component gets exactly (and only) the data it needs

    Data Data Data Data Data Data Data Data
  6. Why colocate? • Avoid overfetching (for performance) • Avoid underfetching

    (for stability) • Enable component-local reasoning
  7. Why colocate? • Avoid overfetching (for performance) • Avoid underfetching

    (for stability) • Enable component-local reasoning • Reusability
  8. Why colocate? • Avoid overfetching (for performance) • Avoid underfetching

    (for stability) • Enable component-local reasoning • Reusability • Fearless refactoring
  9. Why colocate? • Avoid overfetching (for performance) • Avoid underfetching

    (for stability) • Enable component-local reasoning • Reusability • Fearless refactoring • Developer velocity
  10. Query aggregation Data Data Data Data Data Data Data Data

    Fragment Fragment Fragment Fragment Fragment Fragment Fragment Fragment
  11. Fragments: pre-transform { fragments: { post: () => Relay.QL` fragment

    on Post { createdAt title ${Tags.getFragment('tagged')} } ` }, }
  12. Fragments: post-transform post: function post() { return (function (RQL_0) {

    return { children: [{ fieldName: 'id', kind: 'Field', metadata: { isGenerated: true, isRequisite: true }, type: 'ID' }, _reactRelay2.default.QL.__frag(RQL_0)], hash: 'NrTO0r2X', kind: 'Fragment', metadata: {}, name: 'PostPreview', type: 'Post' }; })(_Tags2.default.getFragment('tagged')); }
  13. The concrete AST • Objects with simple properties: POJOs •

    "Concrete Nodes" • Metadata from schema
  14. Store Data query SampleQuery { node(id: 660361306) { address {

    country } id name hometown { id name } } }
  15. Store Data query SampleQuery { node(id: 660361306) { address {

    country } id name hometown { id name } } } { "660361306": { "address": { "country": "US" }, "hometown": { "id": "115970731750761", "name": "Adelaide" }, "id": "660361306", "name": "Greg Hurrell", } }
  16. { 115970731750761: { __dataID__: '115970731750761', name: 'Adelaide', }, 660361306: {

    __dataID__: '660361306', address: { __dataID__: 'client:1', }, hometown: { __dataID__: '115970731750761', }, id: '660361306', name: 'Greg Hurrell', }, 'client:1': { __dataID__: 'client:1', country: 'US', }, } Store Data { "660361306": { "address": { "country": "US" }, "hometown": { "id": "115970731750761", "name": "Adelaide" }, "id": "660361306", "name": "Greg Hurrell", } }
  17. { 115970731750761: { __dataID__: '115970731750761', name: 'Adelaide', }, 660361306: {

    __dataID__: '660361306', address: { __dataID__: 'client:1', }, hometown: { __dataID__: '115970731750761', }, id: '660361306', name: 'Greg Hurrell', }, 'client:1': { __dataID__: 'client:1', country: 'US', }, } Store Data { "660361306": { "address": { "country": "US" }, "hometown": { "id": "115970731750761", "name": "Adelaide" }, "id": "660361306", "name": "Greg Hurrell", } }
  18. Deferred fragments Data we already have (from feed): essential to

    display permalink page. "Nice-to-have" data that we need only render "eventually".
  19. Deferring a fragment fragment on Story { actors { name

    } body title ${StoryMenu.getFragment('story')} ${Comments.getFragment('story').defer()} }
  20. Deferring a fragment fragment on Story { actors { name

    } body title ${StoryMenu.getFragment('story')} ${Comments.getFragment('story').defer()} }
  21. Nested deferred fragments export type SplitQueries = { __parent__: ?SplitQueries;

    __path__: NodePath; __refQuery__: ?RelayRefQueryDescriptor; deferred: Array<SplitQueries>; required: ?RelayQuery.Root; };
  22. Nested deferred fragments export type SplitQueries = { __parent__: ?SplitQueries;

    __path__: NodePath; __refQuery__: ?RelayRefQueryDescriptor; deferred: Array<SplitQueries>; required: ?RelayQuery.Root; };
  23. Nested deferred fragments { required: topLevelQuery, deferred: [{ required: nestedQuery,

    deferred: [{ required: innermostQuery, deferred: [], }], }], }
  24. Nested deferred fragments { required: topLevelQuery, deferred: [{ required: nestedQuery,

    deferred: [{ required: innermostQuery, deferred: [], }], }], }
  25. Nested deferred fragments { required: topLevelQuery, deferred: [{ required: nestedQuery,

    deferred: [{ required: innermostQuery, deferred: [], }], }], }
  26. Nested deferred fragments { required: topLevelQuery, deferred: [{ required: nestedQuery,

    deferred: [{ required: innermostQuery, deferred: [], }], }], }
  27. Nested deferred fragments { required: topLevelQuery, deferred: [{ required: nestedQuery,

    deferred: [{ required: innermostQuery, deferred: [], }], }], }
  28. Nested deferred fragments { required: topLevelQuery, deferred: [{ required: nestedQuery,

    deferred: [{ required: innermostQuery, deferred: [], }], }], }
  29. RelayQueryVisitor class RelayQueryVisitor { visit(node, nextState) {} visitField(node, nextState) {}

    visitFragment(node, nextState) {} visitRoot(node, nextState) {} traverse(node, nextState) {} }
  30. RelayQueryVisitor class RelayQueryVisitor { visit(node, nextState) {} visitField(node, nextState) {}

    visitFragment(node, nextState) {} visitRoot(node, nextState) {} traverse(node, nextState) {} }
  31. RelayQueryVisitor class RelayQueryVisitor { visit(node, nextState) {} visitField(node, nextState) {}

    visitFragment(node, nextState) {} visitRoot(node, nextState) {} traverse(node, nextState) {} }
  32. RelayQueryVisitor class RelayQueryVisitor { visit(node, nextState) {} visitField(node, nextState) {}

    visitFragment(node, nextState) {} visitRoot(node, nextState) {} traverse(node, nextState) {} }
  33. RelayQueryVisitor class RelayQueryVisitor { visit(node, nextState) {} visitField(node, nextState) {}

    visitFragment(node, nextState) {} visitRoot(node, nextState) {} traverse(node, nextState) {} }
  34. RelayQueryVisitor class RelayQueryVisitor { visit(node, nextState) {} visitField(node, nextState) {}

    visitFragment(node, nextState) {} visitRoot(node, nextState) {} traverse(node, nextState) {} }
  35. splitDeferredRelayQueries visitFragment(node, splitQueries) { if (node.isDeferred()) { const childSplitQueries =

    {}; const required = this.traverse(node, childSplitQueries); if (required) { const wrapped = wrapNode(required); childSplitQueries.required = wrapped; } if (required || childSplitQueries.deferred.length) { splitQueries.deferred.push(childSplitQueries); } return null; } else if (node.hasDeferredDescendant()) { return this.traverse(node, splitQueries); } else { return node; } }
  36. splitDeferredRelayQueries visitFragment(node, splitQueries) { if (node.isDeferred()) { const childSplitQueries =

    {}; const required = this.traverse(node, childSplitQueries); if (required) { const wrapped = wrapNode(required); childSplitQueries.required = wrapped; } if (required || childSplitQueries.deferred.length) { splitQueries.deferred.push(childSplitQueries); } return null; } else if (node.hasDeferredDescendant()) { return this.traverse(node, splitQueries); } else { return node; } }
  37. splitDeferredRelayQueries visitFragment(node, splitQueries) { if (node.isDeferred()) { const childSplitQueries =

    {}; const required = this.traverse(node, childSplitQueries); if (required) { const wrapped = wrapNode(required); childSplitQueries.required = wrapped; } if (required || childSplitQueries.deferred.length) { splitQueries.deferred.push(childSplitQueries); } return null; } else if (node.hasDeferredDescendant()) { return this.traverse(node, splitQueries); } else { return node; } }
  38. splitDeferredRelayQueries visitFragment(node, splitQueries) { if (node.isDeferred()) { const childSplitQueries =

    {}; const required = this.traverse(node, childSplitQueries); if (required) { const wrapped = wrapNode(required); childSplitQueries.required = wrapped; } if (required || childSplitQueries.deferred.length) { splitQueries.deferred.push(childSplitQueries); } return null; } else if (node.hasDeferredDescendant()) { return this.traverse(node, splitQueries); } else { return node; } }
  39. splitDeferredRelayQueries visitFragment(node, splitQueries) { if (node.isDeferred()) { const childSplitQueries =

    {}; const required = this.traverse(node, childSplitQueries); if (required) { const wrapped = wrapNode(required); childSplitQueries.required = wrapped; } if (required || childSplitQueries.deferred.length) { splitQueries.deferred.push(childSplitQueries); } return null; } else if (node.hasDeferredDescendant()) { return this.traverse(node, splitQueries); } else { return node; } }
  40. splitDeferredRelayQueries visitFragment(node, splitQueries) { if (node.isDeferred()) { const childSplitQueries =

    {}; const required = this.traverse(node, childSplitQueries); if (required) { const wrapped = wrapNode(required); childSplitQueries.required = wrapped; } if (required || childSplitQueries.deferred.length) { splitQueries.deferred.push(childSplitQueries); } return null; } else if (node.hasDeferredDescendant()) { return this.traverse(node, splitQueries); } else { return node; } }
  41. splitDeferredRelayQueries visitFragment(node, splitQueries) { if (node.isDeferred()) { const childSplitQueries =

    {}; const required = this.traverse(node, childSplitQueries); if (required) { const wrapped = wrapNode(required); childSplitQueries.required = wrapped; } if (required || childSplitQueries.deferred.length) { splitQueries.deferred.push(childSplitQueries); } return null; } else if (node.hasDeferredDescendant()) { return this.traverse(node, splitQueries); } else { return node; } }
  42. splitDeferredRelayQueries visitFragment(node, splitQueries) { if (node.isDeferred()) { const childSplitQueries =

    {}; const required = this.traverse(node, childSplitQueries); if (required) { const wrapped = wrapNode(required); childSplitQueries.required = wrapped; } if (required || childSplitQueries.deferred.length) { splitQueries.deferred.push(childSplitQueries); } return null; } else if (node.hasDeferredDescendant()) { return this.traverse(node, splitQueries); } else { return node; } }
  43. splitDeferredRelayQueries visitFragment(node, splitQueries) { if (node.isDeferred()) { const childSplitQueries =

    {}; const required = this.traverse(node, childSplitQueries); if (required) { const wrapped = wrapNode(required); childSplitQueries.required = wrapped; } if (required || childSplitQueries.deferred.length) { splitQueries.deferred.push(childSplitQueries); } return null; } else if (node.hasDeferredDescendant()) { return this.traverse(node, splitQueries); } else { return node; } }
  44. Query printing AST query ViewerQuery { viewer { actor {

    name } isEmployee timezone zodiac } }
  45. visitFragment(node, state) { if (node.isContainerFragment()) { const dataID = getComponentDataID(state);

    const fragmentPointer = new GraphQLFragmentPointer( dataID, node ); this._setDataValue(state, fragmentPointer); } else { this.traverse(node, state); } } Reading query data
  46. visitFragment(node, state) { if (node.isContainerFragment()) { const dataID = getComponentDataID(state);

    const fragmentPointer = new GraphQLFragmentPointer( dataID, node ); this._setDataValue(state, fragmentPointer); } else { this.traverse(node, state); } } Reading query data
  47. Reading query data visitFragment(node, state) { if (node.isContainerFragment()) { const

    dataID = getComponentDataID(state); const fragmentPointer = new GraphQLFragmentPointer( dataID, node ); this._setDataValue(state, fragmentPointer); } else { this.traverse(node, state); } }
  48. Reading query data visitFragment(node, state) { if (node.isContainerFragment()) { const

    dataID = getComponentDataID(state); const fragmentPointer = new GraphQLFragmentPointer( dataID, node ); this._setDataValue(state, fragmentPointer); } else { this.traverse(node, state); } }
  49. Reading query data visitFragment(node, state) { if (node.isContainerFragment()) { const

    dataID = getComponentDataID(state); const fragmentPointer = new GraphQLFragmentPointer( dataID, node ); this._setDataValue(state, fragmentPointer); } else { this.traverse(node, state); } }
  50. Other topics • Garbage collection • Mutations • Tracked queries

    & "Fat" queries • GraphQL Subscriptions • Routing • Server rendering • And many others...