-
-
Notifications
You must be signed in to change notification settings - Fork 926
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
Components don't work with ES6 classes. #618
Comments
You might be interested in how I've integrated ES6 classes with Mithril components in my project: https://github.com/flarum/core/blob/master/js/lib/component.js Usage: class FooComponent extends Component {
constructor(props) {
super(props);
this.showing = m.prop(true);
}
view() {
return m('div', this.showing ? this.props.content : '');
}
}
m.mount(document.body, FooComponent.component({content: 'bar'})); Note that the component class itself is the controller, and the view is its method. It's a bit more like React. |
That's some tricky/non-obvious code going on in An aside: I notice you don't allow parameters to the view function. I was arguing this with a co-worker today. I think a component's view should allow for arguments so that you can pass in a child vdom. For example, say you have a component that is logically a container, wouldn't you want to be able to pass in the child virtual dom to the component's view? class MyContainer {
view(ctrl, children) {
return m('div.cool-class', {}, children);
}
}
function View(ctrl) {
return m('div', [
m.component(MyContainer, [m('div', "I am a child")])
]);
}
m.mount(document.body, {view: View}); As for using
I do like being able to use |
TL;DR: this isn't how Mithril works. Why would you want it to work this way? The purpose of the components structure is to provide 2 objects — a On Wednesday, 13 May 2015, Richard Eames notifications@github.com wrote:
Regards, barney.carroll@gmail.com barneycarroll.com |
@barneycarroll, to whom are you directing that comment? If it's me: mithril should be agnostic whether or not my "controller constructor" is a plain function, ES5 class/object, or ES6 class, but it shouldn't break in the case it is an ES6 class. My problem is that mithril changes
Without using Edit:* changing |
@Naddiseo sorry, I just worked out what you're going on about. Bad reading on my part. The bug is that controllers don't inherit prototypes (classes are just a sugar expression that defines constructor + prototype in one).
Personally, I've never been a fan of function controller( x, y ){
var privateX = x
var privateY = y
return {
publicZ : function(){
return privateX + privateY
}
}
}
I'd dread to think what kind of application you have that's facing measurable performance issues because of closures. You realise Mithril core doesn't use prototypes at all and the internal functions execute 1000s of times in any given view? But I digress. For better or worse, |
Thought this would do it, but tests are breaking. :/ |
@Naddiseo hey, I think this might be a false alarm caused by typos in the original fiddle (the controller's Code, for reference: let component = {
controller : class{
constructor(){
this.x = 1
}
increment(){
this.x ++
}
},
view : ctrl =>
m('h1',
"Hello ",
ctrl.x,
m( 'br' ),
m( 'button', {
onclick : e => ctrl.increment()
},
'Increment!'
)
)
}
m.mount( document.body, {
view: ctrl =>
m('div',
component,
" World"
)
} ) |
@barneycarroll But now you've changed the premise by not using m.component() for the parameterization. The assessment that prototypes are a core part and this should be fixed ought still to stand. This also breaks using a TypeScript class as a controller. Anyway, if you leave |
plz, plz make the use of classes optional. I don't want to be forced to use them. I find them cumbersome and ugly. The code is IMHO much easier to read without them. |
+100 The argument for seems to be "I can't experience terrible flaws in the language because the library doesn't give me the opportunity". It's not a credible user story if you ask me. |
+1 to @barneycarroll and @StephanHoyer. I would say that adding support for classes even as an optional feature is adding unnecessary cruft to the codebase. But I'm also interested in hearing more from @Naddiseo and @tobscure why they want Components as classes. |
@diogob, I only want to be able to use classes for the
Whatever the case may be, my point earlier in the thread still holds: if mithril uses |
if you just want that class as your controller why not just pass it like this |
ops, it will not work. sorry.. |
One great thing about classes is that you have full code-completion, you dont have to remember any names just select them. |
@daslicht BTW, that point is moot if you consider that current components are almost classes conceptually. IMHO, I see other reasons to integrate classes and/or functions, though. A great example is developer ergonomics and debugging at scale - think of the React Developer Tools extension for Chrome. It's pretty helpful for React developers. Without the ability to get the name of a component, it's not possible to create that kind of thing for Mithril. And if components are plain objects, that's not possible (objects don't have a @lhorie You might appreciate the above paragraph for your rewrite. |
I don't care how it's called unless you get full code-completion. Just choose from a list without even tying. That makes meaningful names much faster to use than typing them manually 1000 times. |
@daslicht Can you choose from a list of everything in scope that satisfies a type? I don't use a lot of TypeScript or IDEs of any kind. |
@daslicht typing the same thing out 1000 times sounds like a lot of hard work. Can you point to any example of a meaningful name in you have typed out more than 10 times in the context of a Mithril component? My contention is that "classes are awesome" is a truism that can't be related to this topic in a practical way. None of the advocates have mentioned any scenario where this might be of use except in the most general way possible: the onus is on people who've actually looked into the code and use it without classes to reimplement the API with classes so that people who like classes can see whether or not it's useful to them. It doesn't sound like a very convincing or appealing exercise. |
@barneycarroll I did mention developer ergonomics - it's not possible to develop things like React Dev Tools or similar for Mithril, since object components don't have that magic That might not appeal to you, since IIRC you don't use it much at scale, but I know some do, and that ability would be incredibly powerful for those use cases. It also helps the more kinesthetic, tactile programmers as well, which tend to do best with a very powerful, detailed autocomplete and GUI graphs of all the nodes in the tree, moving in real time, like in Light Tables. IIUC, you're best with just text in that tab completion with identifiers and some mild autocomplete works perfectly fine for you most of the time, and pictures usually just get in the way of your usual routine. I'm more of a mixture, where I need a little of both. It really comes down to how people think (and how no two think alike). So I understand how it wouldn't appeal to everyone, but since people think best in different ways (classes are a little more visually descriptive, while objects are a little more concise and easier to textually digest), and both versions have their merits (components are easier to generate from factories if they're objects, while classes are easier to statically analyze), that's why I've always supported both versions from the very start. |
May i ask why? As far as i can see, you are suggesting we should instantialize class automatically inside |
@winterland1989 via |
i see, but this will change m's semantic, since m is supposed to be evaluated eagerly without cache(without subtree retain optimization), if we instantialized a stateful component using m, there must be some magic should be cast, no? |
Working on this now for the rewrite. |
Adding a few ¢ to this thread. For stateless components, using a POJO is totally fine; I don't see much reason to use classes. So this will mostly be about stateful components. For stateful components that have their own properties or methods, I think POJOs are slightly confusing when you first start using them, as they are used by Mithril as a template to create instances. What For the rest of this comment I'm going to talk about Typescript, which I realize must represent a tiny minority of Mithril users, so I'm not sure how much effort should be spent accomodating us. Typed JS variants do seem to be gaining popularity however and I think these create some additional use cases for classes. First, it's a little more conventional to declare a class to take advantage of types than to do something like this. Not that it's a terrible solution but it does have drawbacks. Now that Typescript supports the rather nice feature of non-nullable types (as Flow does) it can be awkward to declare a non-nullable property on a typed POJO if you can't assign it until interface MyComponent extends Mithril.Component {
foo: Foo
}
const myComponent: MyComponent = {
foo: undefined, // <- can't do if we want non-nullable, can't omit either.
oninit (this: MyComponent, vnode: Mithril.Vnode) {
this.foo = new Foo(vnode.attrs.fooInitializer)
},
view() {/*...*/}
} In the above case, I want to treat Now interestingly, neither Typescript or Flow can actually detect this problem: class MyComponent implements Mithril.Component {
foo: Foo // this.foo is undefined but no compiler error
constructor() {}
oninit (vnode: Mithril.Vnode) {
this.foo = new Foo(vnode.attrs.fooInitializer)
}
view () { ... }
} So that would compile happily. Simply allowing classes to be used as components gets around the problem of having non-nullable property assignment deferred until What might be a better, long-term solution is to have the constructor called instead of/in addition to class MyComponent implements Mithril.Component {
foo: Foo
constructor (vnode: Mithril.Vnode) {
this.foo = new Foo(vnode.attrs.fooInitializer)
}
view () { ... }
} For reference, here are types for mithril 1.0 that I've been hacking away on. |
Folks, I'd love to have your feedback on #1339 You can play with it here: https://github.com/j2css/mithril.js/blob/b581e5a1b4843eca3937835b62714b160dcb6b8c/mithril.js For classes: class MyComponent extends m.Component {
constructor(vnode) {
// initialize things here
// vnode.state doesn't exist yet, but you can access its future incarnation as `this`
}
// define hooks here if you want
oncreate() {}
view() { return "Classy" }
}
m.render(document.body, m(MyComponent)) For factories: function MyFactory(vnode) {
// initialize things here (don't rely on `vnode.state` yet)
var state = {
// define hooks here if you want
oncreate() {},
view(vnode) {
// this === state === vnode.state
return "Industrial"
}
}
return state
}
m.render(document.body, m(MyFactory)) In both cases |
@pygy that factory example is very interesting. It would be very nice to have a native way to write mithril components that don't need I always imagined the various hooks to be passed in to the factory as streams, and for the factory to return a view stream and nothing else. But I think I like this better ( though I like hooks as streams as an idea ( I'd like if the router used streams too) ). It would be nice to be able to return either a So this: would be a valid component function MyFactory(vnode) {
return () => "nice pun!"
}
m.render(document.body, m(MyFactory)) And because props are functions, you could return a view stream function Adder(vnode){
const a = m.prop(0)
const b = m.prop(0)
const sum = m.prop.combine( () => a() + b(), a,b)
const view = sum.map(function(){
return [
m('input[type=number]', { oninput: m.withAttr('value', a) })
,m('input[type=number]', { oninput: m.withAttr('value', b) })
,m('Sum: ', sum() )
]
})
sum(0)
return view
}
m.render(document.body, m(Adder)) I'd really like to be able to just return a function optionally, I think it will be the common case and could open up some interesting optimizations down the line if the view is a stream. But I could happily live with the current proposal of just returning |
Here's a typescript example using a class component: https://github.com/spacejack/mithril-class-test And a factory component: https://github.com/spacejack/mithril-factory-test Experimental typings included. Seems to work okay so far. 👍 |
I've updated my above examples to include POJO, class and factory components here: https://github.com/spacejack/mithril-component-types This uses my most recent type definitions for 1.0 with some additons to cover the class and factory component types (see here.) These definitions provide much better inference for POJO and factory components. Class components are unfortunately a bit awkward to work with. I can't see a way to get strongly typed attrs in the hyperscript function (example) due to the difficulty of expressing the attrs & state types in the parameters. For a class component, hyperscript must take a There are pros and cons to each type of component, and then each of those can utilize different approaches to declare types (or not.) |
Closing as we are no longer accepting feature requests for 0.2.x. |
Since ES6 classes need to be called with
new
andthis
needs to be an instance of the class, components will not work with a class. The issue stems from here: https://github.com/lhorie/mithril.js/blob/next/mithril.js#L554Example (using babel-compiled class): https://jsfiddle.net/1prjtv78/
A work around is to wrap the component controller like:
Which transpiles to something equally ugly:
Example of working: https://jsfiddle.net/1prjtv78/1/
Perhaps the
parametize.controller
function can do something similar to the transpiled code.The text was updated successfully, but these errors were encountered: