styled-components ก็คือ library สำหรับใช้ css ใน react อีกรูปแบบหนึ่ง ก่อนหน้าที่จะลองเปลี่ยนมาใช้ library นี้ผมได้ใช้ material-ui version 4 มาก่อนซึ่งจะเป็นรูปแบบของ CSS-in-JS ซึ่งจะเจอประเด็นเรื่องการทำ dynamic styles ที่ลำบากพอสมควรและพอดีได้มาเจอและอ่าน document ของ styled-components คร่าว ๆ แล้วเข้าไปเจอ motivation ที่เขียนไว้ว่า “optimize the experience for developers” จึงตัดสินใจลองใช้ดู
หลังจากเริ่มใช้ styled-components มาสักพักก็เป็นเวลาอันสมควรเลยจะขอจดบันทึกและแชร์เทคนิคไว้หน่อยประมาณนี้
Conditional rendering
เมื่อเราต้องการสร้าง component ที่ dynamic ขึ้นมาหน่อยเราสามารถ ใช้วิธีที่เรียกว่า “interpolation” หรือการแทรก function เข้าไป template literals
ตั้งแต่เขียน styled-components ผมต้องย้ายปุ่มเปลี่ยนภาษาไปเป็น alt+tab เพราะต้องใช้ grave ในการทำ styles
ซึ่งใน function นั้นเราจะสร้างเงื่อนไขและการคืนค่าออกมาเป็น string เพื่อใช้เป็น value ของ css property โดย props ที่เรา pass เข้ามาก็เปรียบเสมือน input
ตัวอย่างแรก เมื่อเราต้องการปุ่มที่เมื่อ selected
เป็น true
จะทำการเปลี่ยนสีของปุ่มเป็นสี green
const App = () => {
return (
<div>
<Item selected />
<Item />
</div>
)
}
const Item = styled.button`
color: ${props => (props.selected ? "green" : "white")};
`
ตัวอย่างที่สอง ถ้าเราต้องการ dynamic หลายๆ css property พร้อมกันละ 😄
import { css } from "styled-components"
const App = () => {
return <Typo noWrap>Text</Typo>
}
❌ ❌ ❌
const Typo = styled.p`
white-space: ${({ noWrap }) => noWrap && "nowrap"};
overflow: ${({ noWrap }) => noWrap && "hidden"};
text-overflow: ${({ noWrap }) => noWrap && "ellipsis"};
`
✅ ✅ ✅
const Typo = styled.p`
${({ noWrap }) =>
noWrap &&
css`
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
`}
`
ccs tag ที่ครอบระหว่าง template literals อาจจะไม่จำเป็นในบางกรณี เช่นในตัวอย่างไม่ต้องมีก็ได้สามารถศึกษาเพิ่มได้ตาม Link
Theming
การที่จะทำ styles ให้มีความเป็นระเบียบ มี pattern จำเป็นอย่างยิ่งที่จะต้องทำระบบ theme ขึ้นมา เช่น การกำหนด primary color, spacing หรือ font-size เป็นต้น ซึ่งใน styled-components ก็สามารถทำ theme ได้เช่นเดียวกับ tool ตัวอื่น ๆ และทำได้ง่ายมาก
- วาง
ThemeProvider
ที่ top สุดของ component (ถ้าเคยใช้ context จะคุ้นเคยกับท่านี้) - สร้าง theme object และ pass เข้าไปที่
ThemeProvider
- component ที่อยู่ภายใต้ provider นั้นสามารถเข้าถึง theme object ผ่านท่า interpolation ได้เลยตามตัวอย่าง
import styled from "styled-components"
import { ThemeProvider } from "styled-components"
const theme = {
colors: {
primary: "blue",
secondary: "green",
},
}
const App = () => {
return (
<ThemeProvider theme={theme}>
<Button>Label</Button>
</ThemeProvider>
)
}
const Button = styled.button`
background-color: ${({ theme }) => theme.colors.primary};
`
การทำ theme ใน styled-components นั้นไม่ยากแต่สิ่งที่ยากคือเราต้องกำหนดค่าอะไรบ้างใน theme ของเราเพื่อให้ในแต่ละ component มี design ที่สอดคล้องกัน (ผมลองอ้างอิงจาก lib เพื่อนบ้านอย่าง material-UI)
การสร้าง component ใหม่จาก component เดิมง่าย ๆ
ในการสร้าง component หนึ่งให้ dynamic และสามารถ reuse ได้ก็เป็นเรื่องที่ดีตามหลักของ clean code แต่ถ้าถึงจุดหนึ่งและมีความรู้สึกว่าการทำ reusable component ทำให้ logic ข้างในซับซ้อนเกินไป ผมก็เลือกที่จะสร้าง component ใหม่โดย inherit styles จาก component เดิมไปสร้างเป็น component ใหม่และเติมสิ่งที่เราต้องการเปลี่ยนแปลงเข้าไปก็เป็นอีกวิธีหนึ่งที่ดีเพื่อลดความซับซ้อนและยังเป็น reusable component อยู่
const App = () => {
return (
<>
<Button>Label</Button>
<SuperButton>Label</SuperButton>
</>
)
}
const Button = styled.button`
background-color: blue;
`
const SuperButton = styled(Button)`
width: 120px;
height: 80px;
background-color: yellow;
`
การ extend styles มาสร้าง component มีประโยชน์ในหลายสถานการณ์ เช่น เราต้องการเพิ่มเติมความสามารถให้ component ที่คนอื่นสร้างไว้และไม่อยากไปแก้ไข code เดิมก็ใช้วิธีนี้เข้าไปช่วยได้
💡 ในบ้างครั้งการกลับไป re-design component เดิม ก็เข้าท่ากว่านะ
“className” prop is important
ถ้าสรุปการสร้าง component จาก styled-components จะมี 3 วิธีหลัก ๆ คือ
- สร้างจาก html tagname
styled.div
- สร้างโดยการ extend จาก styled function
styled(Button)
- สร้างจาก other component ตามตัวอย่างด้านล่าง
วิธีการนี้จะแตกต่างจากการ extend styles ตรง ๆ จากหัวข้อก่อนหน้านี้เล็กน้อย
import styled from "styled-components"
const StyledButton = ({ className }) => {
return <Button className={className}>Label</Button>
}
const Button = styled.button`
background-color: blue;
`
วิธีการสร้าง component แบบนี้จะเกิดบ่อยที่สุด จะเห็นว่าเราสร้าง StyledButton
component จากนั้นจะถูกนำไปใช้ใน Area
component โดยเราต้องการเพิ่มสีเข้าไปใน StyledButton
ด้วยโดยที่ไม่ต้องการให้แก้ไขใน component เดิม
const Area = styled(StyledButton)`
width: 120px;
height: 80px;
background-color: yellow;
`
const App = () => {
return (
<>
<StyledButton>Label</StyledButton>
<Action>Label</Action>
</>
)
}
📌 สิ่งสำคัญคือห้ามลืมใส่ className props ที่ StyledButton
(ตอนครั้งแรกผมติดตรงนี้สักพักเลยเพราะลืมใส่)
“as” prop
ปกติแล้วเมื่อเราจะสร้าง component ข้างในก็จะมี HTML element เป็นส่วนประกอบ เช่น Button ก็จะมี <button />
หรือ textbox ก็จะมี <input />
แต่ถ้าเราต้องการ component อีกประเภทหนึ่งละ ที่ข้างในสามารถเปลี่ยน HTML element ได้ตาม condition เช่น <Typography />
ที่จะสามารถเปลี่ยนข้างในให้เป็น h1
, h2
, p
หรือ จะเป็น element อะไรก็ได้ที่เราต้องการเกี่ยวกับการ display text ตามจุดประสงค์ของการนำ component ไปใช้แต่ styles ข้างในจะยังคงใช้เหมือนกัน
คำตอบ คือใช้ "as"
เราจะสามารถสลับ HTML element ได้
import styled from "styled-components"
const Typo = styled.div`
color: red;
font-weight: bold;
`
const App = () => {
return (
<>
<Typo as="h1">Label</Typo>
<Typo as="h5">Label</Typo>
<Typo as="p">Label</Typo>
</>
)
}
ความหมายเพิ่มเติมของ “as” polymorphic prop
แถมอีกนิด
ถ้าใช้งาน vs code สามารถติดตั้ง extension ที่ชื่อ vscode-styled-components ช่วยในการ highlight syntax ทำให้ code เราสวยงามขึ้นและอ่านง่าย