React(8) - CSS in JS


Posted by TempuraEngineer on 2022-10-22

目錄


CSS in JS是甚麼

CSS in JS是組件的設計模式,如其名就是在JS寫CSS,它讓每個組件的樣式都是獨立,而不是拆分放到通通放到index.css

不是React原生的功能,它需要透過第三方package(ex:styled-component、emotion)來實現

npm i styled-components


CSS in JS的優缺點

優點

1.動態產生class name,可以避免元件之間,因為class name相同而互相影響
2.條件樣式(conditional style)

缺點

1.是為需要將function component內的css做轉換,然後注入到DOM,所以效能比直接寫class name來得,而且使用CSS in JS的組件越多,效能就越差

2.CSS in JS會動態產生class name,所以沒辦法像直接寫class name(有css檔案)一樣快取,這也會導致效能變差


props

那用styled-component的話會怎麼寫吧

首先import styled-component

import styled, { css } from 'styled-components'; 

const colorMap = {
  primary:{
    normal:'#08A6BB',
    dark:'#068394'
  },
  error:{
    normal:'#dc3545',
    dark:'#b62d3b'
  },
  warning:{
    normal:'gold',
    dark:'#e2c000'
  },
  secondary:{
    normal:'#9999a3',
    dark:'#71717a'
  }      
};
const SimpleButton = styled.button`
  border: 2px solid;
  border-radius: 0.25rem;
  padding: 0.5em;

  ${props => {
    if(props.outline){
      return css`
        background: transparent;
        color: ${colorMap[props.color].normal};
        border-color: ${colorMap[props.color].normal};

        &:hover{
          color: white;
          background: ${colorMap[props.color].normal};
        }
      `
    }else{
      return css`
        background: ${colorMap[props.color].normal};
        color: white;
        border-color: ${colorMap[props.color].normal};

        &:hover{
          background: ${colorMap[props.color].dark};
          border-color: ${colorMap[props.color].dark};
        }        
      `
    }
  }}
`;

SimpleButton.defaultProps = { // 設置props的default value
  color: 'primary',
}

function App(){
  return (
    <div className="container">
      {/* 使用onClick prop就將方法綁定 */}
      <SimpleButton onClick={() => alert('this is normal button')}>Normal Button</SimpleButton>

      {/* 使用自訂的outline、color prop設定樣式 */}
      {/* 如果想和Tailwind混用,使用className,就可以把Tailwind的class name傳進去 */}
      <SimpleButton outline color="error" className="ml-3">Outline Button</SimpleButton>
    </div>
  );
}

export default App;


延伸樣式

延伸樣式常用於特別化的情況,只要用styled()將base component包起來就可以

extend style

const PillButton = styled(SimpleButton)`
  border-radius:50rem;
`;

function App(){
  return (
    <div className="container">
      <PillButton className="ml-3"color="secondary">PillButton</PillButton>
    </div>
  );
}

export default App;


attrs

attrs和JS的setAttribute(name, value)一樣,可以幫styled component設置各種attribute

const Input = styled.input.attrs({ type: 'checkbox' })``;

const Label = styled.label`
  align-items: center;
  display: flex;
  margin: 0 4px;
`;

class CheckItem extends Component {
  constructor(props) {
    super(props);
    this.state = {

    };

    this.toggle = this.toggle.bind(this);
  }

  toggle(e){
    this.props.onBoxClick(this.props.data.index, e.target.checked);
  }

  render() {
    const {text, isChecked} = this.props.data;

    return (
      <div className={`flex ${isChecked && 'line-through'}`}>
        <Input checked={this.props.data.isChecked} onChange={this.toggle} disabled={this.props.data.isChecked}></Input>
        <Label>{text}</Label>
      </div>      
    );
  }
}

class ToDoList extends Component {
  constructor(props) {
    super(props);
    this.state = {
      pendingValue:'',
      list:[
        {text:'walking dog', isChecked:false},
      ],
    };

    this.addItem = this.addItem.bind(this);
    this.changePendingValue = this.changePendingValue.bind(this);
    this.toggleCheckbox = this.toggleCheckbox.bind(this);
  }

  changePendingValue(e){
    this.setState({
      pendingValue:e.target.value
    });
  }

  addItem(){
    this.setState((state) => {
      state.list.push({text:this.state.pendingValue, isChecked:false});
    });

    this.setState({
      pendingValue:''
    })
  }

  toggleCheckbox(index, isChecked){
    let list = JSON.parse(JSON.stringify(this.state.list));
    const targetItem = list[index];

    targetItem.isChecked = isChecked;
    list.splice(index, index + 1, targetItem);

    this.setState({
      list:list
    });
  }

  render() { 
    return (
      <div>
        <input type="text" value={this.state.pendingValue} className="p-2" onChange={this.changePendingValue}/>

        <SimpleButton className="ml-2" onClick={this.addItem}>add</SimpleButton>

        <ul>
          {
            this.state.list.map((item, index) => 
              <CheckItem data={{...item, index}} onBoxClick={this.toggleCheckbox} key={`${item.text}-${index}`}></CheckItem>)
          }
        </ul>
      </div>
    );
  }
}

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {

    };
  }

  render() { 
    return (
      <div className='container'>
        <h1 className='text-xl bold mb-3'>React</h1>
        <ToDoList></ToDoList>
      </div>      
    );
  }
}

export default App;


helpers

createGlobalStyle

一般來說styled component的scope只會是該組件,以確保不同組件間的樣式不會互相干擾

如果想要讓樣式變成全域的(ex: reset.css那類整個網站都要套用的樣式),就需要使用createGlobalStyle

import { createGlobalStyle } from 'styled-components'

const GlobalStyle = createGlobalStyle`
  body {
    color: ${props => (props.darkMode ? 'white' : '#333')};
  }
`;

function App(){
  return (
    <div>
      {/* 如果是暗黑模式就把字全部都設為白色 */}
      <GlobalStyle darkMode />
    </div>
  )
}


keyframes & theme

import styled, { keyframes } from 'styled-components

function animationHelper(colors){
  return keyframes`
    0%{
      background-color: ${colors[0]};
    }

    25%{
      background-color: ${colors[1]};
    }

    50%{
      background-color: ${colors[2]};
    }

    75%{
      background-color: ${colors[3]};
    }

    100%{
      background-color: ${colors[4]};
    }
  `;
}

const brown = animationHelper(['#A7A284', '#8a8462', '#716834', '#5a5019', '#433E0E']);
const red = animationHelper(['#f87e8a', '#fd3e51', '#dc3545', '#b62d3b', '#91252f']);
const blue = animationHelper(['#7e9df8', '#3e64fd', '#4035dc', '#462db6', '#253991']);

const Box = styled.div`
  animation: blue 750ms infinite;
  animation: ${props => props.theme.animation} 750ms 5;
`

Box.defaultProps = {
  theme:{
    animation:blue
  }
} 

function App(){
  return (
    <div className='flex justify-around'>
      <Box className='p-3' theme={{animation:brown}}>box</Box>
      <Box className='p-3' theme={{animation:red}}>box</Box>
      <Box className='p-3'>box</Box>
    </div>
  )
}

export default App;


參考資料

Day 23 - 為什麼要用 Styled-components
styled component
A Lukewarm Approval of CSS-in-JS
Usage
CSS vs. CSS-in-JS: How and why to use each


#React #CSS in JS #styled component







Related Posts

分辨 Slice()、Split()、Splice()

分辨 Slice()、Split()、Splice()

翻譯 API

翻譯 API

從前端傳資料給後端(GET, POST)、從 PHP 連線到 MySQL 資料庫

從前端傳資料給後端(GET, POST)、從 PHP 連線到 MySQL 資料庫


Comments