-
Notifications
You must be signed in to change notification settings - Fork 4.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Private experimental cross-module selectors and actions #44521
Private experimental cross-module selectors and actions #44521
Conversation
27fbb8f
to
a09263f
Compare
Size Change: +717 B (0%) Total Size: 1.31 MB
ℹ️ View Unchanged
|
packages/data/src/index.js
Outdated
|
||
function __experimentalPrivateSelector( { name }, selector ) { | ||
return ( ...args ) => | ||
selector( defaultRegistry.stores[ name ].store.getState(), ...args ); |
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.
It is a major shortcoming that the selector always selects from the default registry, and not the one that's in React context and is returned by useRegistry
. I wonder if we can do anything about it?
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.
// registration
registerPrivateSelectors( blockEditorStore, {
getContentLockingParent
} );
// plain usage
const { privateOf } = unlock( dataExperiments );
privateOf( registry.select( blockEditorStore ) ).getContentLockingParent();
// usage in React
useSelect( ( select ) => ( {
parent: privateOf( select( blockEditorStore ) ).getContentLockingParent();
} ) );
I can register private selectors and actions on a store, and then the object returned by select
has the good old public methods, but can also be "unlocked" with privateOf
to access the private parts.
The same can be done with actions and dispatch
.
Ideally, I would not need a custom privateOf
function, but could use the general purpose unlock
to unlock any object. Could be implemented by having a Symbol.for( 'accessKey' )
property on an object. Very similar to how, to a for of
loop, you can pass either an object with a Symbol.iterator
field or an actual iterator.
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.
@jsnajdr I really like this idea and I'm exploring an implementation. Would you mind taking a look? I think the new lockObj/unlockObj API could very well replace the old register/unlock API – I just need some better names.
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.
I'm adding this to my todo-list after finishing the React 18 migration. Which currrently utilizes me 100% 🙂
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.
7550dd2
to
cf3ae62
Compare
c3c874f
to
3b3eaca
Compare
I just updated this PR as @jsnajdr suggested: // registration
registerPrivateSelectors( blockEditorStore, {
getContentLockingParent
} );
// plain usage
unlock( registry.select( blockEditorStore ) ).getContentLockingParent();
// usage in React
useSelect( ( select ) => ( {
parent: unlock( select( blockEditorStore ) ).getContentLockingParent();
} ) ); You can register private selectors and actions on a store, and the object returned by This now works with all the registries, not just the default one. It's easy to migrate the current code to the new one – we just need to add |
d418a57
to
172e63b
Compare
The JavaScript unit tests seems to fail due to |
…electors, registerPrivateActions, and privateOf methods
172e63b
to
e444d1d
Compare
I'm merging this PR into #46131 – the two are closely related, updating them separately takes effort, and keeping the code separate makes the dependency less clear. |
…ate-selectors-and-actions
Introduces a private selectors APIs in `@wordpress/data` via [the new `@wordpress/experimental`](#43386 (comment)) package: ```js // Package wordpress/block-data: import { unlock } from '../experiments'; import { experiments as dataExperiments } from '@wordpress/data'; const { registerPrivateActionsAndSelectors } = unlock( dataExperiments ); import { store as blockEditorStore } from './store'; import { __unstableSelectionHasUnmergeableBlock } from './store/selectors'; registerPrivateActionsAndSelectors( store, {}, { __experimentalHasContentRoleAttribute } ); // plain usage unlock( registry.select( blockEditorStore ) ).getContentLockingParent(); // usage in React useSelect( ( select ) => ( { parent: privateOf( select( blockEditorStore ) ).__unstableSelectionHasUnmergeableBlock(); } ) ); ```
import { __unstableSelectionHasUnmergeableBlock } from './store/selectors'; | ||
registerPrivateSelectors( store, { | ||
__experimentalHasContentRoleAttribute, | ||
} ); | ||
``` |
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.
These selectors don't match, which adds confusion.
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.
ah snap good spot, I'll fix this in #46131
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.
## Description This commit introduces a more convenient API for managing the private experiments as the former `register`-based API was quite cumbersome to use. The idea is to "lock" private data inside public objects: ```js const { lock, unlock } = __dangerousOptInToUnstableAPIsOnlyForCoreModules( '<CONSENT STRING>', '@wordpress/blocks' ); export const publicObject = {}; lock( __experiments, "Shh, private data!" ); publicObject // {} unlock( publicObject ) // "Shh, private data!" ``` This new `lock()`/`unlock()` API enables private functions, classes, components, selectors, actions, arguments, and properties. Any package that opted-in to private APIs can call `unlock()` on publicly available artifacts to retrieve the related private API. Kudos to @jsnajdr for [identifying an opportunity to simplify the API](#44521 (comment))! ## Examples ### Private selectors: ```js // In wordpress/block-data: import { store as blockEditorStore } from './store'; import { unlock } from '../experiments'; import { __unstableSelectionHasUnmergeableBlock } from './store/selectors'; unlock( store ).registerPrivateSelectors( { __unstableSelectionHasUnmergeableBlock } ); // In a React component: function MyComponent() { const hasRole = useSelect( ( select ) => ( unlock( select( blockEditorStore ) ).__unstableSelectionHasUnmergeableBlock() ) ); // ... } ``` ### Private functions, classes, and variables ```js // In packages/package1/index.js: import { lock } from './experiments'; export const experiments = {}; /* Attach private data to the exported object */ lock(experiments, { __experimentalCallback: function() {}, __experimentalReactComponent: function ExperimentalComponent() { return <div/>; }, __experimentalClass: class Experiment{}, __experimentalVariable: 5, }); // In packages/package2/index.js: import { experiments } from '@wordpress/package1'; import { unlock } from './experiments'; const { __experimentalCallback, __experimentalReactComponent, __experimentalClass, __experimentalVariable } = unlock( experiments ); ``` ### Private function arguments To add an experimental argument to a stable function you'll need to prepare a stable and an experimental version of that function. Then, export the stable function and `lock()` the unstable function inside it: ```js // In @wordpress/package1/index.js: import { lock } from './experiments'; // The experimental function contains all the logic function __experimentalValidateBlocks(formula, __experimentalIsStrict) { let isValid = false; // ...complex logic we don't want to duplicate... if ( __experimentalIsStrict ) { // ... } // ...complex logic we don't want to duplicate... return isValid; } // The stable public function is a thin wrapper that calls the // experimental function with the experimental features disabled export function validateBlocks(blocks) { __experimentalValidateBlocks(blocks, false); } lock( validateBlocks, __experimentalValidateBlocks ); // In @wordpress/package2/index.js: import { validateBlocks } from '@wordpress/package1'; import { unlock } from './experiments'; // The experimental function may be "unlocked" given the stable function: const __experimentalValidateBlocks = unlock(validateBlocks); __experimentalValidateBlocks(blocks, true); ``` ### Private React Component properties To add an experimental argument to a stable component you'll need to prepare a stable and an experimental version of that component. Then, export the stable function and `lock()` the unstable function inside it: ```js // In @wordpress/package1/index.js: import { lock } from './experiments'; // The experimental component contains all the logic const ExperimentalMyButton = ( { title, __experimentalShowIcon = true } ) => { // ...complex logic we don't want to duplicate... return ( <button> { __experimentalShowIcon && <Icon src={some icon} /> } { title } </button> ); } // The stable public component is a thin wrapper that calls the // experimental component with the experimental features disabled export const MyButton = ( { title } ) => <ExperimentalMyExistingButton title={ title } __experimentalShowIcon={ false } /> lock(MyButton, ExperimentalMyButton); // In @wordpress/package2/index.js: import { MyButton } from '@wordpress/package1'; import { unlock } from './experiments'; // The experimental component may be "unlocked" given the stable component: const ExperimentalMyButton = unlock(MyButton); export function MyComponent() { return ( <ExperimentalMyButton data={data} __experimentalShowIcon={ true } /> ) } ``` Co-authored-by: Ramon <[email protected]> Co-authored-by: Robert Anderson <[email protected]>
What?
This PR introduces a private selectors APIs in
@wordpress/data
via the new@wordpress/experimental
package:This PR demonstrates the usage by making the
__unstableSelectionHasUnmergeableBlock
selector private. I intend to roll back this change before merging, and then follow-up with a migration PR to ensure new experimental selectors won't be added to the public API.Why?
Exporting
__unstable
and__experimental
selectors makes them available to extenders and locks them in as a part of the public API. Let's not add new such exports if it can be avoided.Testing
Confirm the new API makes sense and that all the tests pass.
cc @jorgefilipecosta @ellatrix @talldan @draganescu @peterwilsoncc @jsnajdr