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

What's the reason behind using class instead of className? #103

Closed
idchlife opened this issue Mar 22, 2016 · 20 comments
Closed

What's the reason behind using class instead of className? #103

idchlife opened this issue Mar 22, 2016 · 20 comments

Comments

@idchlife
Copy link

I've been thinking, why class but not className? If I'm not mistaken, className in react used due to Element.className and not Element.class. Why class here? If suddenly someone would try to switch-and-go to react or similar, he will experience huge compatibility issues.

Or there is no goal in the end - for preact to be total drop-in-replacement but to be something new?

@developit
Copy link
Member

Excellent question. To start, Preact is an implementation of the core value behind React - the goal is not to be a perfect drop-in replacement for React, since that would require re-implementing things that the React team has deprecated or moved away from.

However, since a "React alternative" is very similar to being an "alternative implementation of React", I put together preact-compat, which smooths out the differences. I think this approach is ideal, because it keeps the Preact core lean and explicitly classifies each feature as being either part of Preact itself, or part of an effort to support React's exact API and intricacies.

The decision to use className was made years ago internally at Facebook, when React wasn't even public. From what I have gathered, the reasons for using className instead of class were that it matches the DOM className property, and that it avoids using the reserved word class. The latter is a reasonable point, but I can count the number of times I've run into this on one hand, and 100% of those times occurred when authoring libraries. In those cases, one typically uses classnames() or similar anyway.

The first point about DOM property parity has actually been mostly discarded as React has evolved. In the latest 15.x release, React actually uses attributes instead of properties in many cases, similar to Preact. There are an incredibly small number of cases where the DOM property for a given HTML attribute uses a different name - className~class and htmlFor~for are two examples. Another would be passing a String value for the style JSX prop, which internally uses style.cssText (doesn't match at all). I don't see anyone using htmlFor="" in React (maybe I'm missing it), and yet className remains universally special-cased.

In addition to these disparities, the semantic meaning of className and class are identical: both are Strings that resolve to the same value. It does so happen that the className property is faster to manipulate than the class attribute - from the perspective of Preact, this is the only reason to differentiate between the two, and is strictly an internal optimization. If the single if statement pertaining to that optimization were to be removed, the value would fall through to setAttribute and the net effect would be purely a small performance difference.

However, if you compare this to React's position on the matter - everyone who learns React+JSX must also understand className - someone who normally writes HTML must now remember that React uses a different attribute name (prop name) for exactly the same purpose. To me, that seems like a silly issue to take a stand on.

I think it's admirable that so much thought went into the initial decisions that were made around that time, but I also think it's a good idea to continually revisit decisions as a project evolves. For those who are new to JSX, className is something that needs to be learned, and since it serves no purpose internally that seems like unnecessary baggage.

Finally, I should mention that Preact will happily consume className props/attributes if you use them. The JSX reviver simply changes the name to class and continues on as it would have if you'd used class.

I hope I don't sound too dismissive or opinionated here, this is a discussion I've had with many people over the past few months and I enjoy talking about it. Cheers!

@idchlife
Copy link
Author

Wow this is really good explanation and I'm with you about deprecation and unnecessary baggage.

Thank you!

Also, I think this exactly explanation should be in FAQ part of the website. It's magnificent.

@AlexAThomas
Copy link

AlexAThomas commented Jun 25, 2016

I'm getting into preact to create my own elements, while still using the libraries out there like : https://github.com/STRML/react-grid-layout which depend on React.

Is there a reccomended way to use preact (with its class prop) with preact-compat+React library (with their className prop?)

@developit
Copy link
Member

@AlexAThomas Preact support both properties. If you prefer className, you can use it as you would normally.

Preact intentionally defaults to class because React's original reasons for choosing className ended up not making much sense, though it took a couple of years to see this and they're stuck with the name now. className was originally chosen because that's the DOM property name, but in reality the value of that prop gets transformed prior to being used anyway - this means the name is shared, but the semantics are not. Another original reason was because props.class throws in IE8 - however, this doesn't provide justification anymore since IE8 is no longer relevant/supported, and Babel & UglifyJS auto-escape keyword properties (props['class']) to avoid the bug.

Preact also ships with object class / className support by default, which further separates the semantics of JSX class/className from the DOM className property, which can only be a String.

<a class={{ foo:true, bar:false }} /> // equivalent to: <a class="foo" />

Let me know if this answers your question - I know it can be strange to see intentional differences in a library that is otherwise so similar to React, but these are part of the reason Preact exists. It is a library that has no growth baggage, and thus can make decisions that build on a few years of learning in this space.

@Download
Copy link
Contributor

Download commented Nov 3, 2016

@AlexAThomas

If you are just starting out with Preact and writing JSX components, I would recommend you to try to restrict yourself to stateless pure functions as much as you can. They have the exact same API in both libraries and it will very quickly 'force' you into the right mindset. You actually only rarely need state.

Here is what a HelloWorld component would look like:

// first, make sure the JSX processor is in scope
import { h } from 'preact'
// or, import React from 'react'

// then use JSX as you see fit in a pure stateless component
export default function Greet({greeting='Hello, World!', children, ...props}) {return (
  <div {...props}>
    <b>{greeting}</b>
    {children}
  </div>
)}

// use it like so:
var markup = <Greet greeting="Hi there!"><p>How are you doing?</p></Greet>

You can even write these components to be 'universal' in the sense that they could be made to work under React and Preact interchangeably, without needing preact-compat or aliasses in webpack etc. This would involve removing the only framework specific code (the import statements) and agreeing on a global name for the JSX processor function and setting that via a pragma in the babel config. There is an interesting article about this subject if you'd like to know more:
http://jxnblk.com/writing/posts/universal-ui-components/
I tried it out and it does work. I write my JSX components without import statements now. But not really actively using it, I just settled for Preact 😉

@targumon
Copy link

targumon commented Mar 5, 2018

Small tip to whoever works with IntelliJ (or WebStorm etc.) and gets warned that 'Attribute class is not allowed here':
Silence it by going to 'Preferences' > 'Inspections' > 'Unknown HTML tag attribute' and adding 'class' to 'Custom HTML tag attributes'.

@levrik
Copy link
Contributor

levrik commented Apr 21, 2018

@developit That doesn't seem to be true that Babel auto-escapes reserved words.
I'm seeing that in my Babel output:

var className = props.class || props.className;

@nojvek
Copy link
Contributor

nojvek commented Mar 12, 2020

@developit I take it that support for preact class as objects was later removed. How does one achieve this ?

This doesn't work anymore

<div
            class={{
              item: true,
              itemSelected: item.selected,
            }}
/>

@marvinhagemeister
Copy link
Member

marvinhagemeister commented Mar 13, 2020

@nojvek That's true, we removed it with the Preact X release as it seemed wrong to have in core. Pretty much all users were using the classnames or clsx library for that on to of Preact anyways. props.class and props.className are always expected to be a string.

@targumon
Copy link

@nojvek maybe you'll find this useful:

I work on a project with tight bundle budget (hence choosing Preact 🤓) so I use my own version of classnames (and there may be other reasons to do so - security concerns regarding 3rd parties, the Leftpad debacle...):

utils:

export const classNames = classArr => classArr.filter(el => el).join(' ') // filter falsy values

someComponent:

<div className={classNames(['always', isSpecialCase && 'special'])}>

@nojvek
Copy link
Contributor

nojvek commented Mar 15, 2020

Ah sweet. That works. Coming from snabbdom the class object notation is natively supported and they use .classList property for diffing which is a set under hood so faster than setting the class attributes on the dom nodes.

I’m hoping preact also uses classList properly rather than class attribute on the underlying dom nodes.

@marvinhagemeister
Copy link
Member

@nojvek That's very unlikey that we'll switch to classList as it doesn't support all our use cases. See this scenario:

// First render
<div class="foo bar bob baz" />

// second render
<div class="bar baz" />

Right now this is just a property setter:

dom.className = "bar baz"

If we would switch to use classList here, we'd suffer greatly in performance as we'd need to introduce various forms of iterations and checks. The classList is only faster if you can ensure that the shape of the object always stays the same between renders.

// First render
<div class={{ foo: true, bar: true }} />

// Second render, shape changed
<div class={{ foo: true }} />

In the above example we don't know if a class was removed right away. We'd need to manually iterate over the old object and check if the property is not present in the new class object.

The only scenario in which this is more performent would be this one:

// First render
<div class={{ foo: true, bar: true }} />

// Second render, shape stays the same
<div class={{ foo: true, bar: false }} />

@DominikGuzei
Copy link

Thanks for the explanation 👍

I just stumbled into this because Preact internally rewrites className to class which was not obvious to me at all and actually causes a small "issue" for me right now as it's not possible to destructure class from a props object like this:

const { class, ...linkProps } = props;
<Link {...linkProps} />

which I needed because the Next.js Link component doesn't accept class or className props.

Facebook also thought about this transition and dropped it exactly because of these issues with class
facebook/react#13525 (comment)

@andrewiggins
Copy link
Member

Interesting. Can you open a new issue with a full reproduction so we can investigate and triage?

@Download
Copy link
Contributor

Download commented Sep 8, 2020

@DominikGuzei
It's a bit ugly but you can do this:

var props = { 'class': 'test', some: 'other' }
var { ...linkProps } = props // shallow copy
delete linkProps['class'] // ugly but works
console.info(linkProps) // {some: "other"}

@ansonatloop
Copy link

+1 on the destructure bit. So Preact adds both class and className to the props if you just pass className? I have no idea how I never stumbled across this issue before...This means if we want to not pass through a class to children we need to pick both props out?

const MyComponent = ({className, ...rest) => (
  <div className={className}>
    <div {...rest} /> <--- gets "class" !!!
  </div>
);

Did I miss something? Again, I can't believe this hasn't caused issues for me yet (maybe it has)

@marvinhagemeister
Copy link
Member

marvinhagemeister commented Sep 28, 2020

@ansonatloop No, the inner <div> doesn't get class, because we're not passing both class and className around. What we're doing is installing a getter that is non-enumerable (= ignored with spread) for the property that is not present. So the code you posted works as expected and no class is spread to the inner div.

We had a regression in 10.5.0 - 10.5.2 that is now fixed. Just published 10.5.3 moments ago.

@ansonatloop
Copy link

@marvinhagemeister Hmm, well, I am on a slightly older version (10.4.7) and with preact-compat that does not seem to exactly be the case.

https://github.com/preactjs/preact/blob/10.4.7/compat/src/render.js#L108

see: classNameDescriptor.enumerable = 'className' in props' which would be true if I pass className in props, meaning both class and className are enumerable.

But nonetheless, you're probably referring to a version in which the above is no longer true. I see #2772 might be related and after bumping to 10.5.3 I don't see this issue anymore 👍

@rejc2
Copy link

rejc2 commented Dec 9, 2020

I just stumbled into this because Preact internally rewrites className to class which was not obvious to me at all and actually causes a small "issue" for me right now as it's not possible to destructure class from a props object like this:

const { class, ...linkProps } = props;
<Link {...linkProps} />

Actually this is easy to fix by assigning the property to a different variable name when destructuring:

const { class: className, ...linkProps } = props;
<Link {...linkProps} />

(See the article on Destructuring assignment on MDN.)

@Download
Copy link
Contributor

Download commented Dec 9, 2020

@rejc2 Oooh nice one! I never thought about that but it's sure a lot cleaner to do it like that 👍

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

No branches or pull requests