本页还没有译文
本页 51 段还没译成Português (BR),下面显示的是英文原文。Plugin Lifecycle
还没翻译译自英文(tanstack.com)本页 51 段都还没译
Every TanStack Devtools plugin follows a well-defined lifecycle: it is registered, mounted into a DOM container, rendered when activated, and cleaned up when the devtools unmount. This page walks through each stage in detail.
Plugin Interface
All plugins implement the TanStackDevtoolsPlugin interface, which is the low-level contract between a plugin and the devtools core. Framework adapters wrap this interface so you can work with familiar components, but under the hood every plugin is reduced to these fields:
interface TanStackDevtoolsPluginProps {
theme: 'dark' | 'light'
devtoolsOpen: boolean
}
interface TanStackDevtoolsPlugin {
id?: string
name:
| string
| ((el: HTMLHeadingElement, props: TanStackDevtoolsPluginProps) => void)
render: (el: HTMLDivElement, props: TanStackDevtoolsPluginProps) => void
destroy?: (pluginId: string) => void
defaultOpen?: boolean
}id (optional)
A unique identifier for the plugin. If you omit it, the core generates one from the name string (lowercased, spaces replaced with dashes, suffixed with the plugin's index). Providing an explicit id is useful when you need a stable identifier across page reloads - for example, to persist which plugins the user had open.
name (required)
Displayed as the tab title in the sidebar. This can be:
- A plain string - rendered as text inside an
<h3>element. - A function - receives the heading element and the current
{ theme, devtoolsOpen }props. You can render anything into the heading, such as an icon next to the name or a fully custom title.
// Simple string name
{ name: 'My Plugin', render: (el) => { /* ... */ } }
// Custom title via function
{
name: (el, { theme }) => {
el.innerHTML = `<span style="color: ${theme === 'dark' ? '#fff' : '#000'}">My Plugin</span>`
},
render: (el) => { /* ... */ }
}render (required)
The main rendering function receives a <div> container element and the current { theme, devtoolsOpen } props. Your job is to render your plugin UI into this container using whatever approach you prefer - raw DOM manipulation, a framework portal, or anything else.
render: (el, { theme, devtoolsOpen }) => {
el.innerHTML = `<div class="${theme}" data-open="${devtoolsOpen}">Hello from my plugin!</div>`
}The render function is called:
- When the plugin's tab is first activated (clicked or opened by default).
- When the theme or devtools open state changes, so your UI can adapt.
destroy (optional)
Called when the plugin is removed from the active set (e.g., the user deactivates the tab) or when the devtools unmount entirely. It receives the pluginId as its argument. Use this for cleanup - cancelling timers, closing WebSocket connections, removing event listeners, etc.
Most plugins don't need to implement destroy because framework adapters handle cleanup automatically.
defaultOpen (optional)
When set to true, the plugin's panel will open automatically when the devtools first load and no user preferences exist in localStorage. At most 3 plugins can be open simultaneously, so if more than 3 specify defaultOpen: true, only the first 3 are opened.
This setting does not override saved user preferences. Once a user has interacted with the devtools and their active-plugin selection is persisted, defaultOpen has no effect.
If only a single plugin is registered, it opens automatically regardless of defaultOpen.
Mount Sequence
Here is what happens when you provide plugins to the devtools:
-
Initialization -
TanStackDevtoolsCoreis instantiated with apluginsarray. Each plugin is assigned anidif one is not already provided. -
DOM containers are created - The core's Solid-based UI creates two DOM elements per plugin:
- A content container:
<div id="plugin-container-{pluginId}">where the plugin renders its UI. - A title container:
<h3 id="plugin-title-container-{pluginId}">where the plugin's name is rendered.
- A content container:
-
Title rendering - For each plugin, the core checks if
nameis a string or function. If it is a string, the text is set directly on the heading element. If it is a function, the function is called with the heading element and current plugin props. -
Plugin activation - When the user clicks a plugin's tab (or the plugin is auto-opened via
defaultOpen), the plugin is added to theactivePluginslist. The core then callsplugin.render(container, props)with the content<div>and{ theme, devtoolsOpen }. -
Rendering - The container is a regular
<div>element. Your plugin can render anything into it - DOM nodes, a framework component tree via portals, a canvas, an iframe, etc. -
Plugin prop changes - When the theme or devtools open state changes,
renderis called again with the new props. Your plugin should update accordingly.
flowchart TD
A["User opens devtools"] --> B["TanStackDevtoolsCore created<br/>with plugins array"]
B --> C["Solid UI renders sidebar<br/>tabs and containers"]
C --> D["<h3 id='plugin-title-container-{id}'><br/>created per plugin"]
C --> E["<div id='plugin-container-{id}'><br/>created per active plugin"]
D --> F["plugin.name<br/><i>string set or function called</i>"]
E --> G["plugin.render(div, props)<br/><i>called with container + plugin props</i>"]Framework Adapter Pattern
You rarely interact with the raw TanStackDevtoolsPlugin interface directly. Instead, each framework adapter converts your familiar component model into the DOM-based plugin contract.
React / Preact
The React adapter takes your JSX element and uses createPortal to render it into the plugin's container element:
// What you write:
<TanStackDevtools
plugins={[{
name: 'My Plugin',
render: <MyPluginComponent />,
}]}
/>
// What happens internally:
// The adapter's render function calls:
// createPortal(<MyPluginComponent />, containerElement)Your React component runs in its normal React tree with full access to hooks, context, state management, etc. It just renders into a different DOM location via the portal.
Solid
The Solid adapter uses Solid's <Portal> component to mount your JSX into the container:
// What you write:
<TanStackDevtools
plugins={[{
name: 'My Plugin',
render: <MyPluginComponent />,
}]}
/>
// What happens internally:
// The adapter wraps your component in:
// <Portal mount={containerElement}>{yourComponent}</Portal>Since the devtools core is itself built in Solid, this is the most native integration. Your component runs inside the same Solid reactive system as the devtools shell.
Vue
The Vue adapter uses <Teleport> to render your Vue component into the container:
<!-- What you write: -->
<TanStackDevtools
:plugins="[{
name: 'My Plugin',
component: MyPluginComponent,
props: { someProp: 'value' },
}]"
/>
<!-- What happens internally: -->
<!-- The adapter renders: -->
<Teleport :to="'#plugin-container-' + pluginId">
<component :is="plugin.component" v-bind="plugin.props" :theme="theme" />
</Teleport>Your Vue component receives the theme as a prop along with any other props you pass. It runs within the Vue app's reactivity system with full access to composition API, inject/provide, etc.
Svelte
The Svelte adapter mounts the plugin component directly into the container and forwards the shared plugin props together with any plugin-specific props:
<!-- What you write: -->
<script lang="ts">
import { TanStackDevtools } from '@tanstack/svelte-devtools'
import MyPluginComponent from './MyPluginComponent.svelte'
const plugins = [
{
name: 'My Plugin',
component: MyPluginComponent,
props: { someProp: 'value' },
},
]
</script>
<TanStackDevtools {plugins} />Internally, the adapter mounts a Svelte host component into the plugin container with { theme, devtoolsOpen, ...plugin.props }. Repeated renders update that host's component and props, preserving the mounted plugin's state when its component identity is unchanged. The host is unmounted when the plugin is destroyed or the adapter shuts down, which runs the component's Svelte cleanup lifecycle.
The Key Insight
graph LR
subgraph fw["Your Framework Runtime"]
comp["Your Plugin Component<br/><i>Full reactivity, hooks, signals</i>"]
end
subgraph core["Devtools Core (Solid.js)"]
container["Plugin Container<br/><div id='plugin-container-{id}'>"]
end
comp -- "Framework rendering API" --> containerRegardless of framework, your plugin component runs in its normal framework context with full reactivity, hooks, signals, lifecycle methods, and dependency injection. The adapter renders it into a DOM location owned by the devtools core. This means:
- React plugins can use
useState,useEffect,useContext, and any React library. - Solid plugins can use signals, stores,
createEffect, and the full Solid API. - Vue plugins can use
ref,computed,watch,inject, and any Vue composable. - Svelte plugins can use runes, context, lifecycle hooks, and any Svelte library.
The framework adapter handles all the wiring between your component and the devtools container.
Plugin State
The devtools persist the active/visible plugin selection in localStorage under the key tanstack_devtools_state. This means that when a user opens specific plugin tabs, their selection survives page reloads.
Key behaviors:
- Maximum 3 visible plugins - At most 3 plugin panels can be displayed simultaneously. Activating a 4th plugin deactivates the earliest one.
defaultOpenvs. saved state - IfdefaultOpen: trueis set on a plugin and no saved state exists in localStorage, the plugin opens on first load. Once the user changes the selection, their preference takes precedence.- Single-plugin auto-open - If only one plugin is registered, it opens automatically regardless of
defaultOpenor saved state. - Stale plugin cleanup - When the devtools load, any plugin IDs in the saved state that no longer match a registered plugin are automatically removed.
Cleanup
When TanStackDevtoolsCore.unmount() is called - either explicitly or because the framework component unmounts - the following happens:
- The Solid reactive system disposes of the entire devtools component tree.
- For each active plugin that provides a
destroyfunction,destroy(pluginId)is called. - All DOM containers created by the core are removed.
Framework adapters handle their own cleanup automatically:
- React unmounts the portal, which triggers the normal React cleanup cycle (
useEffectcleanup functions, etc.). - Vue destroys the Teleport, which runs
onUnmountedhooks in your component. - Solid disposes of the Portal's reactive scope, running any
onCleanupcallbacks. - Svelte calls
unmount()for the mounted component, running its cleanup lifecycle.
You typically do not need to implement destroy unless your plugin has manual subscriptions, timers, WebSocket connections, or other resources that aren't tied to your framework's lifecycle. If all your cleanup is handled by framework hooks (like useEffect cleanup in React or onCleanup in Solid), the adapter takes care of it for you.