前言
最近在搞tsx,迫于tsx的特性,页面中无法直接使用js定义伪类,加之使用 tailwindcss 已经满足大部分css需求,不愿为一两句伪类代码徒增引入文件,于是上手 styled-components 。
css in js
css in js 并不是指一个具体的类库,而是指一种使用js来写css的理念。在以前web开发有着关注点分离原则,也就是各种技术互相分离,不要耦合在一起。但这一理念在由react带来的组件化潮流中被打破。每个组件包含了所有需要用到的代码,不依赖外部,组件之间没有耦合,很方便复用。而这种关注点混合的写法也逐渐成为主流。
styled-components
虽然 css in js 使一个组件更加的一体化,减少了外部文件的依赖耦合。但是由react所提供的语法依然存在很多问题,除了上文所提到的伪类问题之外,还有诸如样式作用域问题、其通过style实现所带来的性能问题等。
为此有很多开发者封装了更加易用的库,我们今天的主角 styled-components 也正是这些类库中关注度和热度最高的一个。
安装
三者仅类库不同,使用方式一致
react
yarn add styled-components
import styled from 'styled-components';
vue2
yarn add vue-styled-components
import styled from 'vue-styled-components';
vue3
yarn add vue3-styled-components
import styled from 'vue3-styled-components';
使用
基础使用
const Wrapper = styled.div`
height: 200px;
width: 400px;
`;
render(<>
<Wrapper>Hello World</Wrapper>
</>)
解决伪类问题
/* 类似于sass的写法 */
const Wrapper = styled.div`
height: 200px;
width: 400px;
&:hover{
border: 1px solid #777;
}
`;
render(<>
<Wrapper>Hello World</Wrapper>
</>)
变量
/* 需要传入函数 */
const height = "400px"
const Wrapper = styled.div`
height: ${() => height};
width: 400px;
`;
render(<>
<Wrapper>Hello World</Wrapper>
</>)
引用图片
/* 在样式组件中直接使用url()的方法引入图片的话,会出现路径的问题 */
/* 需要先用import引入图片,再用插值表达式的形式插入样式字符串中 */
import logo from './logo.png';
const Logo = styled.div`
background: url(${() => logo})
`
render(<>
<Logo />
</>)
传入Props
/* 插值函数会携带props的值 */
const Wrapper = styled.div`
color: ${(props) => props.color ? props.color : 'red'};
`;
render(<>
<Wrapper color="#000">Hello World</Wrapper>
</>)
定义css样式复用
/* 通过定义css变量可以复用一些样式 */
const disabledStyle = css`
background: transparent;
color: rgba(0, 0, 0, 0.38);
border: 2px solid rgba(0, 0, 0, 0.38);
`;
const Wrapper = styled.div`
${() => disabledStyle};
`;
render(<>
<Wrapper>Hello World</Wrapper>
</>)
定义子组件样式
const Wrapper = styled.div`
/* 方式一 通过寻找子标签 */
> h1 {
color: red
}
/* 方式二 通过寻找子组件名 */
${H1} {
color: red
}
`;
render(<>
<Wrapper>
<H1>Hello World</H1>
</Wrapper>
</>)
继承样式
/* 通过继承的方式,在不改变原组件样式的情况下对原组件样式扩展调整为新组件 */
const Button = styled.button`
font-size: 14px;
margin: 8px;
padding: 8px;
`
const BlueButton = styled(Button)`
color: blue;
border: 2px solid blue;
`;
render(<>
<Button>Hello World</Button>
<BlueButton>Hello World</BlueButton>
</>)
修改其它组件样式
/* 不推荐使用,直接修改其它组件的样式会影响该组件的耦合性,不易维护 */
const StyledA = styled.a`
font-size: 20px
`
/* 鼠标悬停后,超链接文字变为红色 */
export const Button = styled.button`
&:hover + ${StyledA}{
color: red;
}
`
render(<>
<Button>Hello World</Button>
<StyledA>Hello World</StyledA>
</>)
自定义组件样式
/* 我们也可以扩展普通组件的样式 */
const Button = ({ className, children }) => <button className={className}>{children}</button>;
const CustomButton = styled(Button)`
color: blue;
font-size: 32px;
`;
render(<>
<Button>Hello World</Button>
<CustomButton>Hello World</CustomButton>
</>)
自定义属性值
/*
我们可以使用attrsAPI 来为样式组件添加一些默认属性值
*/
/*
它们可以通过标签模板插值函数拿到 props 传值,在样式中也可以访问到这部分的props传值。
*/
const PasswordInput = styled.input.attrs({
type: 'password',
margin: (props) => props.size || '1em',
padding: (props) => props.size || '1em',
})`
font-size: 1em;
margin: ${(props) => props.margin};
padding: ${(props) => props.padding};
`;
render(<>
<PasswordInput placeholder="请输入密码" size="0.25rem" />
</>)
优先级
/* 使用 && 可以提升某一段样式的优先级,其实现方式是通过对类名的重复 */
const Wrapper = styled.div`
height: 200px;
width: 400px;
color: blue;
&& {
/* 这里的color优先级更高 */
color: red;
}
`;
render(<>
<Wrapper>Hello World</Wrapper>
</>)
结合tailwindcss
由react带来的 css in js 的模式,由于js的参与拥有了更高的灵活性和复用性,加上一些开发者们完善的类库也大大提升了其在处理复杂情况的能力。
不过也正是由于js直接操作css,而js的逻辑代码与css样式又互相混合难以梳理,极大的影响了一个组件的可读性,你在阅读一段代码时需要跳过很多你不关心的样式部分,而若将css样式抽离又会徒增一个组件的外部依赖,降低该组件的可维护性。
个人认为在css的处理方面,vue的模板将html、js、css分为三部分互相独立的写法会更好。
推荐结合tailwindcss,也就是原子css可以极大的减少一个组件内的样式数量,使开发者更专注于html的编写和js的逻辑上,遇到复杂样式再通过 styled-components 编写,提升代码的简洁度。
tailwindcss例:
const Wrapper = <div class="px-20 h-full flex items-center hover:bg-white-light">
Hello World
</div>