Skip to content

Commit 1dbe106

Browse files
committed
update
1 parent ee48eb2 commit 1dbe106

File tree

1 file changed

+22
-6
lines changed

1 file changed

+22
-6
lines changed

text/0000-slots.md

+22-6
Original file line numberDiff line numberDiff line change
@@ -70,18 +70,34 @@ export default function App() {
7070
7171
When creating UI library, we have different approaches on the component API design. The most common way is the Configuration pattern used by most UI libraries, everything in one component and provides child props for composition, like `<TextField label helperText />`. It's easy to use, but when we need to to add some extra props to label like `data-testid`, then we have to introduce new props for that which will bloat the api very easily.
7272
73-
Another way to solve this problem is Composition pattern, like `<TextField><TextFieldLabel /><TextFieldInput /></TextField>`, it provides the best flexibility, we are free to customise every part of our component, but it causes another problem: consistency, we have to organise your sub components exactly the same order as expected, and the biggest problem is that it's very hard and cumbersome to communicate between parent and children.
73+
Another way to solve this problem is Composition pattern, like `<TextField><TextFieldLabel /><TextFieldInput /></TextField>`, it provides the best flexibility, we are free to customise every part of our component, but it causes another problem: consistency, we have to organise the sub components exactly the same order as expected, and the biggest problem is that it's very hard and cumbersome to communicate between parent and children, e.g. A11y support.
7474
75-
For a dedicated Design System, we need consistent ui regardless how we compose it. Here the Slots pattern solve those problem perfectly, we can compose our component with both flexibility and consistency, and it's extreme easy to add A11y support thanks the ability of Inversion of Control. We don't need to using React Context to communicate between parent and children, and no rerendering needed to sync the data.
75+
For a dedicated Design System, we need consistent ui regardless how we compose it. Here the Slots pattern solve those problem perfectly, we can compose our components with both flexibility and consistency, and it's extreme easy to add A11y support thanks the ability of Inversion of Control. We don't need to using React Context to communicate between parent and children, and no rerendering needed to sync the data.
7676
77-
## Accessible List, virtualisation and custom renderer
77+
## Accessible List
78+
79+
Given the following element
80+
81+
```jsx
82+
<Menu>
83+
<MenuItem onSelect={download}>Download</MenuItem>
84+
<MenuItem onSelect={save}>Save</MenuItem>
85+
<MenuItem onSelect={preview}>Preview</MenuItem>
86+
</Menu>
87+
```
88+
89+
How can we set `aria-activedescendant={activeElementId}` to `Menu` for assistive tech, how will `MenuItem` know if it's currently active? The problem is well explained by (Reach UI)[https://github.com/reach/reach-ui/tree/dev/packages/descendants#the-problem].
90+
91+
But the Reach UI's solution is not ideal and a bit cumbersome, it doesn't work well with SSR. With Slots, the menu can read the children's information before rendering to DOM tree, and can decide how to render the children, so the problem could be solve in a very elegant and efficient way.
92+
93+
## virtualisation and custom renderer
7894
7995
Quoted from [the comment](https://github.com/facebook/react/issues/24979#issuecomment-1193176328) by @devongovett
8096
8197
> A bunch of libraries have a problem where they need to know about certain types of descendants. For example, a list component with keyboard navigation needs to know what elements exist in the collection in order to implement things like typeahead, arrow keys, selection, etc. Reach UI has a good [overview](https://github.com/reach/reach-ui/tree/dev/packages/descendants) of a bunch of different approaches to this. The most commonly used of them involve rendering all of the items to the DOM, and using some kind of context-based registration system to tell the parent about themselves, and the DOM to sort them into the correct order.
8298
> This has the downside that all of the items must be in the DOM at all times. In some cases, like virtualized scrolling, or a combobox/select where users can set the item without showing the list, some or all of the items shouldn't be rendered to the DOM. In React Aria, we walk the JSX tree to do this, which makes for a more natural API than giving up JSX completely (info). But this breaks composition, because only certain known element types are allowed.
8399
84-
Even with the Reach UI's solution, it doesn't work well with SSR. With the Slots pattern, we don't render the children to real DOM but only collect information, so virtualisation is supported by nature.
100+
With the Slots pattern, we don't render the children to real DOM but only collect information, so virtualisation is supported by nature.
85101
86102
Also the Slots proposal actually enabled another way of creating custom renderer, we don't need to import the `react-reconciler` which added a lot of bundle size, instead we use the version already bundled in `react-dom` or `react-native`, we define custom tags by `createSlot` and implement custom rendering in `createHost`, an easy showcase is implementing dynamic and extendable declarative configuration.
87103
@@ -118,7 +134,7 @@ The api is very similar to the [deleted](https://github.com/facebook/react/pull/
118134
119135
`createSlot<T extends React.ElementType>(Fallback?: T)` creates a slot component and won't be rendered to real DOM but only collect node info, which will be used by `createHost`. `Fallback` is a fallback component that if the slot is used without HostSlots(outside of `createHost`), it will be fallback to a normal component, which is similar to `slot` property for Web Components, that if the slot is not used in template it will act as a normal component. If `Fallback` is not provided, it will create a pure slot component that nothing will be rendered if it's used without HostSlots. (Not sure about the `Fallback` argument as it's not able to implement with `react-call-return`.)
120136
121-
`createHost(children: React.ReactNode, callback: (slots: React.ReactElement[]) => JSX.Element | null)` mounts the children which hosting slotted components and return the collected slots elements in callback, and then we can render the result to real DOM conditionally. We can find the slots by filter the `slots`(shown in the example above).
137+
`createHost(children: React.ReactNode, callback: (slots: React.ReactElement[]) => JSX.Element | null)` mounts the children which hosting slotted components and return the collected slots elements in callback, and then we can render the result to real DOM conditionally. We can find the slots by filter the `slots`(shown in the [example](#basic-example) above).
122138
123139
For `children` of `createHost`, similar to `react-call-return`, slot component can be extended, wrapped with `Fragment`, but can't be wrapped with Host components(like `div`), see the following use cases
124140
@@ -167,7 +183,7 @@ const Comp1 = () => (
167183
);
168184
```
169185
170-
For nested slots, `createHost` works like peeling the onion, it will only collect the top level slots, nested slots will be handled at next tick if they are included in `callback`'s return, to continue with the very first example:
186+
For nested slots, `createHost` works like peeling the onion, it will only collect the top level slots, nested slots will be handled at next tick if they are included in `callback`'s return, to continue with the very first (example)[#basic-example]:
171187
172188
```jsx
173189
const NestedField = createSlot();

0 commit comments

Comments
 (0)