Skip to content
This repository has been archived by the owner on Jul 20, 2020. It is now read-only.

Using react-three-renderer with Typescript #127

Open
jugglingcats opened this issue Nov 17, 2016 · 13 comments
Open

Using react-three-renderer with Typescript #127

jugglingcats opened this issue Nov 17, 2016 · 13 comments

Comments

@jugglingcats
Copy link

This doesn't appear to be straightforward given the lack of @types for this lib. I have tried to force it in and ignore the typescript compiler warnings but get the following error:

Uncaught TypeError: Cannot read property 'Object3D' of undefined
    at eval (webpack:///./~/react-three-renderer/lib/Resources/ResourceContainer.js?:31:18)

Are there any plans to provide support for Typescript? Or are there any workarounds perhaps?

Thanks

@jugglingcats
Copy link
Author

My error was due to using wrong THREE version, so that is resolved.

I also managed to get TS compile errors to be reduced significantly by adding the following to my code:

declare global {
    namespace JSX {
        interface IntrinsicElements {
            scene: any;
            perspectiveCamera: any;
            ambientLight: any;
            axisHelper: any;
            // other elements can be added here
        }
    }
}

Note that Typescript treats component names in JSX with lowercase as 'intrinsic', ie. expects them to be HTML and not React components.

I still have an error on compiling about missing module "react-three-component" but Webpack is including it and my app is running.

Perhaps others can add Typescript specific help/workarounds to this thread. Would be good to see first class Typescript support at some point!

@toxicFork
Copy link
Collaborator

Regarding lower case components being html perhaps we could extend the
definitions or something to accept custom "native types", I think that
would be awesome for prop type verification on compile time!

Currently the main priority of the project is to have full feature
compatibility with threejs on latest react, but it would still be awesome
to see it working nicely with TS :) let's keep the issue open until then,
could be a fun one.

On Thu, Nov 17, 2016, 20:57 jugglingcats notifications@github.com wrote:

My error was due to using wrong THREE version, so that is resolved.

I also managed to get TS compile errors to be reduced significantly by
adding the following to my code:

declare global {
namespace JSX {
interface IntrinsicElements {
scene: any;
perspectiveCamera: any;
ambientLight: any;
axisHelper: any;
// other elements can be added here
}
}
}

Note that Typescript treats component names in JSX with lowercase as
'intrinsic', ie. expects them to be HTML and not React components.

I still have an error on compiling about missing module
"react-three-component" but Webpack is including it and my app is running.

Perhaps others can add Typescript specific help/workarounds to this
thread. Would be good to see first class Typescript support at some point!


You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
#127 (comment),
or mute the thread
https://github.com/notifications/unsubscribe-auth/AA0iLY1tdbnYIMhkJMsdBeCvhjbIUWByks5q_L_HgaJpZM4K1o39
.

@jugglingcats
Copy link
Author

I agree it would be great to have prop/attribute validation and auto-complete in the IDE and this is one of the main benefits of using Typescript IMO.

I am not sure adding elements to JSX.IntrinsicElements is the way to go, because of possible conflicts with other libs (you would effectively be polluting a shared namespace). I think ultimately you might consider providing initial-caps versions of react-three-renderer components, ie. Scene, PerspectiveCamera and so on. These could either be used as <React3.Scene> or aliased during import.

Just my 2p

@jugglingcats
Copy link
Author

For info I already found a name clash: line is defined as type React.SVGProps.

It runs :) but compiler and IDE complain :(

@toxicFork
Copy link
Collaborator

I think things would need to be context sensitive somehow as new renderers emerge. Could do an annotation to a file or the render function for example to specify what sort of renderer will be used for a component.

@jugglingcats
Copy link
Author

I don't understand very much about renderers, but presumably a single component could use React3 and SVG, eg:

render() {
  return (<div>
   <svg><line ... /></svg>
   <React3>
   ...
     <line ... />
   ...
   </React3>
  </div>)
}

@toxicFork
Copy link
Collaborator

That's a bad component that should be split in half and deserves multiple
warnings ;)

On Fri, Nov 18, 2016, 11:48 jugglingcats notifications@github.com wrote:

I don't understand very much about renderers, but presumably a single
component could use React3 and SVG, eg:

render() {
return (


<line ... />

...
<line ... />
...

) }


You are receiving this because you commented.

Reply to this email directly, view it on GitHub
#127 (comment),
or mute the thread
https://github.com/notifications/unsubscribe-auth/AA0iLVZVWYGeFkzGqZatPPn1qoP-lxm1ks5q_ZCfgaJpZM4K1o39
.

@jugglingcats
Copy link
Author

Heheh. I tend to agree!

I thought more about what you said and I think what you need is a way to say "The React3 component doesn't contain JSX.IntrinsicElements but instead contains React3.IntrinsicElements". That would be a nice solution.

@xogeny
Copy link

xogeny commented Apr 18, 2017

I just came across this thread. I came up with a slightly different approach that doesn't involve having to extend JSX.IntrinsicElements but gives you all the type safety. It involves a bit of work creating wrapper elements. But honestly, I think these are probably as much work as creating a .d.ts file.

First, I we have to import React3. I was wrestling with TS2+'s augmentation stuff and couldn't get it to work. So I just punted and did this at the top of my file:

let React3: React.StatelessComponent<React3Props> = require('react-three-renderer');

...and I defined React3Props as:

export interface React3Props {
    mainCamera: string;
    width: number;
    height: number;
    onAnimate?: () => void;
}

I'm sure there is more, but this is all I'm using so far. Then you have the issue of getting that stuff out of IntrinsicElements. I managed that by creating wrapper classes like this:

export interface PerspectiveCameraProps {
                    name: string;
                    fov: number;
                    aspect: number;
                    near: number;
                    far: number;
                    position: THREE.Vector;
}

export class PerspectiveCamera extends React.Component<PerspectiveCameraProps,void> {
    render() {
        return React.createElement("perspectiveCamera", { ...this.props });
    }
}

Now you've got a component that the TS compiler recognizes as a first class component. This means you can then do:

<PerspectiveCamera
     name="camera"
     fov={75}
     aspect={this.props.width / this.props.height}
     near={0.1}
     far={1000}
     position={this.cameraPosition}/>

Now this may seem like a lot of work. But like I said, I'm not sure it is any more work than writing a normal .d.ts file. A slight advantage that this has, I suppose, is that it doesn't require react-three-renderer to change anything about its conventions. As far as I can tell, what the author was going for was a direct mapping between his components and the underlying THREE.* functions (which have lower case names). So this is just layering on top of that things that are more like first class React (class-based) components that can be composed just like the react-three-renderer components but with increased type safety.

Thoughts?

@jugglingcats
Copy link
Author

jugglingcats commented Apr 19, 2017

Interesting approach...!

I assume it works fine when you have nested react-three-renderer components, even though you are introducing an intermediate element into the component tree?

There would be a small performance overhead but I don't see this being an issue.

I guess one disadvantage is that this would likely be a separate project and cannot easily become a first class citizen, ie. part of the react-three-renderer project in the way that .d.ts definitions could.

I just posted this question to SO as a last attempt to find a "pure" solution: http://stackoverflow.com/questions/43493059/react-typescript-custom-renderer-and-jsx-intrinsicelements.

If we adopt this approach, would you be willing to host the project...? Or maybe @toxicFork would be willing to make it a companion project to react-three-renderer.

@xogeny
Copy link

xogeny commented Apr 19, 2017

The intermediate elements don't really seem to cause any problems with the exception of <scene>. I'm not sure why, but that one doesn't want to be wrapped. Perhaps there is some logic somewhere that insists it be a direct child of the root. Not sure. For the others, I've successfully created "intermediate" elements that are nice first class react components (with their own typed properties, etc).

I agree, there is a small overhead. Note that, as far as I can tell, anytime you'd like to group things into reusable components, you'd introduce just about the same overhead. I'm just forcing each leaf in the tree to be a reusable component. There is both the overhead of the function call as well as the overhead of traversing a deeper tree. Perhaps there are other concerns as well.

I'm not sure how the "mounting" vs. "rendering" stuff is handled internally in R3R. My hope is that it is using the virtual DOM approach to optimize (i.e., minimize) manipulations of the 3D scene. If so, that might help. If not, then it would perhaps amplify any inefficiencies introduced here.

You are correct that it would probably be a separate project. You could, of course, include all this in R3R itself. But I'm guessing the author doesn't want that headache. I would have preferred the .d.ts approach myself, to be clear. But since that seems very difficult in this case because of the conventions followed by R3R and TS, this was the best I could come up with.

As for "hosting" it, I'd certainly be willing to make it publicly available. I make no promises on completeness or maintenance. I don't expect to be using this extensively in the future. So I can't say I'd be able to do all the greens keeping some might expect. Perhaps @toxicFork can chime in with his opinions before we really make any decisions here.

@toxicFork
Copy link
Collaborator

toxicFork commented Apr 28, 2017

Cool solutions :)

I would prefer a comment / .d.ts based solution even if it takes more work because I am against overheads in general. However, there are ways to solve the overhead problem, for example we could (with some effort) write a babel (or whatever compiler) pre/post process plugin to convert these back into native components.

I think it would be good to have it as a separate project if possible (so you will have authority over it, issues for it won't be lost in normal r3r issues, and so on), then we can consider merging it into R3R when the time comes.

@xogeny
Copy link

xogeny commented Apr 28, 2017

FYI, I actually gave up on my wrapper stuff. It wasn't that it was technically unsound. I just ended up opting for something that interacted directly with the THREE api rather than representing the data declaratively in JSX.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants