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
Pure Functions as Stateless Components #3995
Conversation
Thank you for your pull request and welcome to our community. We require contributors to sign our Contributor License Agreement, and we don't seem to have you on file. In order for us to review and merge your code, please sign up at https://code.facebook.com/cla - and if you have received this in error or have any questions, please drop us a line at cla@fb.com. Thanks! |
Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Facebook open source project. Thanks! |
}; | ||
}; | ||
|
||
wrappedStatelessFunctions.push({fn: fn, Klass: Klass}); |
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.
It's safer and faster to store the result as a property on the function (and use WeakMap when it becomes widely supported). It's not super sanitary, but pretty common JS-practice.
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.
@syranide thanks. changed
Should I provide context as second argument? |
We should ideally try to avoid caching things on these function objects if possible. One neat feature of these functions is that they don't need a backing instance which could help performance. We also know that they won't have a What if instead of wrapping, you just kept executing it until you get a result that can be instantiated? |
One plausible incremental route could be to fork ReactCompositeComponent and see how far we can optimize that branch. |
@sebmarkbage I'll try |
@sebmarkbage @jimfb I'm copied |
Nice work! It won't warn you for not having a render method inside a component though. It'll try to use it as a render function instead of as a constructor. (With babel, you'll get a warning that the constructor can't be used as a function). Maybe you could do something like: function isStatelessComponentType(type) {
return (
typeof type === 'function' &&
(typeof type.prototype === 'undefined' ||
type.prototype instanceof ReactComponent) // change this line
);
} |
@bsansouci Sorry, but I don't get it. function Component(props) {
return <div>{props.a}</div>;
} Constructor will be rendered as usual using ReactCompositeComponent. |
Oh whoops, I meant NOT an instance of ReactComponent. |
Problem is in any classes should be rendered using ReactCompositeComponent not only childs of ReactComponent |
So any function that has render in prototype is composite component. |
Ok so any component without a render method will make React throw an error then? |
Any function without render in prototype will make React throw error if that function returns not React Element. |
@@ -83,6 +106,8 @@ function instantiateReactComponent(node, parentCompositeType) { | |||
// represenations. I.e. ART. Once those are updated to use the string | |||
// representation, we can drop this code path. | |||
instance = new element.type(element); | |||
} else if (isStatelessComponentType(element.type)) { |
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 won't work since we allow module pattern components that may not have a visible prototype. E.g:
function Foo() {
return {
render() {
return <div />;
}
};
}
I'm surprised this wasn't covered by a unit test.
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.
Are you sure React should support this kind of definition? What is benefit of this over stateless functions and classes?
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.
It was explicitly added in 0.13 because we don't want to be opinionated about how to structure classes. That is especially important for integration with compile-to-js languages.
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.
So, I don't see any option how to check function is stateless or module before ReactCompositeComponentMixin.mountComponent
. That means we should merge ReactCompositeComponentMixin
and ReactStatelessComponentMixin
and turn on optimisations if var inst = new Component(publicProps, publicContext);
returns ReactElement
.
May be you have other better and simpler ideas?
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.
@sebmarkbage I found a problem with that kind of components while trying to fix this: #4109
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.
@sebmarkbage I'm done with hack that supports stateless and module pattern components at same time.
Now component constructor called two times. I can fix this if you agreed to remove context
argument from module pattern components constructor:
// Now
type StatelessComponent = (props) -> ReactElement
type ModulePatternComponent = (props, context) -> ReactComponent
// Should be
type StatelessComponent = (props) -> ReactElement
type ModulePatternComponent = (props) -> ReactComponent
My static object/class above does just that, technically you could just skip the instantiation and pass the object/class as the instance (EDIT: ok that's not entirely true). It would even appear almost like a regular instance if someone where to try and get a reference to it, not sure why you would though. With the possibility of using option 4 for wrapping a regular function to produce such a static class/object, optionally with any other behaviors included (say
I'm curious what those are and how that actually works out given the "expensive default behavior", it sounds like having to set a magic property on the lambda would defeat the purpose. So you would end passing it through a function anyway at which point you could just go option 4 to begin with I would assume? Then there's also the issue of |
@syranide Yeah, I'm not saying your alternate syntaxes aren't valid, was just explaining the benefits. The function syntax is fairly natural, and using attributes to specify special behaviors is also fairly natural/flexible. If you prefer the fourth suggestion, you can always implement it yourself to append the attribute. The function syntax doesn't hurt you if you prefer to use a wrapper function instead of setting an attribute. |
@jimfb Lambdas with special attributes feels like revisiting function makePureLambda(render) {
return {
shoudComponentUpdate: predefined,
render: render
};
}
var MyLambdaComponent = makePureLambda(function() {
return <div />
}); That and all things considered, including inline lambdas being a really bad idea (as it breaks |
+1 |
Lambdas with attributes are conceptually no different from ES6 classes with attributes. Both are standard javascript constructs with attributes that are understandable by React. |
@jimfb If the attributes are intended to be used with classes then it kind of makes sense, is that the idea? It sounded like the attributes where for lambdas only. |
@syranide |
@jimfb "Our" objection is towards introducing opinionated attributes to specifically work around the limitations of lambdas. Classes having (static) properties isn't really controversial. |
@syranide Maybe I'm miss-understanding something, but what's the difference? class MyComponent
{
...
}
MyComponent.defaultProps = {};
module.exports = MyComponent; function MyComponent()
{
...
}
MyComponent.defaultProps = {};
module.exports = MyComponent; They look the same to me. Define something on your standard-javascript construct, which React understands, for more advanced functionality. |
@jimfb Ah, we're talking about different things :)
|
Right... but it's the same... function MyComponent()
{
...
}
MyComponent.pureRender = true;
module.exports = MyComponent; One could easily imagine the |
@jimfb #3995 (comment) ... so I think we agree, if this is a feature you intend for classes too, then I see nothing wrong with it (whether it is an attribute or w/e). But when you proposed it, it sounded like a lambda-only feature (to make up for their simplicity) and that didn't sound right. |
Ok, cool, glad we agree :). Thanks for the good discussion! |
@jimfb Yeah, I think you're right now. It trips me up a bit that you could wrap a function in another function calling it, and some behaviors no longer apply, but OTOH it's fair: we don't know if the new function also exhibits them. |
@gaearon It's worth noting that the wrapper function can return an element instead of manually invoking the delegate, thereby preserving the original behaviors because the nested component renders independently. The wrapper can also copy the values from the delegate, thereby exposing those behaviors while avoiding a second component render. Or the wrapper can expose different behaviors if so desired. This behavior is the same as HOCs using class based components, and is fully flexible without making assumptions about the propagation of behaviors. I'd argue that these are the ideal semantics. |
@jimfb Agreed. 👍 |
For anyone following along on this issue, an alternate variant of this was merged in #4587 but we might take this implementation or a similar one in a future release. |
any plans to merge this into 0.15? |
@sebmarkbage @spicyj @syranide @jimfb |
We would need to refactor out the part of the initialization that makes it necessary to create a "ReactStatelessOrCompositeComponentWrapper" and avoid the allocation there. If you can avoid the extra allocation we can take it. See |
We took #4587. At the very least, this would require a ton of work. Merge conflicts, six months old, this PR is dead. |
ref #3220