This repository has been archived by the owner on Jun 26, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
MVP hooks support #1272
Merged
bvaughn
merged 11 commits into
facebook:master
from
bvaughn:hooks-integration-experimental
Jan 14, 2019
Merged
MVP hooks support #1272
Changes from 7 commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
3e60854
Support hooks
46f69d9
Ensure that hooks inspection is being done for the current fiber (sin…
b5ac442
Support useDebugValueLabel() hook
7b07dae
Rename useImperativeMethods -> useImperativeHandle
9c29ee0
Rename useDebugValueLabel -> useDebugValue
21ab2e8
Copied over changes from React PR
f1a92de
Fixed some edge case UI bugs with hooks updates
b3de585
Don't show undefined value for unlabeled custom hooks
a89784d
Fix typo (== -> ===)
f456a68
Don't show state for Fibers using hooks in Profiler
16f4fcc
Moved hooks-specific hacks inside of Agent into a new plug-in
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,6 +16,7 @@ var assign = require('object-assign'); | |
var nullthrows = require('nullthrows').default; | ||
var guid = require('../utils/guid'); | ||
var getIn = require('./getIn'); | ||
var {inspectHooksOfFiber} = require('../backend/ReactDebugHooks'); | ||
|
||
import type {RendererID, DataType, OpaqueNodeHandle, NativeType, Helpers} from '../backend/types'; | ||
|
||
|
@@ -87,6 +88,7 @@ class Agent extends EventEmitter { | |
global: Object; | ||
internalInstancesById: Map<ElementID, OpaqueNodeHandle>; | ||
idsByInternalInstances: WeakMap<OpaqueNodeHandle, ElementID>; | ||
rendererIdsByInternalInstance: WeakMap<OpaqueNodeHandle, RendererID>; | ||
renderers: Map<ElementID, RendererID>; | ||
elementData: Map<ElementID, DataType>; | ||
roots: Set<ElementID>; | ||
|
@@ -96,22 +98,34 @@ class Agent extends EventEmitter { | |
capabilities: {[key: string]: boolean}; | ||
_updateScroll: () => void; | ||
_inspectEnabled: boolean; | ||
_prevInspectedHooks: any = null; | ||
|
||
constructor(global: Object, capabilities?: Object) { | ||
super(); | ||
this.global = global; | ||
this.internalInstancesById = new Map(); | ||
this.idsByInternalInstances = new WeakMap(); | ||
this.rendererIdsByInternalInstance = new WeakMap(); | ||
this.renderers = new Map(); | ||
this.elementData = new Map(); | ||
this.roots = new Set(); | ||
this.reactInternals = {}; | ||
var lastSelected; | ||
this.on('selected', id => { | ||
var data = this.elementData.get(id); | ||
if (data && data.publicInstance && this.global.$r === lastSelected) { | ||
this.global.$r = data.publicInstance; | ||
lastSelected = data.publicInstance; | ||
var inspectedHooks = null; | ||
if (data) { | ||
if (data.publicInstance && this.global.$r === lastSelected) { | ||
this.global.$r = data.publicInstance; | ||
lastSelected = data.publicInstance; | ||
} | ||
if (data.containsHooks) { | ||
inspectedHooks = this.updateHooksTree(id); | ||
} | ||
} | ||
if (this._prevInspectedHooks !== inspectedHooks) { | ||
this._prevInspectedHooks = inspectedHooks; | ||
this.emit('inspectedHooks', inspectedHooks); | ||
} | ||
}); | ||
this._prevSelected = null; | ||
|
@@ -223,6 +237,7 @@ class Agent extends EventEmitter { | |
this.on('isRecording', isRecording => bridge.send('isRecording', isRecording)); | ||
this.on('storeSnapshot', (data) => bridge.send('storeSnapshot', data)); | ||
this.on('clearSnapshots', () => bridge.send('clearSnapshots')); | ||
this.on('inspectedHooks', data => bridge.send('inspectedHooks', data)); | ||
} | ||
|
||
scrollToNode(id: ElementID): void { | ||
|
@@ -390,6 +405,15 @@ class Agent extends EventEmitter { | |
this.renderers.set(id, renderer); | ||
this.elementData.set(id, data); | ||
|
||
// We need to map Fiber instance IDs to their renderers to support Hooks inspection. | ||
// This is because the Store does not know how to connect the two, | ||
// but the Agent needs to use the parent renderer's injected internals to get the current dispatcher. | ||
// We only need to store this for components that use hooks (to reduce memory impact). | ||
// We can only store on-mount (to reduce write operations) since hooks cannot be conditional. | ||
if (data.containsHooks) { | ||
this.rendererIdsByInternalInstance.set(component, renderer); | ||
} | ||
|
||
var send = assign({}, data); | ||
if (send.children && send.children.map) { | ||
send.children = send.children.map(c => this.getId(c)); | ||
|
@@ -414,6 +438,18 @@ class Agent extends EventEmitter { | |
delete send.type; | ||
delete send.updater; | ||
this.emit('update', send); | ||
|
||
// If the element that was just updated is also being inspected, update the hooks values. | ||
if ( | ||
this._prevInspectedHooks !== null && | ||
this._prevInspectedHooks.elementID === id | ||
) { | ||
const inspectedHooks = this.updateHooksTree(id); | ||
if (this._prevInspectedHooks !== inspectedHooks) { | ||
this._prevInspectedHooks = inspectedHooks; | ||
this.emit('inspectedHooks', inspectedHooks); | ||
} | ||
} | ||
} | ||
|
||
onUpdatedProfileTimes(component: OpaqueNodeHandle, data: DataType) { | ||
|
@@ -431,6 +467,37 @@ class Agent extends EventEmitter { | |
this.emit('updateProfileTimes', send); | ||
} | ||
|
||
updateHooksTree(id: ElementID) { | ||
const data = this.elementData.get(id); | ||
const internalInstance = this.internalInstancesById.get(id); | ||
if (internalInstance) { | ||
const rendererID = this.rendererIdsByInternalInstance.get(internalInstance); | ||
if (rendererID) { | ||
const internals = this.reactInternals[rendererID].renderer; | ||
if (internals && internals.currentDispatcherRef) { | ||
// HACK: This leaks Fiber-specific logic into the Agent which is not ideal. | ||
// $FlowFixMe | ||
const currentFiber = data.state === internalInstance.memoizedState ? internalInstance : internalInstance.alternate; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is gross for obvious reasons, but it needs to live somewhere. Happy to move it out of the Agent if anyone thinks it's too gross even for a temporary solution. |
||
|
||
const hooksTree = inspectHooksOfFiber(currentFiber, internals.currentDispatcherRef); | ||
|
||
// It's also important to store the element ID, | ||
// so the frontend can avoid potentially showing the wrong hooks data for an element, | ||
// (since hooks inspection is done as part of a separate Bridge message). | ||
// But we can't store it as "id"– because the Bridge stores a map of "inspectable" data keyed by this field. | ||
// Use an id that won't conflict with the element itself (because we don't want to override data). | ||
// This is important if components have both inspectable props and inspectable hooks. | ||
return { | ||
elementID: id, | ||
id: 'hooksTree', | ||
hooksTree, | ||
}; | ||
} | ||
} | ||
} | ||
return null; | ||
} | ||
|
||
onUnmounted(component: OpaqueNodeHandle) { | ||
var id = this.getId(component); | ||
this.elementData.delete(id); | ||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doing this (storing data, having Fiber-specific code) in the Agent sucks. I'm open to suggestions for a better place for this logic to live, but at the same time– I didn't stress about it too much because I think this version of DevTools is short lived (without either a rewrite or a refactor).