Skip to content
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

adding types for @emotion/native #1634

Merged
merged 11 commits into from Dec 16, 2019
Merged

Conversation

patsissons
Copy link
Contributor

What:

adds some types for @emotion/native

Why:

How:

Checklist:

  • Documentation
  • Tests
  • Code complete
  • Changeset

@changeset-bot
Copy link

changeset-bot bot commented Nov 14, 2019

🦋 Changeset is good to go

Latest commit: 2f08db6

We got this.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@codesandbox-ci
Copy link

codesandbox-ci bot commented Nov 14, 2019

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

Latest deployment of this branch, based on commit 2f08db6:

Sandbox Source
Emotion Configuration

@Andarist
Copy link
Member

Thank you for working on this! From what I can see those are based on emotion@10 typings - would you be willing to convert them to what we have currently on the next branch for @emotion/styled?

@patsissons
Copy link
Contributor Author

@Andarist the types are based on the next branch? am i missing something?

@Andarist
Copy link
Member

For example there are no equivalents for ThemelessReactNativeStyledComponent & ThemedReactNativeStyledComponent in https://github.com/emotion-js/emotion/blob/ad77ed24b4bfe62d6c80f0498cd7e76863e2f28e/packages/styled/types/base.d.ts

@patsissons
Copy link
Contributor Author

it's possible the naming is just confusing. Themeless just means that the Theme type is empty and the field is then removed from the downstream types.

@Andarist
Copy link
Member

I think this still interacts a little bit different with Theme. On next branch there are no conditional tests against Theme generic - it just gets passed along into appropriate types as argument.

Looking also for example at this:

<Tag extends keyof JSX.IntrinsicElements>(
tag: Tag,
options?: StyledOptions<JSX.IntrinsicElements[Tag]>
): CreateStyledComponent<
{ theme?: Theme },
JSX.IntrinsicElements[Tag],
{ theme: Theme }
>

and in Theme shows up twice - one is optional (passed as ComponentProps) and one is required (passed as StyleProps). I guess this might be the difference linked to my first statement in this comment - you don't allow at the moment a theme prop which overrides context theme.

@patsissons
Copy link
Contributor Author

The conditional types were meant as an improvement to these types. i can completely remove the theme field from type system visibility (for style props) if it provides no value. I did have a mistake in my earlier types that is now corrected to allow overriding theme on styled components regardless of whether a contextual theme is present.

@Andarist
Copy link
Member

I would prefer to keep those types structured in the very same way as @emotion/styled types are structured - because quite frankly we don't personally use @emotion/native. The main focus of emotion is web and changes are made primarily for related packages. Having those in sync would at least make it a lot easier to backport changes from @emotion/styled to this package.

I'm not yet entirely sure, but it's also very likely that Theme will get reworked quite a bit (but not focus on this as part of this PR - unless you want to wait till we merge changes to @emotion/styled). We plan to leverage interface merging so people can just augment built-in type with their typed Theme. This should simplify things quite a bit - the work is being done as part fo this PR #1609 if you want to take a look.

@patsissons
Copy link
Contributor Author

ok, i reduced the types to just be analogies to the default styled types. We can't really share any types because of the react native stylesheet type (not without altering the styled types at least).

@Andarist
Copy link
Member

We can't really share any types because of the react native stylesheet type (not without altering the styled types at least).

That's fine - sharing probably would make them more complex than they need to be.

Thanks for doing this! I'll probably merge this in over the weekend or shortly after that.

@patsissons
Copy link
Contributor Author

Shall I move this out of draft?

@Andarist
Copy link
Member

@patsissons yeah, you can do that. I'm in the middle of aligning those types just a little bit more to what we have in @emotion/styled now. (I'm in example including support for shouldForwardProp)

@patsissons
Copy link
Contributor Author

sure thing, let me know if you want me to make any other changes.

@patsissons patsissons marked this pull request as ready for review November 20, 2019 03:23
@Andarist
Copy link
Member

I've pushed out my changes - the work is not 100% done yet though. I would highly appreciate your review on this one.

From what I understand @emotion/native accepts a different set of CSS properties than "web" version and thus it doesn't quite make sense to reuse types from @emotion/serialize because they assume web. I've created copies of those which I have needed to make this work here.

Unfortunately, types which I have used to create ObjectInterpolation conflict with each other, so the created type is invalid:

// @ts-ignore
export interface ObjectInterpolation
extends RN.ViewStyle,
RN.TextStyle,
RN.ImageStyle {}

I'm not sure what's the best way to work around this here. Any ideas?

Other than that tests need some work - I haven't really touched them, so they are using a type that doesn't exist anymore. Would you be willing to work on making those tests work?

type ReactNative = typeof RN

export type ReactNativeStyle = ReturnType<ReactNative['StyleSheet']['flatten']>
export type ObjectInterpolation = RN.ViewStyle | RN.TextStyle | RN.ImageStyle
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this type union should be fine instead of the polymorphic version that was being used previously.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like it's hard for us to create strict typings for those if we want to have types similar to those for the web version. From what I understand this will indeed work somewhat OK, but will allow incorrect object styles to be set on a particular component (in example it will allow text styles to be used on a view component). To keep things simple, I would say it's OK. WDYT?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll take another pass to see if I can improve it, but I think that may not be avoidable

shouldForwardProp?(propName: PropertyKey): boolean
}

export interface StyledWithComponent<ComponentProps extends {}> {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

open to a better name for this

component: Component,
options?: StyledOptions<ComponentPropsWithoutRef<Component>>
): CreateStyledComponent<
ComponentPropsWithoutRef<Component> & { theme?: Theme },
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i wasn't sure why the custom component variant is using an intersection for component props rather than specific props like the react native components are below.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @JakeGinnivan, that was based on the types for @emotion/styled

@Andarist
Copy link
Member

I see you have restructured typings a little bit. I'm not saying that those changes are bad, most of them are probably good - but also many of them could be considered rather cosmetic. I've intentionally written them like I've written them, so they could mirror what we have in for a web version.

Truth to be told @emotion/native has been created by a community member, we've merged it in and all - as it works. We don't have time to focus on it though in the same way as we focus on the web version. That's why I think it's crucial for us to have those types as close as possible to each other so we can sync them in the future. If we diverge them now it will just be way harder for us to maintain this as we are not using @emotion/native in any projects on our own.

If you believe that some changes you have made are particularly good we could evaluate if they could be backported to @emotion/styled as well - that could be done in followup PRs though.

@patsissons
Copy link
Contributor Author

patsissons commented Nov 24, 2019

Most of the cosmetic/name changes were to help me trace how the types were being expanded so I could ensure all types were being forwarded through properly. Removal of the PropsOf helper and changing Tag to Component is because native has no concept of jsx tags, so it's specific to react native. If there were some other changes you would like to discuss I'm happy to explain any decisions and revert if that's desired. I should also mention that I made a number of changes to resolve linter errors, but ended up using a more appropriate tsconfig instead, so some of those changes could probably be reverted without any issue now. I'll try and comment on some of the changes now.

@patsissons patsissons force-pushed the next-native-types branch 2 times, most recently from 1781c39 to 755ca6c Compare November 24, 2019 18:58
Copy link
Contributor Author

@patsissons patsissons left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hopefully these notes clear up some of the changes i made earlier. I'm going to rebase shortly to 🔥 the merge commit and cleanup the commit history.

packages/native/types/base.d.ts Outdated Show resolved Hide resolved

export interface FunctionInterpolation<MergedProps> {
(mergedProps: MergedProps): Interpolation<MergedProps>
}

export type Interpolation<MergedProps = undefined> =
export type Interpolation<MergedProps = unknown> =
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unknown is a more descriptive default for these MergedProps than undefined

packages/native/types/base.d.ts Show resolved Hide resolved
packages/native/types/base.d.ts Show resolved Hide resolved
packages/native/types/base.d.ts Show resolved Hide resolved
Comment on lines 90 to 114
export interface StyledWithComponent<ComponentProps extends {}> {
withComponent<
Component extends ComponentType<ComponentPropsWithoutRef<Component>>
>(
component: Component
): StyledComponent<ComponentProps & ComponentPropsWithoutRef<Component>>
withComponent<ComponentName extends ReactNativeComponentNames>(
component: ReactNativeComponents[ComponentName]
): StyledComponent<ComponentProps, ReactNativeComponentProps<ComponentName>>
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these withComponent signatures were pulled out into their own interface so that we can use a type union below for StyledComponent.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wouldnt we be able to do the same without pulling out into its own interface?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you're right, we don't need this to be its own interface any more (i think it was useful before when i needed to use a union).

): StyledComponent<ComponentProps, ReactNativeElements[Tag]>
}
> = ComponentType<ComponentProps & SpecificComponentProps> &
StyledWithComponent<ComponentProps>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't make much sense to type StyledComponent as an extension to a functional component. Technically it should be typed as a ForwardRefExoticComponent (and more generally, NamedExoticComponent). This might be a more appropriate choice (i'll give it a try in a moment).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NamedExoticComponent works appropriately here so i've gone with that choice.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this makes sense, cc @JakeGinnivan - shouldn't we do the same for web types?

packages/native/types/base.d.ts Show resolved Hide resolved
packages/native/types/tslint.json Outdated Show resolved Hide resolved
Copy link
Contributor Author

@patsissons patsissons left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Andarist the interpolation types are now working with inference, I think that's about as far as I can go with the typings. LMK how you want to proceed from here (feel free to commit on top and adjust names or squash the branch at this point)

packages/native/types/base.d.ts Show resolved Hide resolved
Comment on lines 62 to 66
export type ReactNativeStyleType<Props> = Props extends {
style?: RN.StyleProp<infer StyleType>
}
? StyleType
: ReactNativeStyle
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this construct lets us extract the specific shape of a react-native style prop, and support custom components too.

): StyledComponent<ComponentProps, ReactNativeElements[Tag]>
}
> = ComponentType<ComponentProps & SpecificComponentProps> &
StyledWithComponent<ComponentProps>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NamedExoticComponent works appropriately here so i've gone with that choice.

ComponentProps extends {},
SpecificComponentProps extends {} = {},
StyleProps extends {} = {},
StyleType = ReactNativeStyle
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found that the StyleType, while properly inferred, was allowing an ImageStyle to be used as a TextStyle but not the other way around. Feels like a bug in the TypeScript inference logic because i couldn't see a relation between the two, but i couldn't get a better answer as to why (i left some comments in the test file).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

type True = RN.ImageStyle extends RN.TextStyle ? true : false
type False = RN.TextStyle extends RN.ImageStyle ? true : false

Seems like ImageStyle is a subtype of TextStyle

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

haha interesting, i'm not going to dig further since it's already difficult to understand why that may be.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly on web input is a subtype of div. It's not like those types form such hierarchy, but rather that div is so basic, that it has no special props associated with it and everything that can be used on it can be used on input as well (+ input has some specific props for it). And because TS type system is structural rather than nominal this forms implicit supertype-subtype relationship.

ComponentPropsWithoutRef<Component> & { theme?: Theme },
{},
{ theme: Theme },
ReactNativeStyleType<ComponentPropsWithoutRef<Component>>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as long as the custom component defines a style?: StyleProp<T>, the T will get inferred and applied to the template interpolation types.

packages/native/types/index.d.ts Outdated Show resolved Hide resolved
{ theme?: Theme },
ReactNativeComponentProps<ComponentName>,
{ theme: Theme },
ReactNativeStyleType<ReactNativeComponentProps<ComponentName>>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all styled react-native components should now be strongly typed against their specific style prop shape.

@patsissons
Copy link
Contributor Author

patsissons commented Nov 24, 2019

I ran into some composition issues testing out these types with a more complicated setup. The react-native styles sometimes override the definitions of certain style fields which can cause grief if you want to share some common styles and compose them downstream. I updated the css function signature to support optionally explicit typing to solve this, as well as implicit typing if the function is used with simple style objects (though these won't be type inferred at definition time). This appears to be working properly in my project, so hopefully it's a worthwhile fix (i added some new test code to cover these types of scenarios).

@Andarist
Copy link
Member

Andarist commented Dec 3, 2019

@patsissons sorry that I haven't yet reviewed this, I hope to do it this week


export function css<StyleType extends ReactNativeStyle = ReactNativeStyle>(
template: TemplateStringsArray,
...args: Array<Interpolation>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if this should use Interpolation. For instance, it doesn't make sense for css to accept functions - for the web we've split this into two similar, but separate, Interpolation and CSSInterpolation. I feel like the same should be done here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes sense to me, i can add those suggestions in pretty quickly

@Andarist
Copy link
Member

@patsissons sorry that I haven't reviewed this sooner but had plenty on my plate. Let me know if you are still interested in working on this one (not much left to do).

@patsissons
Copy link
Contributor Author

@patsissons sorry that I haven't reviewed this sooner but had plenty on my plate. Let me know if you are still interested in working on this one (not much left to do).

No worries, i'm in the same boat. I've addressed your review suggestions and things looks reasonable to me right now. My mind isn't as deep into these types as it was a few weeks ago so maybe spot check the changes i made. Otherwise i think i'm satisfied with these types for an MVP release, i'll try and pull them into a project later today to give them a broader test.

StyleType extends ReactNativeStyle = ReactNativeStyle
> extends Array<CSSInterpolation<MergedProps, StyleType>> {}

export type CSSInterpolation<
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that MergedProps props are actually used anyhow inside CSSInterpolation, it only seems to be passed around, but not utilized. This is good though - with css you don't get access to any props at all. This means though that we can just drop this typeparam here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yup, absolutely. a bit of blind refactoring lands you with useless type params 😄

@patsissons
Copy link
Contributor Author

@Andarist are we good to ship? 🚢

@Andarist Andarist merged commit 456be9a into emotion-js:next Dec 16, 2019
louisgv pushed a commit to louisgv/emotion that referenced this pull request Sep 6, 2020
* adding types for react native

* fixing styled component types

* removing condition type inference for existence of a theme

* Move @emotion/native TS types closer to what we have for @emotion/styled

* some minor adjustments to the types

* updating interpolation types to support inferred style type

* addressing some deficiencies

* Use Theme interface from @emotion/core in @emotion/native

* adding review suggestions

* Add changeset
@github-actions github-actions bot mentioned this pull request Nov 10, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants