necolas / react-native-web

Cross-platform React UI packages
https://necolas.github.io/react-native-web
MIT License
21.62k stars 1.79k forks source link

Exporting types #1684

Closed necolas closed 3 years ago

necolas commented 4 years ago

I'm looking for input from people who use typed JavaScript with this library.

  1. If you use Flow or TypeScript, how do you type react-native-web exports?
  2. If you work across platforms, how do you deal with platform-specific differences in the APIs or components like View?
  3. Does this library need to export Flow and TypeScript types?
  4. Is there a way to generate TypeScript interfaces from Flow types so you don't need to maintain both?
EvanBacon commented 4 years ago

Here is a useful dialog about TypeScript in RNW: https://github.com/necolas/react-native-web/issues/832

There is no DefinitelyTyped package for react-native-web. I'm not entirely sure how one would work (efficiently). I've seen people propose copying all react-native types and then extending them, and somehow aliasing types/react-native to types/react-native-web. This of course wouldn't work with other out-of-tree solutions so I would discourage the approach.

Personally I would opt for universal packages like "react animated" or "react text" (probably not with these names since they're all taken). These universal packages would implement iOS, Android, and web in the same place. Said approach would also fix some versioning issues and help reduce the custom bundler configuration required for universal React apps.

I will note that RN is now moving towards a monorepo structure (creating packages like react-native/normalize-color color and react-native/polyfills) which could make modifications like this more feasible.

At Expo, we've refactored all of our universal React packages from flow to TypeScript (as opposed to adding external type definitions files .d.ts) because we find this is easier to work with from a DX perspective.

I personally think that having better types will help bring more visibility to important react-native-web features (e.g. href on Text). Due to the high demand in the Expo/React community for TypeScript support on web, I may look at creating some abstract packages which reexport react-native(-web) modules with proper types.

paularmstrong commented 4 years ago

In Build Tracker (typescript, web-only), I pulled the react-native types from definitely-typed, then removed all of the iOS/Android specific stuff and added web-specific bits (source). Ideally I would take the few minutes to contribute this back, but I don't feel comfortable enough with my knowledge on Typescript to really be authoritative that any of it was done correctly.

At Twitter, we have our own flow definitions for react-native-web, but had built this up before this package was exporting most of its types. Ideally, we would love to use the source here, but have found that our type definitions tend to be more strict (which we like).

Is there a way to generate TypeScript interfaces from Flow types so you don't need to maintain both?

There is an inverse for this using flow-typed, though it's not 100% working. I've found it work for maybe 75% of the times I've used it. The times it didn't, some light hand-editing fixed it.

necolas commented 4 years ago

@EvanBacon I don't know that much of what you suggested is actionable. I'm a bit concerned about the implication that you're going to repackage everything under different packages

@paularmstrong I wasn't aware this package was exporting types at all 😅 Would be happy to hear about what your flow types look like and what stricter types could be upstreamed

paularmstrong commented 4 years ago

Ah, they're not exporting types, correct. I believe I had done something with the module.name_mapper to re-map to the src files. It worked out okay, but not everything was fully typed at the time. And the main index.js not having // @flow at the top was problematic as well.

I'm not as connected with that area of our code anymore, so I'm not certain what we have that's more strict. Maybe @comp615 has some thoughts on that.

The one thing that I've noticed that's really nice about the TypeScript types from react-native that I'm using in Build Tracker is that the StyleSheet props are fully typed and throws errors if you use a style that isn't allowed (like color on a View doesn't work, but will on Text). We definitely don't have that same level of strict typing in Flow.

comp615 commented 4 years ago

I filed a ticket during the last update (pre 0.13) to look into using the RNW types by default (with Paul's mapper hack) instead of our typedef module file. As @paularmstrong mentioned, they are not currently exported in RNW, which would be a great (and I think easy?) starting point to alleviate some of the boilerplate.

In particular, it would really help with major updates since it makes it much more trivial to track down all the things that changed and have certainty around them since they'd rely on the real source definition and not our manual list. I found it very useful last upgrade to read the release notes and update our flow file accordingly, and then go fix each usage in our code where there was a flow error.

necolas commented 4 years ago

What's the best way to export the types? Babel is currently stripping them when it compiles the files.

paularmstrong commented 4 years ago

flow-copy-source or DIY copy every file over to your dist directory and add .flow as the final extension. Probably also need to ensure // @flow is at the top of every file that supports types

Albert-Gao commented 4 years ago

Environment:

I do not use any new API from RN v0.63, so so far no problem.

For my project, for any cross-platform components,

two cases:

  1. the native and web share the same signature.
    • I create the components.tsx and components.web.tsx, and always use components.tsx as the source when importing.
  2. the native and web share a different signature.
    • I create a single entry like component.tsx and deal with the platform difference from there, like the above and platform check.

And all the type info, I just use from @types/react-native

No problems and works great.

So, I always share the same signature across platforms. Because I do not want to propagate the platform differences to the caller, so the vast majority of the codebase can be platform agnostic.

In short,

I think if react-native-web can maintain an exact same type info match to @types/react-native, that will be awesome, but if there are any differences, just need to doc it, and I am fine with that, that just means, for any differences, we might need to create this one more abstraction (for example different file extension and implementation for a different platform) for every difference that we are using in the user-land code, but that is totally understandable.

But if the API surface can be 100% percent compatible (for non-supported, won't throw an error), then it should be fine to just use the @types/react-native, otherwise, seems unnecessary and maintaining the types are a big task

thanks for the awesome package, so beautiful.

necolas commented 4 years ago

I came here because I was shocked to find out that there's no Typescript types

Well, RN is Flow typed and doesn't provide TS types, so I don't know what you're pretending to be so shocked about.

I would advice leveraging on @types/react-native instead

Those types aren't accurate for Windows or Web.

es-lynn commented 4 years ago

Well, RN is Flow typed and doesn't provide TS types, so I don't know what you're pretending to be so shocked about.

Most RN libraries provide types.

Those types aren't accurate for Windows or Web.

Can you give examples of which parts aren't accurate?

Offhand I can only think of ScrollView: { bounces: boolean }, which itself isn't even accurate for mobile because it's not supported on Android.

JavanPoirier commented 4 years ago

I'm not entirely sure how one would work (efficiently). I've seen people propose copying all react-native types and then extending them, and somehow aliasing types/react-native to types/react-native-web. This of course wouldn't work with other out-of-tree solutions so I would discourage the approach.

@EvanBacon, At You.i TV we have a out of tree RN solution to extend to 11+ platforms and I did just that for our type definitions. I extend from the official RN types and Omit/Pick the types as needed. You mention, "This of course wouldn't work with other out-of-tree solutions so I would discourage the approach." I fail to see how this is the case. Can you please explain?

As omitting/picking the types is a job of its own, for now I am happy to have all RN types for RN Web vs none. So I just pointed the RN types to it with: yarn add -D @types/react-native-web@npm:@types/react-native

You can also just install @types/react-native and point the type package to react-native-web through your tsconfig.js:

{
  "compilerOptions": {
    ...
    "baseUrl": "./", // Required with the use of paths. May differ depending on project structure.
    "paths": {
      "@types/react-native-web": ["./node_modules/@types/react-native"]
    },
    ...
  }
}
JavanPoirier commented 4 years ago

@necolas, I could look into writing the initial TypeScript definition file for this library. I see that the Flow types are include in the repo. I am not experienced with Flow, however would you like to the types to be included here (like Flow) or in Definitely Typed?

nandorojo commented 4 years ago

Could TypeScript declaration merging solve this?

That's what I'm using in my current project.

Example

// react-native-web/overrides.ts

declare module 'react-native' {
  interface PressableStateCallbackType {
    hovered: boolean
  }
}

That would extend the existing PressableStateCallbackType from @types/react-native (with one caveat, more on that below.)

Screen Shot 2020-10-19 at 12 32 15 PM

Caveat

There is one problem with this approach at the moment. @types/react-native exports certain types as type rather than interface. This prevents declaration merging.

For instance, this is the current type for Pressable's callback:

// @types/react-native/index.d.ts line 473
export type PressableStateCallbackType = Readonly<{
    pressed: boolean;
}>;

Changing it to this makes it extensible:

export interface PressableStateCallbackType {
    pressed: boolean;
}

Thus, declaration merging could (in a few cases) require a PR to @types/react-native turning type into interface.

hosseinmd commented 4 years ago

I think ReactNative could have web type too. ReactNative supported specific type for android or iOS that could support specific type for web too.

RichardLindhout commented 4 years ago

I don't think React Native wants that as the react-native-macos, react-native-web, react-native-windows are not 'official'. Maybe it would be better to create a different type system which has special properties for all these platforms available, but that would require a lot of work.

Maybe we will have to wait for React Native to add iOS Pointer support as this would probably create the hovered in React Native itself for iOS: https://react-native.canny.io/feature-requests/p/ios-pointer-support

hosseinmd commented 4 years ago

I do not agree with you because React Native added web to official documentation but not added react-native-macos or react-native-windows

minheq commented 3 years ago

If you use Flow or TypeScript, how do you type react-native-web exports? I use @types/react-native. When there are missing types for props that are supported on react-native-web I wrap the component with right types for props, and pass though to react-native component. But I like @nandorojo approach as well.

If you work across platforms, how do you deal with platform-specific differences in the APIs or components like View? Same as above, but I will add comments to the prop type.

Does this library need to export Flow and TypeScript types? I don't think so. I would rely on @types/react-native only, and patch the differences with "wrapper" components

Is there a way to generate TypeScript interfaces from Flow types so you don't need to maintain both? I don't know.

nandorojo commented 3 years ago

In my app, I use patch-package to change @types/react-native's read-only types from type to interface. This change addresses the caveat I mentioned above.

I then use declaration merging in my own files.

Until there is a better solution, I'll be sticking with that. I can put together a gist if it would be useful.

Re: the questions:

  1. TypeScript declaration merging
  2. Yes, it should do TypeScript declaration merging for you. I don't know about Flow.

I'm not a big fan of creating my own View just for type support, so I prefer that approach.

baptisteArno commented 3 years ago

In my app, I use patch-package to change @types/react-native's read-only types from type to interface. This change addresses the caveat I mentioned above.

I then use declaration merging in my own files.

Until there is a better solution, I'll be sticking with that. I can put together a gist if it would be useful.

Re: the questions:

  1. TypeScript declaration merging
  2. Yes, it should do TypeScript declaration merging for you. I don't know about Flow.

I'm not a big fan of creating my own View just for type support, so I prefer that approach.

What about when you need to overwrite an existing field? For example if I need to add "fixed" in position type:

declare module "react-native" {
  interface FlexStyle {
    position?: "absolute" | "relative" | "fixed";
  }
}

It complains about position being already declared.

nandorojo commented 3 years ago

I've tried to avoid fixed position and things that RN doesn't support, but when you can't, you might need to use patch-package to edit the react native types package.

flip-it commented 3 years ago

Similarly, borderRadius, translateY, translateX, and fontSize only allows number in RN, but in RNW it can also be set to a string. RNW also allows me to use the AccessibilityRole's list and listitem, not present in RN's type.

I actually do this naive transformation in a node script on postinstall:

const RN_TSD = process.cwd() + '/node_modules/@types/react-native/index.d.ts';
const raw = fs.readFileSync(RN_TSD);
let transformed = raw.toString();

transformed = transformed.replace('borderRadius?: number;', 'borderRadius?: number | string;');
/ * ... */

fs.writeFileSync(RN_TSD, transformed);

I know it will break on a change in the type, but it satisfies me for now.

janlat commented 3 years ago

@baptisteArno Should work if you change FlexStyle to ViewStyle

nandorojo commented 3 years ago

Some good news: @types/react-native seems to have implemented the fix suggested here, which means declaration merges works for TypeScript in the case of Pressable.

You can now do this in your app:

// react-native-web/overrides.ts

declare module 'react-native' {
  interface PressableStateCallbackType {
    hovered?: boolean
    focused?: boolean
  }
}

And you'll get type support for Pressable, for instance.

I think it would make sense to have one TS file in react-native-web that includes all of the declaration merging, such as the code sample above. This way, anyone who uses react-native-web gets upgraded types. For those who do not, nothing changes.

nandorojo commented 3 years ago

Sorry to keep adding messages here, but I think the solution is to add a declaration-merging.ts file /types. Similar to the flow files that are already there, it will look like this:

declare module 'react-native' {
  interface PressableStateCallbackType {
    hovered?: boolean
    focused?: boolean
  }
  interface ViewStyle {
    transitionProperty?: string
    transitionDuration?: string
  } 
  // ...etc
}

I only added a few types there, but it would be easy to add the rest of the RNW-specific props. This would solve the problem for TypeScript users.

padapada09 commented 3 years ago

You should install react-native types

   npm install @types/react-native

And redirect them to react-native-web types in tsconfig.json

    "paths": {
      "@types/react-native-web": ["../node_modules/@types/react-native"],
      "react-native": ["../node_modules/react-native-modules"]
    }
comp615 commented 3 years ago

I played around most of today with Flow and trying to get the types to just pop up using flow-copy-source. I was able to get flow updated to the latest version, including re-copying some of the vendored code from RN, and doing some code mods. See that all here: https://github.com/comp615/react-native-web/tree/flow.

However, when I tried to use this in a sample expo projects. I still got a fair number of flow errors from:

The code to update flow from that branch is 99% good, with the package change to copy sources being the bit I'd pull out.

I'd like to try it again with a create-react-app or Twitter to see if a different project setup works more easily. Expo seems to just work with react-native because RN is directly linked from source and not compiled through babel.

As a side musing...flow internally via types-first seems to keep some externally visible signature for each file. It would be awesome if it would just dump those as .flow.js files so we didn't have to copy source as lib authors.

hosseinmd commented 3 years ago

@necolas is it solved typescript issue?

nandorojo commented 3 years ago

The current commit doesn't fix it for TypeScript users.

I mentioned the TS solution in comment above.

react-native-tvos uses the strategy I mentioned. You can see their types declaration file here.

Maybe there's a way to turn the generated flow files into TS types.

kopax-polyconseil commented 2 years ago

I am experience errors with accessibilityRole typing on the Web : https://github.com/necolas/react-native-web/issues/2189#issuecomment-1008886405

Is there a support for TypeScript here we can use or a workaround ?

orlando commented 2 years ago

I am experience errors with accessibilityRole typing on the Web : #2189 (comment)

Is there a support for TypeScript here we can use or a workaround ?

I answered this here https://github.com/necolas/react-native-web/issues/2194#issuecomment-1022714079.

Tl;dr add this to your types file.

declare module 'react-native' {
  interface TextProps {
    // https://github.com/necolas/react-native-web/blob/master/packages/react-native-web/src/modules/AccessibilityUtil/propsToAccessibilityComponent.js#L12
    accessibilityLevel?: number;
    accessibilityRole?:
      | 'heading'
      | 'label'
      | 'list'
      | 'listitem'
      | 'main'
      | 'region'
      | 'strong';
  }
}
yawnr commented 2 years ago

@nandorojo pardon my ignorance, but doesn't declaring the module like that wipe out all of the existing type definitions from @types/react-native? If I do something like

// types/rn-web.d.ts
import * as RNTypes from 'react-native';

declare module 'react-native' {
  export type ImageRequireSource = string;
}

then all of my existing RN imports error with a no exported member error. Is there some config you're using to be able to extend the module declaration?

nandorojo commented 2 years ago

Add this:

import 'react-native'

See: https://github.com/nandorojo/solito/blob/master/example-monorepos/blank/packages/app/rnw-overrides.tsx

I used .tsx there, I believe that worked for me.

juanpprieto commented 1 year ago

This did it for me

npm i --save-dev @types/react-native-web@npm:@types/react-native
erickreutz commented 9 months ago

What is the current state of this with typescript?

blazejkustra commented 2 months ago

There is no DefinitelyTyped package for react-native-web. I'm not entirely sure how one would work (efficiently).

Until now. Happy to announce that types for react-native-web are now available on DefinitelyTyped! :tada:

The package comes with a react-native-web global declaration, so you can use it in your project type-safe.

import { AppRegistry } from 'react-native-web';

And it also extends the react-native types, so you can use react-native components in your react-native-web project.

import { View } from 'react-native';

<View style={{ position: 'fixed' }} />

Installation

npm install --save-dev @types/react-native-web

To extend the react-native types, you have to supply react-native-web as a member of the types compiler option in tsconfig.json.

{
  "compilerOptions": {
    "types": ["react-native-web"]
  }
}