Styled-Components: ไลบรารี CSS-in-JS สำหรับ Modern Web
เผยแพร่แล้ว: 2022-03-11CSS ได้รับการออกแบบมาสำหรับเอกสาร ซึ่งคาดว่า "เว็บเก่า" จะมีอยู่ การเกิดขึ้นของตัวประมวลผลล่วงหน้า เช่น Sass หรือ Less แสดงให้เห็นว่าชุมชนต้องการมากกว่าที่ CSS เสนอ เนื่องจากเว็บแอปมีความซับซ้อนมากขึ้นเรื่อยๆ ข้อจำกัดของ CSS จึงมองเห็นได้ชัดเจนขึ้นเรื่อยๆ และบรรเทาได้ยาก
Styled-components ใช้ประโยชน์จากพลังของภาษาการเขียนโปรแกรมที่สมบูรณ์—JavaScript—และความสามารถในการกำหนดขอบเขตเพื่อช่วยจัดโครงสร้างโค้ดให้เป็นส่วนประกอบ ซึ่งจะช่วยหลีกเลี่ยงข้อผิดพลาดทั่วไปในการเขียนและดูแล CSS สำหรับโครงการขนาดใหญ่ นักพัฒนาสามารถอธิบายรูปแบบของส่วนประกอบได้โดยไม่มีความเสี่ยงจากผลข้างเคียง
มีปัญหาอะไร?
ข้อดีอย่างหนึ่งของการใช้ CSS คือสไตล์นั้นแยกออกจากโค้ดโดยสิ้นเชิง ซึ่งหมายความว่านักพัฒนาและนักออกแบบสามารถทำงานคู่ขนานกันโดยไม่รบกวนซึ่งกันและกัน
ในทางกลับกัน องค์ประกอบที่มีสไตล์ ทำให้ง่ายต่อการตกหลุมพรางของรูปแบบและตรรกะการมีเพศสัมพันธ์ที่แข็งแกร่ง Max Stoiber อธิบายวิธีหลีกเลี่ยงสิ่งนี้ แม้ว่าแนวคิดในการแยกตรรกะและการนำเสนอจะไม่ใช่เรื่องใหม่ แต่อาจมีคนพยายามใช้ทางลัดเมื่อพัฒนาส่วนประกอบ React ตัวอย่างเช่น ง่ายต่อการสร้างส่วนประกอบสำหรับปุ่มตรวจสอบความถูกต้องที่จัดการการคลิกตลอดจนลักษณะของปุ่ม ต้องใช้ความพยายามอีกเล็กน้อยในการแยกออกเป็นสองส่วน
สถาปัตยกรรมคอนเทนเนอร์/การนำเสนอ
นี่เป็นหลักการที่ค่อนข้างง่าย ส่วนประกอบกำหนดลักษณะของสิ่งต่าง ๆ หรือจัดการข้อมูลและตรรกะ องค์ประกอบการนำเสนอที่สำคัญมากคือไม่ควรมีการพึ่งพาใดๆ พวกเขาได้รับอุปกรณ์ประกอบฉากและแสดงผล DOM (หรือลูก) ตามลำดับ ในทางกลับกัน คอนเทนเนอร์รู้เกี่ยวกับสถาปัตยกรรมข้อมูล (สถานะ รีดอกซ์ ฟลักซ์ ฯลฯ) แต่ไม่ควรรับผิดชอบในการแสดงผล บทความของ Dan Abramov เป็นคำอธิบายที่ดีและละเอียดมากเกี่ยวกับสถาปัตยกรรมนี้
รำลึกถึง SMACSS
แม้ว่าสถาปัตยกรรมที่ปรับขนาดได้และโมดูลาร์สำหรับ CSS จะเป็นคู่มือสไตล์สำหรับการจัดระเบียบ CSS แต่แนวคิดพื้นฐานก็คือแนวคิดที่ตามมาโดยส่วนใหญ่โดยอัตโนมัติตามองค์ประกอบที่มีสไตล์ แนวคิดคือการแยก CSS ออกเป็นห้าหมวดหมู่:
- ฐาน ประกอบด้วยกฎทั่วไปทั้งหมด
- เลย์ เอาต์มีจุดประสงค์เพื่อกำหนดคุณสมบัติโครงสร้างตลอดจนส่วนต่างๆ ของเนื้อหา (ส่วนหัว ส่วนท้าย แถบด้านข้าง เนื้อหา เป็นต้น)
- โมดูล ประกอบด้วยหมวดหมู่ย่อยสำหรับบล็อกตรรกะต่างๆ ของ UI
- สถานะ กำหนดคลาสตัวดัดแปลงเพื่อระบุสถานะขององค์ประกอบ เช่น ฟิลด์มีข้อผิดพลาด ปุ่มปิดการใช้งาน
- ธีม ประกอบด้วยสี แบบอักษร และรูปลักษณ์อื่นๆ ที่อาจปรับเปลี่ยนได้หรือขึ้นอยู่กับความชอบของผู้ใช้
การแยกส่วนนี้ในขณะที่ใช้ องค์ประกอบที่มีสไตล์ นั้นเป็นเรื่องง่าย โปรเจ็กต์มักจะรวมถึงการทำให้เป็นมาตรฐานหรือรีเซ็ต CSS บางประเภท โดยทั่วไปแล้วจะอยู่ในหมวด ฐาน คุณยังสามารถกำหนดขนาดฟอนต์ทั่วไป ขนาดบรรทัด ฯลฯ ซึ่งสามารถทำได้ผ่าน CSS ปกติ (หรือ Sass/Less) หรือผ่านฟังก์ชัน injectGlobal
ที่จัดเตรียมโดย styled-components
สำหรับกฎ เล ย์เอาต์ ถ้าคุณใช้เฟรมเวิร์ก UI มันอาจจะกำหนดคลาสคอนเทนเนอร์หรือระบบกริด คุณสามารถใช้คลาสเหล่านั้นร่วมกับกฎของคุณเองได้อย่างง่ายดายในส่วนประกอบเลย์เอาต์ที่คุณเขียน
โมดูล จะตามมาโดยอัตโนมัติด้วยสถาปัตยกรรมของ ส่วนประกอบ ที่มีสไตล์ เนื่องจากสไตล์นั้นแนบมากับส่วนประกอบโดยตรง แทนที่จะอธิบายไว้ในไฟล์ภายนอก โดยพื้นฐานแล้ว แต่ละองค์ประกอบที่มีสไตล์ที่คุณเขียนจะเป็นโมดูลของตัวเอง คุณสามารถเขียนโค้ดสำหรับจัดแต่งทรงผมโดยไม่ต้องกังวลเกี่ยวกับผลข้างเคียง
สถานะ จะเป็นกฎที่คุณกำหนดภายในส่วนประกอบของคุณเป็นกฎตัวแปร คุณเพียงแค่กำหนดฟังก์ชันเพื่อสอดแทรกค่าของแอตทริบิวต์ CSS ของคุณ หากใช้เฟรมเวิร์ก UI คุณอาจมีคลาสที่มีประโยชน์ที่จะเพิ่มไปยังส่วนประกอบของคุณด้วย คุณอาจมีกฎ CSS หลอกตัวเลือก (โฮเวอร์ โฟกัส ฯลฯ)
ธีม สามารถสอดแทรกภายในส่วนประกอบของคุณได้ เป็นความคิดที่ดีที่จะกำหนดธีมของคุณเป็นชุดของตัวแปรเพื่อใช้ในแอปพลิเคชันของคุณ คุณยังสามารถหาสีโดยทางโปรแกรม (โดยใช้ไลบรารี่หรือด้วยตนเอง) เช่น เพื่อจัดการกับคอนทราสต์และไฮไลท์ จำไว้ว่าคุณมีความสามารถเต็มรูปแบบของภาษาการเขียนโปรแกรมที่คุณต้องการ!
นำพวกเขามารวมกันเพื่อแก้ปัญหา
สิ่งสำคัญคือต้องรวมไว้ด้วยกันเพื่อประสบการณ์การนำทางที่ง่ายขึ้น เราไม่ต้องการจัดระเบียบตามประเภท (การนำเสนอเทียบกับตรรกะ) แต่โดยการทำงาน
ดังนั้น เราจะมีโฟลเดอร์สำหรับส่วนประกอบทั่วไปทั้งหมด (ปุ่มและอื่นๆ) ส่วนอื่นๆ ควรจัดระเบียบตามโครงการและฟังก์ชันของโครงการ ตัวอย่างเช่น ถ้าเรามีคุณลักษณะการจัดการผู้ใช้ เราควรจัดกลุ่มส่วนประกอบทั้งหมดเฉพาะสำหรับคุณลักษณะนั้น
ในการใช้สถาปัตยกรรมคอนเทนเนอร์/การนำเสนอ ขององค์ประกอบที่ มีสไตล์กับแนวทาง SMACSS เราจำเป็นต้องมีส่วนประกอบประเภทพิเศษ: โครงสร้าง เราลงเอยด้วยองค์ประกอบสามประเภท มีสไตล์ โครงสร้าง และคอนเทนเนอร์ เนื่องจาก องค์ประกอบที่มีสไตล์ ตกแต่งแท็ก (หรือส่วนประกอบ) เราจึงต้องการองค์ประกอบประเภทที่สามนี้เพื่อจัดโครงสร้าง DOM ในบางกรณี อาจเป็นไปได้ที่จะอนุญาตให้ส่วนประกอบคอนเทนเนอร์จัดการโครงสร้างของส่วนประกอบย่อย แต่เมื่อโครงสร้าง DOM ซับซ้อนและจำเป็นสำหรับวัตถุประสงค์ในการมองเห็น ทางที่ดีควรแยกส่วนประกอบออกจากกัน ตัวอย่างที่ดีคือตาราง โดยที่ DOM มักจะใช้ข้อมูลที่ละเอียดมาก
ตัวอย่างโครงการ
มาสร้างแอปขนาดเล็กที่แสดงสูตรอาหารเพื่อแสดงหลักการเหล่านี้กัน เราสามารถเริ่มสร้างส่วนประกอบสูตรอาหารได้ องค์ประกอบหลักจะเป็นตัวควบคุม จะจัดการกับสถานะ - ในกรณีนี้คือรายการสูตร นอกจากนี้ยังจะเรียกใช้ฟังก์ชัน API เพื่อดึงข้อมูล
class Recipes extends Component{ constructor (props) { super(props); this.state = { recipes: [] }; } componentDidMount () { this.loadData() } loadData () { getRecipes().then(recipes => { this.setState({recipes}) }) } render() { let {recipes} = this.state return ( <RecipesContainer recipes={recipes} /> ) } }
มันจะแสดงรายการสูตรอาหาร แต่ไม่จำเป็น (และไม่ควร) จำเป็นต้องรู้วิธีการ ดังนั้นเราจึงสร้างองค์ประกอบอื่นที่ได้รับรายการสูตรและผลลัพธ์ DOM:

class RecipesContainer extends Component{ render() { let {recipes} = this.props return ( <TilesContainer> {recipes.map(recipe => (<Recipe key={recipe.id} {...recipe}/>))} </TilesContainer> ) } }
ที่จริงแล้ว เราต้องการสร้างตารางไทล์ อาจเป็นความคิดที่ดีที่จะทำให้เค้าโครงไทล์จริงเป็นส่วนประกอบทั่วไป ดังนั้นถ้าเราแยกมันออกมา เราจะได้ส่วนประกอบใหม่ที่มีลักษณะดังนี้:
class TilesContainer extends Component { render () { let {children} = this.props return ( <Tiles> { React.Children.map(children, (child, i) => ( <Tile key={i}> {child} </Tile> )) } </Tiles> ) } }
TilesStyles.js:
export const Tiles = styled.div` padding: 20px 10px; display: flex; flex-direction: row; flex-wrap: wrap; ` export const Tile = styled.div` flex: 1 1 auto; ... display: flex; & > div { flex: 1 0 auto; } `
โปรดสังเกตว่าองค์ประกอบนี้เป็นการนำเสนออย่างหมดจด มันกำหนดสไตล์และรวมสิ่งที่เด็ก ๆ ได้รับไว้ในองค์ประกอบ DOM ที่มีสไตล์อื่นที่กำหนดว่าไทล์มีลักษณะอย่างไร เป็นตัวอย่างที่ดีว่าองค์ประกอบการนำเสนอทั่วไปของคุณจะมีลักษณะอย่างไรในเชิงสถาปัตยกรรม
จากนั้น เราต้องกำหนดลักษณะของสูตร เราจำเป็นต้องมีองค์ประกอบคอนเทนเนอร์เพื่ออธิบาย DOM ที่ค่อนข้างซับซ้อน รวมทั้งกำหนดสไตล์เมื่อจำเป็น เราลงเอยด้วยสิ่งนี้:
class RecipeContainer extends Component { onChangeServings (e) { let {changeServings} = this.props changeServings(e.target.value) } render () { let {title, ingredients, instructions, time, servings} = this.props return ( <Recipe> <Title>{title}</Title> <div>{time}</div> <div>Serving <input type="number" min="1" max="1000" value={servings} onChange={this.onChangeServings.bind(this)}/> </div> <Ingredients> {ingredients.map((ingredient, i) => ( <Ingredient key={i} servings={servings}> <span className="name">{ingredient.name}</span> <span className="quantity">{ingredient.quantity * servings} {ingredient.unit}</span> </Ingredient> ))} </Ingredients> <div> {instructions.map((instruction, i) => (<p key={i}>{instruction}</p>))} </div> </Recipe> ) } }
โปรดสังเกตว่าคอนเทนเนอร์สร้าง DOM บางส่วน แต่นี่เป็นตรรกะเดียวที่มีอยู่ จำไว้ว่าคุณสามารถกำหนดสไตล์ที่ซ้อนกันได้ ดังนั้นคุณไม่จำเป็นต้องสร้างองค์ประกอบที่มีสไตล์สำหรับแต่ละแท็กที่ต้องมีการจัดสไตล์ นี่คือสิ่งที่เราทำสำหรับชื่อและปริมาณของรายการส่วนผสม แน่นอน เราสามารถแยกส่วนเพิ่มเติมและสร้างส่วนประกอบใหม่สำหรับส่วนผสมได้ ขึ้นอยู่กับคุณ—ขึ้นอยู่กับความซับซ้อนของโครงการ—เพื่อกำหนดความละเอียด ในกรณีนี้ มันเป็นเพียงองค์ประกอบที่มีสไตล์ที่กำหนดไว้พร้อมกับส่วนที่เหลือในไฟล์ RecipeStyles:
export const Recipe = styled.div` background-color: ${theme('colors.background-highlight')}; `; export const Title = styled.div` font-weight: bold; ` export const Ingredients = styled.ul` margin: 5px 0; ` export const Ingredient = styled.li` & .name { ... } & .quantity { ... } `
สำหรับจุดประสงค์ของแบบฝึกหัดนี้ ฉันใช้ ThemeProvider มันแทรกธีมลงในอุปกรณ์ประกอบฉากของส่วนประกอบที่มีสไตล์ คุณสามารถใช้มันเป็น color: ${props => props.theme.core_color}
ฉันแค่ใช้ wrapper ขนาดเล็กเพื่อป้องกันแอตทริบิวต์ที่ขาดหายไปในธีม:
const theme = (key) => (prop) => _.get(prop.theme, key) || console.warn('missing key', key)
คุณยังสามารถกำหนดค่าคงที่ของคุณเองในโมดูลและใช้ค่าคงที่เหล่านั้นแทนได้ ตัวอย่างเช่น: color: ${styleConstants.core_color}
ข้อดี
ข้อดีของการใช้ องค์ประกอบที่มีสไตล์ คือคุณสามารถใช้มันได้มากเท่าที่คุณต้องการ คุณสามารถใช้เฟรมเวิร์ก UI ที่คุณชื่นชอบและเพิ่ม องค์ประกอบที่มีสไตล์ ไว้ด้านบนได้ นอกจากนี้ยังหมายความว่าคุณสามารถโยกย้ายองค์ประกอบโครงการที่มีอยู่ตามองค์ประกอบได้อย่างง่ายดาย คุณสามารถเลือกจัดรูปแบบเค้าโครงส่วนใหญ่ด้วย CSS มาตรฐาน และใช้เฉพาะ องค์ประกอบที่มีสไตล์สำหรับส่วนประกอบที่ นำกลับมาใช้ใหม่ได้
ข้อเสีย
นักออกแบบ/ผู้ผสานรวมสไตล์จะต้องเรียนรู้ JavaScript พื้นฐานเพื่อจัดการกับตัวแปรและใช้งานแทน Sass/Less
พวกเขาจะต้องเรียนรู้ที่จะนำทางโครงสร้างโปรเจ็กต์ด้วย แม้ว่าฉันจะโต้แย้งว่าการค้นหาสไตล์สำหรับส่วนประกอบในโฟลเดอร์ของส่วนประกอบนั้นง่ายกว่าการค้นหาไฟล์ CSS/Sass/Less ที่ถูกต้องซึ่งมีกฎที่คุณต้องแก้ไข
พวกเขาจะต้องเปลี่ยนเครื่องมือเล็กน้อยหากต้องการเน้นไวยากรณ์ ผ้าสำลี ฯลฯ จุดเริ่มต้นที่ดีคือด้วยปลั๊กอิน Atom และปลั๊กอิน Babel นี้