React

[classnames] classnames/bind 함수와 classnames 함수의 차이

Asset Type
File Type
When to use
Reference
Created by
Created time
2022/03/13 14:48
Last edited time
2022/03/13 16:00
1.
classnames를 사용하지 않을 때
import styles from './Media.module.scss'; ... <div className={`${styles.bl_Media} ${styles.bl_Media__A}`}><div>
TypeScript
복사
2.
classnames 사용
import styles from './Media.module.scss'; import cs from 'classnames'; ... <div className={cs(styles.bl_Media, styles.bl_Media__A)}><div>
TypeScript
복사
3.
classnames/bind 사용
import styles from './Media.module.scss'; import cs from 'classnames/bind'; const cx = cs.bind(styles); ... <div className={cx('bl_Media','bl_Media__A')}><div>
TypeScript
복사

Example 3: with classnames.bind

In the above example, we're using the styles object every time we need to reference a local classname. This can be quite cumbersome. If you're converting an old component to use CSS modules you can actually increase the files size (and reduce the readability) quite a bit.
Thankfully, classnames ships with an alternative bind method that's designed specifically to make working with CSS modules much less cumbersome. It requires a small bit of extra set up (and has one important caveat) but it gets us closer to the less verbose syntax we enjoyed in the days before CSS Modules. Of course, there are numerous benefits of using CSS Modules. So, why not make it easy to work with?
위의 예에서는 로컬 클래스 이름을 참조해야 할 때마다 styles 객체를 사용한다. 이것은 꽤 번거로울 수 있다. 오래된 구성요소를 CSS 모듈을 사용하도록 변환하는 경우 실제로 파일 크기를 크게 늘리거나 가독성을 낮출 수 있다.
다행히 클래스 네임은 CSS 모듈 작업을 훨씬 덜 번거롭게 하기 위해 특별히 설계된 대체 바인드 방법을 제공한다. 약간의 추가 설정이 필요하지만(중요한 주의사항이 하나 있음) CSS Modules 이전 시대에 즐겼던 덜 장황한 구문에 더 가까워진다.
Bad things:
If you see cx('something') you will need to refer to the styles.css to be sure you're not accidentally referencing a global class (more on this below).
cx('something')가 표시되면 styles.css를 참조하여 글로벌 클래스를 실수로 참조하는 것이 아닌지 확인해야 한다(아래에 자세히 설명되어 있음).
Good things:
More like using classnames without CSS Modules
Using cx('something') is significantly fewer characters than classnames(styles.something)
Using cx({ selected }) is significantly less cumbersome/error prone than classnames({ [styles.selected]: selected})
In situations where styles is undefined (some edge cases in tests and SSR), the bound cx function falls back silently to global classes.
Using the bound cx version makes it less painful to use dash-cased strings, consider cx('something-else') versus classnames(styles['something-else']). It essentially removes the pressure to use camelCased strings for classnames.
- CSS 모듈 없이 클래스 이름을 사용하는 것과 더 유사하다 - cx('something')를 사용하는 것은 클래스 이름(스타일)보다 문자 수가 상당히 적다. - cx({ selected })를 사용하는 것은 classnames({ [styles.selected]: selected})보다 훨씬 덜 번거롭고 오류가 발생하기 쉽다. - 스타일이 정의되지 않은 상황(테스트 및 SSR의 일부 에지 사례)에서는 바인딩된 cx 함수가 전역 클래스로 자동으로 돌아간다. - 클래스 이름에 camelCased 문자열을 사용해야 하는 부담을 없애준다.
Note: Falling back to global styles is maybe a good thing or a bad thing... depending on why the styles object was undefined.
import classnames from 'classnames/bind'; // <-- notice bind import PropTypes from 'prop-types'; import React from 'react'; import styles from './styles.css'; const cx = classname.bind(styles); // <-- explicitly bind your styles const Something = ({ selected }) => { return ( <div className={cx('something', { selected })}> // .selected에 대한 selected 변수 Hello <span className={cx('global-style', 'something-else')}> World </span> </div> ); }; Something.propTypes = { selected: PropTypes.bool }; export default Something;
TypeScript
복사

Laying a trap

Take a moment and refresh your memory on the trap we've been laying. As noted above, it's possible to find an edge case where the bound cx func will return an unexpected global class name.
Remember also, we're explicitly binding our styles object map to the cx function. The issue is not a matter of being explicit. Within your file, using cx or classnames are equally explicit. Consider cx('something') in the example above versus classnames(styles.something). Both examples demonstrate where the class name comes from.
If you are going to be using the bound cx func, you will need to keep these traps in mind.
Note: Although the bound cx function exposes these edge cases, it's really a failing of the developer. You shouldn't be placing global classes in your CSS modules (unless you wrap them in :global()). And you shouldn't be referencing local classes in your component that aren't actually in your CSS module. Whoops!
위에서 언급한 바와 같이, cx 함수가 예기치 않은 전역 클래스 이름을 반환하는 에지 케이스를 발견할 수 있다.
또한 스타일 객체 맵을 cx 함수에 명시적으로 바인딩하고 있다. 그 이슈는 명시적인 것의 문제가 아니다. 파일 내에서 cx 또는 classnames을 사용하는 것은 동일하게 명시적이다. 두 예제 다 className이 어디서 왔는지 정의한다. 만약 당신이 cx 함수를 사용한다면, 당신은 이 트랩들을 명심할 필요가 있을 것이다.
참고: 비록 바인딩된 cx 함수가 이러한 엣지 케이스를 노출시키지만, 이는 사실 개발자의 실수다.
:global()로 감싸지 않는 한 CSS 모듈에 글로벌 클래스를 배치해서는 안된다.
그리고 실제로 CSS 모듈에 없는 구성 요소의 로컬 클래스를 참조해서는 안된다.

Trap 1: an unexpected global

Consider this: cx('something-local').
If that class name isn't defined in your CSS module (remember our example CSS file above), then an unmangled global classname will be applied instead. Meaning, your <div className={cx('something-local')} /> will be rendered as <div class="something-local" />. If you were expecting <div class="__mangled__something-local" /> it could lead to unexpected style errors, especially if something-local is defined in some hidden corner of your global CSS.
Pretend your application includes Twitter Bootstrap CSS. In that case, there will be a .container class globally available. Imagine that you thought that you had a container class in your CSS module. You might be surprised when the global container CSS gets applied. In the example below, mentally replace .something-local with .container.
이것을 고려해보자: cx('something-local').
클래스 이름이 CSS 모듈에 정의되지 않은 경우(위의 예제 CSS 파일을 기억하십시오), 얽히지 않은 글로벌 클래스 이름이 대신 적용됩니다. 즉, <div className={cx('something-local')} />가 <div class="something-local" /]로 렌더링됩니다. 특히 글로벌 CSS의 숨겨진 모서리에 something-local이 정의된 경우 <div class="__controlled_something-local" /> 스타일 오류가 발생할 수 있습니다.
당신의 어플리케이션이 트위터 부트스트랩 CSS를 포함하고 있다고 가정해보세요. 이 경우 .container 클래스가 전체적으로 제공됩니다. 당신이 당신의 CSS 모듈에 컨테이너 클래스가 있다고 생각한다고 상상해 보세요. 글로벌 컨테이너 CSS가 적용되면 놀라실 수 있습니다. 아래의 예에서, 정신적으로 .local을 .container로 대체한다.
// Trap 1: an unexpected global import classnames from 'classnames/bind'; import styles from './styles.css'; // whoops! something-local is undefined styles['something-local']; // --> undefined const cx = classname.bind(styles); // perhaps unepectedly, outputs a *global* class reference cx('something-local'); // --> "something-local" not "__mangled__something-local"
TypeScript
복사
Note: you need to use your imagination here. This is very likely to show up after a refactor, when a class name declaration was removed from the CSS file but not from the component. In that case, the class name will no longer exist in the styles import. If, by random chance, that old class name is defined in some globally included CSS that you are unaware of, you might accidentally run into a class name collision.
참고: 여기서는 상상력을 사용해야 합니다. 이것은 클래스 이름 선언이 CSS 파일에서 제거되었지만 구성 요소에서는 제거되지 않았을 때 리팩터 뒤에 나타날 가능성이 매우 높다. 이 경우 클래스 이름은 스타일 가져오기에 더 이상 존재하지 않습니다. 우연히 이전 클래스 이름이 사용자가 알지 못하는 전역적으로 포함된 일부 CSS에 정의된 경우 클래스 이름 충돌이 발생할 수 있습니다.

Trap 2: an unexpected local

Also, consider this: cx('something-global').
If that class name is defined in your CSS module (again, remember our example CSS file above), then a mangled classname will be used instead! This could also lead to unexpected style errors. Meaning, your <div className={cx('something-global')} /> will be rendered as <div class="__mangled__something-global" />. If you were expecting <div class="something-global" /> it could lead to unexpected style errors, especially if something-global is designed to be included from your global CSS.
Again, pretend your application includes Twitter Bootstrap CSS, and you want to use the global .container class. Imagine that someone has, unexpectedly, added a .container class to your CSS module. You might be surprised when the global styles aren't applied. In the example below, mentally replace .something-global with .container.
또한, 이것을 고려하라: 'cx('something-global)').
해당 클래스 이름이 CSS 모듈에 정의된 **인 경우(위의 예제 CSS 파일 기억), *망글드* 클래스 이름이 대신 사용됩니다! 이는 또한 예상치 못한 스타일 오류로 이어질 수 있다. 즉, '<div className={cx('something-global')} />'이(가) '<div class='__cled__something-global'로 렌더링됩니다.' 만약 당신이 '<div class='something-global' /'을 기대했다면 예상치 못한 스타일 오류가 발생할 수 있으며, 특히 'something-global'이 글로벌 CSS에서 포함되도록 설계된 경우 더욱 그러하다.
다시 말해, 당신의 응용 프로그램에 Twitter Bootstrap CSS가 포함되어 있다고 가정하고, 글로벌 '[.brap]'(https://github.com/twbs/bootstrap/blob/1b76b3cf2f8b60800ab068764ba926026899dafd/dist/css/bootstrap-grid.css#L22-L28)) 클래스를 사용하고자 한다. 누군가가 예기치 않게 당신의 CSS 모듈에 '.container' 클래스를 추가했다고 상상해 보세요. 글로벌한 스타일이 적용되지 않으면 깜짝 놀랄 수 있습니다. 아래의 예에서, 정신적으로 '.global'을 '.container'로 대체한다.
// Trap 2: an unexpected local import classnames from 'classnames/bind'; import styles from './styles.css'; // whoops! something-global is actually local styles['something-global']; // --> __mangled__something-global const cx = classname.bind(styles); // perhaps unepectedly, outputs a *local* class reference cx('something-global'); // --> "__mangled__something-global" not "something-global"
TypeScript
복사

How cx works

Traps aside, it's important to look deeper into how the bound cx function works. Perhaps understanding the inner workings better will make the traps more apparent and easier to avoid.
함정은 차치하고, 바인딩된 cx 함수가 어떻게 작동하는지 더 깊이 들여다보는 것이 중요하다. 아마도 내면의 일을 더 잘 이해하는 것은 함정을 더 분명하고 피하기 쉽게 만들 것이다.

Toy version of classnames.bind

First thing's first... the bound version of classnames isn't magic. It's a bound selector. To demonstrate the key functionality, consider this toy example where we're creating our own cx function from scratch.
You can see below some examples of what classnamescx and cxFake return respectively. You can also see that nothing prevents you from referencing the styles object directly. It's worth noting that the classnames func imported from 'classnames/bind' works exactly the same as importing it from 'classnames' (except it now has a bind method).
클래스 이름의 장난감 버전입니다.제본하다
첫번째는... 클래스 이름의 바인딩 버전은 마법이 아닙니다. 묶인 선택기야 주요 기능을 설명하기 위해, 처음부터 우리만의 cx 함수를 만드는 장난감 예를 고려해 보십시오.
아래에서 class names, cx, cxFake가 각각 반환하는 몇 가지 예를 볼 수 있습니다. 또한 스타일 객체를 직접 참조할 수 있는 방법은 없습니다. 'classnames/bind'에서 가져온 classnames func는 'classnames'에서 가져오는 것과 정확히 동일하게 작동합니다(현재 바인딩 메서드가 있다는 점은 제외).
import { get } from 'lodash'; import classnames from 'classnames/bind'; import styles from './styles.css'; // attempt to read the value from the styles object, return the key by default const fakeBind = (styles) => (key) => get(styles, key, key); // <-- a curried selector const cx = classnames.bind(styles); const cxFake = fakeBind(styles); // <-- "binding" is another name for currying // regular classnames('global-style') // --> global-style classnames('something') // --> something (global) classnames(styles.something) // --> __mangled__something (local) // bound cx('global-style') // --> global-style cx('something') // --> __mangled__something (local) cx(styles.something) // --> __mangled__something (oh, cool) // fake bound cxFake('global-style') // --> global-style cxFake('something') // --> __mangled__something (local) cxFake(styles.something) // --> __mangled__something
TypeScript
복사
Looking deeper at the fakeBind function, we can see that it is simply returning a property from the styles object. In cases where that property isn't present on the styles object, it simply returns the original string. Nothing could be simpler!
fakeBind 함수를 더 깊이 들여다보면 단순히 스타일 객체에서 속성을 반환하는 것을 알 수 있습니다. 스타일 객체에 해당 속성이 없는 경우 원래 문자열만 반환합니다. 이보다 간단할 순 없어!