-
-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
breaking: conditional ActionReturn type if Parameter is void #7442
breaking: conditional ActionReturn type if Parameter is void #7442
Conversation
Co-authored-by: Ivan Hofer <ivan.hofer@outlook.com>
Not marking |
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.
This gives a new perspective on how we can approach things, but I feel we're overcomplicating ourselves here
Co-authored-by: Ignatius Bagus <ignatius.mbs@gmail.com>
src/runtime/action/index.ts
Outdated
@@ -17,8 +17,8 @@ | |||
* | |||
* Docs: https://svelte.dev/docs#template-syntax-element-directives-use-action | |||
*/ | |||
export interface ActionReturn<Parameter = any> { | |||
update?: (parameter: Parameter) => void; | |||
interface ActionReturn<Parameter = void> { |
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.
The default value is not needed because everywhere a vaule gets passed
interface ActionReturn<Parameter = void> { | |
interface ActionReturn<Parameter> { |
* You can return an object with methods `update` and `destroy` from the function. | ||
* If your action doesn't accept a parameter, type if as `Action<HTMLElement, void>`. | ||
* | ||
* You can return an object with methods `update` (if the action accepts a parameter) and `destroy` from the function. | ||
* See interface `ActionReturn` for more details. |
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.
You don't export
this interface anymore. Not sure if this line could be confusing to people reading it and then trying to import that type from svelte/action
src/runtime/action/index.ts
Outdated
} | ||
export type Action<Element = HTMLElement, Parameter = any> = | ||
void extends Parameter | ||
? <Node extends Element>(node: Node) => void | ActionReturn<Parameter> |
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.
is <Node extends Element>
really needed?
I would just type node
as Element
src/runtime/action/index.ts
Outdated
export interface ActionReturn<Parameter = any> { | ||
update?: (parameter: Parameter) => void; | ||
interface ActionReturn<Parameter = void> { | ||
update?: void extends Parameter ? undefined : (parameter: Parameter) => void; |
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 would use never
instead of undefined
src/runtime/action/index.ts
Outdated
@@ -17,8 +17,8 @@ | |||
* | |||
* Docs: https://svelte.dev/docs#template-syntax-element-directives-use-action | |||
*/ | |||
export interface ActionReturn<Parameter = any> { | |||
update?: (parameter: Parameter) => void; | |||
interface ActionReturn<Parameter = void> { |
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.
interface ActionReturn<Parameter = void> { | |
export interface ActionReturn<Parameter = any> |
not sure why this change slipped in (might have been an oversight by me), I think it's better to revert it to the previous version.
Revisiting this I finally found a way to type this so that it
/**
* Actions can return an object containing the two properties defined in this interface. Both are optional.
* - update: An action can have a parameter. This method will be called whenever that parameter changes,
* immediately after Svelte has applied updates to the markup.
* - destroy: Method that is called after the element is unmounted
*
* Additionally, you can specify which additional attributes and events the action enables on the applied element.
* This applies to TypeScript typings only and has no effect at runtime.
*
* Example usage:
* ```ts
* interface Attributes {
* newprop?: string;
* 'on:event': (e: CustomEvent<boolean>) => void;
* }
*
* export function myAction(node: HTMLElement, parameter: Parameter): ActionReturn<Parameter, Attributes> {
* // ...
* return {
* update: (updatedParameter) => {...},
* destroy: () => {...}
* };
* }
* ```
*
* Docs: https://svelte.dev/docs#template-syntax-element-directives-use-action
*/
export interface ActionReturn<Parameter = any, Attributes extends Record<string, any> = Record<never, any>> {
update?: [Parameter] extends [never] ? never : (parameter: Parameter) => void;
destroy?: () => void;
/**
* ### DO NOT USE THIS
* This exists solely for type-checking and has no effect at runtime.
* Set this through the `Attributes` generic instead.
*/
$$_attributes?: Attributes;
}
/**
* Actions are functions that are called when an element is created.
* You can use this interface to type such actions.
* The following example defines an action that only works on `<div>` elements
* and optionally accepts a parameter which it has a default value for:
* ```ts
* export const myAction: Action<HTMLDivElement, { someProperty: boolean }> = (node, param = { someProperty: true }) => {
* // ...
* }
* ```
* You can return an object with methods `update` and `destroy` from the function and type which additional attributes and events it has.
* See interface `ActionReturn` for more details.
*
* Docs: https://svelte.dev/docs#template-syntax-element-directives-use-action
*/
export interface Action<Element = HTMLElement, Parameter = never, Attributes extends Record<string, any> = Record<never, any>> {
<Node extends Element>(...args: [Parameter] extends [never ]? [node: Node] : undefined extends Parameter? [node: Node, parameter?: Parameter] : [node: Node, parameter: Parameter]): void | ActionReturn<Parameter, Attributes>;
}
type A = (undefined | boolean) extends undefined ? boolean : string;
type B = undefined extends (undefined | boolean) ? boolean : string;
type C = undefined extends (boolean) ? boolean : string;
const required: Action<HTMLElement, boolean> = (node, param) => {}
const required1: Action<HTMLElement, boolean> = (node, param) => {
return {
update: (p) => p === true,
destroy: () => {}
}
}
const required2: Action<HTMLElement, boolean> = (node) => {}
const required3: Action<HTMLElement, boolean> = (node, param) => {
return {
// @ts-expect-error comparison always resolves to false
update: (p) => p === 'd',
destroy: () => {}
}
}
required(null as any, true);
// @ts-expect-error (only in strict mode) boolean missing
required(null as any);
// @ts-expect-error no boolean
required(null as any, 'string');
const optional: Action<HTMLElement, boolean | undefined> = (node, param?) => {}
const optional1: Action<HTMLElement, boolean | undefined> = (node, param?) => {
return {
update: (p) => p === true,
destroy: () => {}
}
}
const optional2: Action<HTMLElement, boolean | undefined> = (node) => {}
const optional3: Action<HTMLElement, boolean | undefined> = (node, param) => {}
const optional4: Action<HTMLElement, boolean | undefined> = (node, param?) => {
return {
// @ts-expect-error comparison always resolves to false
update: (p) => p === 'd',
destroy: () => {}
}
}
optional(null as any, true);
optional(null as any);
// @ts-expect-error no boolean
optional(null as any, 'string');
const no: Action<HTMLElement, never> = (node) => {}
const no1: Action<HTMLElement, never> = (node) => {
return {
destroy: () => {}
}
}
// @ts-expect-error param given
const no2: Action<HTMLElement, never> = (node, param?) => {}
// @ts-expect-error param given
const no3: Action<HTMLElement, never> = (node, param) => {}
// @ts-expect-error update method given
const no4: Action<HTMLElement, never> = (node) => {
return {
update: () => {},
destroy: () => {}
}
}
// @ts-expect-error second param
no(null as any, true);
no(null as any);
// @ts-expect-error second param
no(null as any, 'string'); Given that this might result in some TS errors appearing for people who previously typed this differently, this should go into 4.x. We then should also find a proper place for the type tests I have in the code snippet. |
@dummdidumm is attempting to deploy a commit to the Svelte Team on Vercel. A member of the Team first needs to authorize it. |
@dummdidumm not quite sure if which typescript version you run for your change, it seemed to have some syntax issues with linters and tsc, i've just fixed them |
Can you revert those? For Svelte version 4 we'll also bump the TS version so this will all be valid then. |
e284d68
to
5320874
Compare
ok i've reverted 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.
Brilliant, and clean!
…ion-void-parameter-type
…b.com/tanhauhau/svelte into tanlh/feat/action-void-parameter-type
--------- Co-authored-by: Ivan Hofer <ivan.hofer@outlook.com> Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> Co-authored-by: Ignatius Bagus <ignatius.mbs@gmail.com> Co-authored-by: Ben McCann <322311+benmccann@users.noreply.github.com> Co-authored-by: Simon Holthausen <simon.holthausen@vercel.com>
This PR strictens the types for
Action
andActionReturn
.Breaking changes / how to migrate
The
Parameter
generic ofAction
andActionReturn
is nownever
by default (it wasany
by default previously). This means that you'll get type errors if you're relying on the previous default type and were using a parameter:You'll also get an error when returning an
update
method from the action in that case because it's expected to not be set, since there's no parameter.To migrate, type the
Parameter
generic explicitly:The same is true for
ActionReturn
:PR description
Based on #7347
Before submitting the PR, please make sure you do the following
[feat]
,[fix]
,[chore]
, or[docs]
.Tests
npm test
and lint the project withnpm run lint