考虑到 BEM,使用 'classnames' 的可扩展 React CSS
Scalable React CSS using 'classnames' with BEM in mind
那么,这里是 React 新手...我首先要说我有一个简单的单页应用程序,它由几个简单的页面组成。
使用 react-router 我为我的组件设置了 'top-down'。为了让您对我的 SPA 结构有一个基本的了解,请参见下文:
index -- layout(react routers) --
|--About Page
|--Home Page
|--Contact Page
我正在从我的主页组件中呈现一个名为 "GlobalHero" 的组件。
这里是GlobalHero.jsx组件。
import React from "react";
var classNames = require('classnames');
import s from '../../../index.scss';
class GlobalHero extends React.Component {
constructor() {
super();
//sets initial state
this.state = {
fadeIn: "",
titleSelected: "",
subTitleSelected: ""
};
}
// <<========= ON COMPONENT RENDER =========
componentDidMount = () => {
console.log("GlobalHero");
console.log(this.props);
this.handleClass("fadeIn");
}
// =========>>
// <<========= CONTROLS THE STATE =========
handleClass = (param) => {
if (param === "fadeIn" && this.state.fadeIn != "true") {
this.setState({fadeIn: "true"});
}
if (param === "titleSelected" && this.state.titleSelected != "true") {
this.setState({titleSelected: "true"});
}
if (param === "subTitleSelected" && this.state.subTitleSelected != "true") {
this.setState({subTitleSelected: "true"});
}
}
// =========>>
render() {
const heroImg = require(`../../../images/hero${this.props.page}.jpg`);
//REMOVES CLASS IN REALTIME BASED ON STATE'S VALUE =========
var containerClasses = classNames({
[s['text-center']]: true,
[s["hidden"]]: this.state.fadeIn != "true",
[s["fadeIn"]]: this.state.fadeIn === "true"
});
var titleClasses = classNames({
[s['blue']]: this.state.titleSelected === "true"
});
var subTitleClasses = classNames({
[s['subTitle']]: true,
[s['text-center']]: true,
[s['blue']]: this.state.subTitleSelected === "true"
});
// =========>>
return (
<div className={s["container-fluid"]}>
<div className={s["row"]}>
<div className={s["col-lg-16"]}>
<div className={containerClasses}>
<img src={heroImg} className={s["hero__img"]}></img>
<h1 onClick={() => this.handleClass("titleSelected")} className={titleClasses}>{this.props.page}!</h1>
<p className={subTitleClasses} onClick={() => this.handleClass("subTitleSelected")}>{this.props.name}, {this.props.age}, {this.props.city}</p>
</div>
</div>
</div>
</div>
);
}
}
export default GlobalHero;
我注意到为组件的元素分配一些简单的 class 名称非常复杂。
I was wondering if there is a better practice for doing this? Maybe
using an external js page to manage my classnames?
欢迎任何意见或建议...提前谢谢。
你的标题提到了BEM but it looks like you are using CSS Modules,它的灵感来自相似的想法,但不是一回事。
无论如何,这是相当主观的,但我有一些想法太多,无法发表评论:
假设您正在通过 Webpack 的 css-loader
使用 css 模块,您可以使用 camelCase
使您的样式属性对 JS 更友好:
loader: "css-loader?modules&camelCase"
现在对于 .text-center
css class 名称你可以简单地使用 s.textCenter
而不是 s["test-center"]
.
- 您可以更好地对其进行组件化:首先,您为单个组件做了很多工作,但您可以 break it down into a few smaller components that each have a single responsibility(例如容器、标题、副标题)。其次,您的
handleClass()
方法会做很多事情,而您可以只使用调用 setState()
的简单处理程序,而无需了解任何有关 class 名称的信息。换句话说,组件应该有道具和状态,只有 render()
函数处理如何将其转换为 class 名称以进行渲染。您也真的不需要在设置之前检查状态的当前值。只需将其设置为应有的值,让 React 为您优化渲染性能。
- 你有使用字符串
"true"
和 "false"
存储的布尔状态标志...这使得处理起来很吵,只存储为布尔值。
你有很多[s["class-name"]]: true
是没有必要的;如果你总是想要呈现一个 class 名称,只需将其作为参数传递给 classNames
:
classNames(s.subTitle, { [s.blue]: this.state.subTitleSelected })
- 没有理由在
componentDidMount
上调用处理程序,只需按您想要的方式初始化状态即可。
- 您似乎在使用 bootstrap CSS 而不是 React Bootstrap 组件。我强烈推荐使用 React Bootstrap.
把它们放在一起我会得到类似的东西:
class GlobalHero extends React.Component {
state = {
fadeIn: true,
titleSelected: false,
subTitleSelected: false
};
handleTitleClick = () => {
this.setState({titleSelected: true});
};
handleSubTitleClick = () => {
this.setState({subTitleSelected: true});
};
render() {
return (
<Grid fluid>
<Row>
<Col lg={16}>
<HeroContainer fadeIn={this.state.fadeIn}>
<HeroImage page={this.props.page} />
<HeroTitle selected={this.state.titleSelected}
onClick={this.handleTitleClick}
page={this.props.page} />
<HeroSubTitle selected={this.state.subTitleSelected}
onClick={this.handleSubTitleClick}
name={this.props.name}
age={this.props.age}
city={this.props.city} />
</HeroContainer>
</Col>
</Row>
</Grid>
);
}
}
const HeroContainer = ({fadeIn, children}) => {
return (
<div className={classNames(s.textCenter, fadeIn ? s.fadeIn : s.hidden)}>
{children}
</div>
);
};
const HeroImage = ({page}) => {
const heroImg = require(`../../../images/hero${page}.jpg`);
return (
<img src={heroImg} className={s.heroImg} />
);
};
const HeroTitle = ({onClick, selected, page}) => (
<h1 onClick={onClick} className={selected ? s.blue : null}>{page}!</h1>
);
const HeroSubTitle = ({onClick, selected, name, age, city}) => (
<p className={classNames(s.subTitle, s.textCenter, { [s.blue]: selected })} onClick={onClick}>
{name}, {age}, {city}
</p>
);
像这样把它分解成更小的组件并不是完全必要的,但请注意,从 GlobalHero
的角度来看,它对样式没有任何作用,它只是设置道具和状态,而小部分没有状态,他们只是根据道具呈现正确的样式。
PS 也许这应该移到 Code Reviews?
那么,这里是 React 新手...我首先要说我有一个简单的单页应用程序,它由几个简单的页面组成。
使用 react-router 我为我的组件设置了 'top-down'。为了让您对我的 SPA 结构有一个基本的了解,请参见下文:
index -- layout(react routers) --
|--About Page
|--Home Page
|--Contact Page
我正在从我的主页组件中呈现一个名为 "GlobalHero" 的组件。
这里是GlobalHero.jsx组件。
import React from "react";
var classNames = require('classnames');
import s from '../../../index.scss';
class GlobalHero extends React.Component {
constructor() {
super();
//sets initial state
this.state = {
fadeIn: "",
titleSelected: "",
subTitleSelected: ""
};
}
// <<========= ON COMPONENT RENDER =========
componentDidMount = () => {
console.log("GlobalHero");
console.log(this.props);
this.handleClass("fadeIn");
}
// =========>>
// <<========= CONTROLS THE STATE =========
handleClass = (param) => {
if (param === "fadeIn" && this.state.fadeIn != "true") {
this.setState({fadeIn: "true"});
}
if (param === "titleSelected" && this.state.titleSelected != "true") {
this.setState({titleSelected: "true"});
}
if (param === "subTitleSelected" && this.state.subTitleSelected != "true") {
this.setState({subTitleSelected: "true"});
}
}
// =========>>
render() {
const heroImg = require(`../../../images/hero${this.props.page}.jpg`);
//REMOVES CLASS IN REALTIME BASED ON STATE'S VALUE =========
var containerClasses = classNames({
[s['text-center']]: true,
[s["hidden"]]: this.state.fadeIn != "true",
[s["fadeIn"]]: this.state.fadeIn === "true"
});
var titleClasses = classNames({
[s['blue']]: this.state.titleSelected === "true"
});
var subTitleClasses = classNames({
[s['subTitle']]: true,
[s['text-center']]: true,
[s['blue']]: this.state.subTitleSelected === "true"
});
// =========>>
return (
<div className={s["container-fluid"]}>
<div className={s["row"]}>
<div className={s["col-lg-16"]}>
<div className={containerClasses}>
<img src={heroImg} className={s["hero__img"]}></img>
<h1 onClick={() => this.handleClass("titleSelected")} className={titleClasses}>{this.props.page}!</h1>
<p className={subTitleClasses} onClick={() => this.handleClass("subTitleSelected")}>{this.props.name}, {this.props.age}, {this.props.city}</p>
</div>
</div>
</div>
</div>
);
}
}
export default GlobalHero;
我注意到为组件的元素分配一些简单的 class 名称非常复杂。
I was wondering if there is a better practice for doing this? Maybe using an external js page to manage my classnames?
欢迎任何意见或建议...提前谢谢。
你的标题提到了BEM but it looks like you are using CSS Modules,它的灵感来自相似的想法,但不是一回事。
无论如何,这是相当主观的,但我有一些想法太多,无法发表评论:
假设您正在通过 Webpack 的
css-loader
使用 css 模块,您可以使用camelCase
使您的样式属性对 JS 更友好:loader: "css-loader?modules&camelCase"
现在对于
.text-center
css class 名称你可以简单地使用s.textCenter
而不是s["test-center"]
.- 您可以更好地对其进行组件化:首先,您为单个组件做了很多工作,但您可以 break it down into a few smaller components that each have a single responsibility(例如容器、标题、副标题)。其次,您的
handleClass()
方法会做很多事情,而您可以只使用调用setState()
的简单处理程序,而无需了解任何有关 class 名称的信息。换句话说,组件应该有道具和状态,只有render()
函数处理如何将其转换为 class 名称以进行渲染。您也真的不需要在设置之前检查状态的当前值。只需将其设置为应有的值,让 React 为您优化渲染性能。 - 你有使用字符串
"true"
和"false"
存储的布尔状态标志...这使得处理起来很吵,只存储为布尔值。 你有很多
[s["class-name"]]: true
是没有必要的;如果你总是想要呈现一个 class 名称,只需将其作为参数传递给classNames
:classNames(s.subTitle, { [s.blue]: this.state.subTitleSelected })
- 没有理由在
componentDidMount
上调用处理程序,只需按您想要的方式初始化状态即可。 - 您似乎在使用 bootstrap CSS 而不是 React Bootstrap 组件。我强烈推荐使用 React Bootstrap.
把它们放在一起我会得到类似的东西:
class GlobalHero extends React.Component {
state = {
fadeIn: true,
titleSelected: false,
subTitleSelected: false
};
handleTitleClick = () => {
this.setState({titleSelected: true});
};
handleSubTitleClick = () => {
this.setState({subTitleSelected: true});
};
render() {
return (
<Grid fluid>
<Row>
<Col lg={16}>
<HeroContainer fadeIn={this.state.fadeIn}>
<HeroImage page={this.props.page} />
<HeroTitle selected={this.state.titleSelected}
onClick={this.handleTitleClick}
page={this.props.page} />
<HeroSubTitle selected={this.state.subTitleSelected}
onClick={this.handleSubTitleClick}
name={this.props.name}
age={this.props.age}
city={this.props.city} />
</HeroContainer>
</Col>
</Row>
</Grid>
);
}
}
const HeroContainer = ({fadeIn, children}) => {
return (
<div className={classNames(s.textCenter, fadeIn ? s.fadeIn : s.hidden)}>
{children}
</div>
);
};
const HeroImage = ({page}) => {
const heroImg = require(`../../../images/hero${page}.jpg`);
return (
<img src={heroImg} className={s.heroImg} />
);
};
const HeroTitle = ({onClick, selected, page}) => (
<h1 onClick={onClick} className={selected ? s.blue : null}>{page}!</h1>
);
const HeroSubTitle = ({onClick, selected, name, age, city}) => (
<p className={classNames(s.subTitle, s.textCenter, { [s.blue]: selected })} onClick={onClick}>
{name}, {age}, {city}
</p>
);
像这样把它分解成更小的组件并不是完全必要的,但请注意,从 GlobalHero
的角度来看,它对样式没有任何作用,它只是设置道具和状态,而小部分没有状态,他们只是根据道具呈现正确的样式。
PS 也许这应该移到 Code Reviews?