来自Max Stoiber的一次演讲,摘要如下:我们现在处于一个“组件化”的时代,组件——是一个非常棒的概念,它非常适合于构建用户界面。通过组件,我们只需要了解一些很小的片段就可以理解整个系统,而不是千万行的模板。而组件化也有它的最佳实践:
其中最重要的一点就是将组件实现的细节与整体隔离开,类似下面这样:
// 不要这样
// 我们只需要关心组件是什么,而不需要知道它是怎么实现的
简单的来说:容器负责“逻辑”,组件负责“展现”
class SidebarContainer extends Comment {
componentDidMount() {
fetch('api.com/sidebar')
.then(res => {
this.setState({
items: res.items
})
});
}
render() {
return (
{this.state.items.map(item => (
{item}
))}
)
}
}
// 容器与组件分离
class Sidebar extends Comment {
componentDidMount() {
fetch('api.com/sidebar')
.then(res => {
this.setState({
items: res.items
})
});
}
render() {
return (
{this.state.items.map(item => (
{item}
))}
)
}
}
说完了组件,另一个方面就是给组件添加样式(Styling)了,怎么样更好的去给组件添加样式呢?以下是一种最佳实践
“将button的class用在header上——仅仅因为它有恰当的background或者font-family什么的”会导致许多意料不到的问题(更普遍的情况是将一种样式既用于header,又用于button——也会导致同样的问题;或者更糟糕的,使用.button span这种选择器,会产生使代码更难维护——一旦在其他地方应用了button,所有其内的span都会被影响,不论你愿不愿意),一旦明确了这个class只用在某个组件,那么问题就很好定位了。
“不用grid这个Class,而使用Grid组件、Column组件”,就像Michael Chen说的:“如果你使用React,你就拥有了一种比CSS类名更强大的样式添加结构——组件”
但是,仅仅是知道最佳实践也没有用,这里有一个最大的问题——实践——尤其是DDL(Deadline,最后期限)的压迫,这让你根本无法使用最佳实践。这就是Max和Glen(CSS Modules的创造者之一)为什么创造了styled.components。我们“移除了从样式到组件的映射”,为什么——如果你只对每个class名字使用一次,那么为什么要使用这个名字?因为从本质上来说,class名字就是一种从样式到其他东西的映射。那么如果你只映射一次,为什么要这个映射?
注意,我们使用的是真正的CSS,就像所有CSS一样,你可以在其中使用选择器、媒体查询:
import styled from 'styled-components';
const ColorChanger = styled.section`
background: papayawhip;
> h2 {
color: palevioletred;
}
@media (min-width: 875px) {
background: mediumseagreen;
> h2 {
color: papayawhip;
}
}
`
(其他内容多是关于用法的,故省略)
npm install --save styled-components
如果你不使用模块打包工具或者包管理器,我们提供了一个托管在unpkg CDN上的全局(“UMD”)版本,在HTML文件的底部加入就行:
或者使用bower下载:
bower install styled-components=https://unpkg.com/styled-components/dist/styled-components.min.js
添加完之后,你就可以在访问到一个window.styled的全局变量。
1)创建组件
styled.components使用模板字面量去给组件添加样式。同时,因为它移除了组件和样式之间的映射,所以当你定义样式的时候,你实际上是创建了一个React组件,并将样式附给了它:
// Create a Title component that'll render an tag with some styles
const Title = styled.h1`
font-size: 1。5em;
text-align: center;
color: palevioletred;
`;
// Create a Wrapper component that'll render a tag with some styles
const Wrapper = styled.section`
padding: 4em;
background: papayawhip;
`;
// Use Title and Wrapper like any other React component – except they're styled!
render(
Hello World, this is my first styled component!
);
2)传递props
// Create an Input component that'll render an tag with some styles
const Input = styled.input`
padding: 0.5em;
margin: 0.5em;
color: palevioletred;
background: papayawhip;
border: none;
border-radius: 3px;
`;
// Render a styled text input with a placeholder of "@mxstbr", and one with a value of "@geelen"
render(
);
3)通过props做适配
你可以将一个函数传入styled组件的模板字符串中,从而通过props为组件做适配:
const Button = styled.button`
/* Adapt the colours based on primary prop */
background: ${props => props.primary ? 'palevioletred' : 'white'};
color: ${props => props.primary ? 'white' : 'palevioletred'};
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border: 2px solid palevioletred;
border-radius: 3px;
`;
render(
);
4)为任意组件添加样式
styled方法对所有你自己的、第三方的组件都工作的很好——这些组件都会将className这个prop传入渲染的子组件,styled方法的工作原理和他们一样。最终,为了使样式生效,这个className必须被传入真实的DOM节点。
// This could be react-router's Link for example
const Link = ({ className, children }) => (
{children}
)
const StyledLink = styled(Link)`
color: palevioletred;
font-weight: bold;
`;
render(
Unstyled, boring Link
Styled, exciting Link
);
当并不必要的时候,仔细考虑一下是否要将你自己的组件包裹在styled组件中——自将会关闭props的自动白名单(?),并且背离了将样式组件和容器组件分离的建议。
你也可以将一个标签传入sytled(),就像styled('div'),实际上,styled.components这种语法只是styled()方法的别名而已。(styled-components总是通过class生成一个真实的样式表,这种样式名会通过className prop传入React组件中-包括第三方组件)。
5)扩展样式
经常情况下,你可能想要使用一个组件——但是需要对原来做出一些小小的改变。现在你可以将它传入一个插值函数中,通过props改变这个组件(覆盖这个样式需要费一番功夫?)。最简单的方法如下:
// The Button from the last section without the interpolations
const Button = styled.button`
color: palevioletred;
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border: 2px solid palevioletred;
border-radius: 3px;
`;
// We're extending Button with some extra styles
const TomatoButton = Button.extend`
color: tomato;
border-color: tomato;
`;
render(
Tomato Button
);
注意:你只能在明确知道组件是一个“样式组件”的使用使用Comp.extend语法,如果你的组件来自于其他文件、或者第三方库,最好使用styled(Comp)——可以完成相同的功能,不过可以作用于所有React组件。在这里,你可以了解到关于Comp.extend和styled(Comp)区别的更多信息。
在非常罕见的情况下,你想要改变styled.components渲染的是哪个标签或者是组件(比如想要对一个应用一个
const Button = styled.button`
display: inline-block;
color: palevioletred;
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border: 2px solid palevioletred;
border-radius: 3px;
`;
// We're replacing the
6)加入额外的props
为了避免向一个渲染的组件、元素加入一些props可能发生的不必要嵌套,你可以使用.attr构造器——它允许你为组件附加额外的props。下面演示了这种方法:你可以向元素加入静态的props、加入第三方的prop(比如向React Router的Link组件中加入‘activeClassName’)、甚至加入更动态的props。.attrs对象也可以接受方法——方法接受组件的props,返回值同样也会被并入结果props中。
const Input = styled.input.attrs({
// we can define static props
type: 'password',
// or we can define dynamic ones
margin: props => props.size || '1em',
padding: props => props.size || '1em'
})`
color: palevioletred;
font-size: 1em;
border: 2px solid palevioletred;
border-radius: 3px;
/* here we use the dynamically computed props */
margin: ${props => props.margin};
padding: ${props => props.padding};
`;
render(
);
7)动画
@keyframes的CSS动画并不属于一个单一组件的范围,但是你又不想他们成为全局的,这就是我们为什么提供了一个keyframes助手——它会为keyframe自动生成一个独一无二的名字,你可以在整个APP范围中使用这个名字。在这种情况下,你获得了使用JavaScript的便捷性,并且避免了名称冲突!
// keyframes returns a unique name based on a hash of the contents of the keyframes
const rotate360 = keyframes`
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
`;
// Here we create a component that will rotate everything we pass in over two seconds
const Rotate = styled.div`
display: inline-block;
animation: ${rotate360} 2s linear infinite;
padding: 2rem 1rem;
font-size: 1.2rem;
`;
render(
<