iT邦幫忙

2017 iT 邦幫忙鐵人賽
DAY 7
0

本篇目的

了解 CSS in JS 的起源、現況,還有其在 React Native 中的用法,順便學習一下 Flexbox。

CSS in JS

Facebook Infrastucture Team 的知名開發者 (Christopher Chedeau) vjeuxCSS in JS 的投影片裡面指出了 CSS 的七大問題:

  • 全域命名空間:例如 bootstrap 就有超過 600 個 class
  • 依賴關係:CSS 本身無法模組化,並宣告依賴關係
  • 不必要的程式碼:已經用不到的 CSS style 常常殘留在程式碼中
  • class 最小化:把很長的 class 壓縮成比較短的 class 才能節省檔案大小
  • 共享常數:JavaScript 與 CSS 之間常會需要用到同一個常數
  • 無決定性的解析方式:CSS 規則抵觸時的優先權判斷很容易造成 bug
  • 破壞隔離性:可以藉由 selector 直接調整內部元素的 style

為了解決 CSS 的這些缺點,各種新的解決辦法不斷被提出,其中大部分是 CSS in JS 的做法,或加上一些 Component-Based 又或是 Babel 編譯的解決方案:

不管是存在了比較久的:

或是近幾個月才出現而且聲勢浩大的:

甚至也有今年十二月才竄出的:

還有更多已經消亡的專案,直到今日,已經有了漫長的討論,但在這個領域仍然看不到明顯的贏家。從 reactjs/core-notes這裡這裡也看得到 Facebook 積極的在做相關的實驗,但目前並沒有滿意的結果。

而且用 JavaScript 寫 Style 雖然解決了不少問題,但也造成了很多問題,例如以下的 Style 是不能直接支援的:

const styles = {
  color: '#fff',
  
  ':hover': {
    'color': 'red',
  },
  
  '@media (max-width: 480px)': {
    width: '100%',
  },
};

為了支援 ::after:hover@media (max-width: 480px) 等等 CSS 既有的寫法,這些 Library 需要用到很多的 Workaround,這部 Inline Styles 的影片有講到一些相關的主題。以筆者的觀點來看,最好是選一種不會被綁死的做法比較保險。

不過至少在做 Component 放 npm 時,筆者會想選用 Inline Style,因為不能假設使用者會使用 Webpack、有一樣的 Webpack 設定檔,也不能假設使用者會使用 Sass、Less、Stylus、PostCSS 任何一種,用 Inline Style 是唯一一個 Zero Configuration 的方式。

另一個值得一提的是 CSS Modules,它被內建在 Webpack 的 css-loader 裡面,可以在 class 名稱加上一個 hash 來避免全域的碰撞:

.btn {}
import styles from './btn.module.css';

styles.btn // btn_1afe3 

如此一來還是可以開心地寫 CSS,這也是筆者最常用的方法之一。

Style in React Native

可以直接把 Style Object 丟進去 style Prop 來處理樣式:

<Text style={{
  width: 38,
  height: 38,
}} />

不過在 React Native 我們一般用 StyleSheet.create 來建立 Style:

const styles = StyleSheet.create({
  base: {
    width: 38,
    height: 38,
  },
  background: {
    backgroundColor: '#222222',
  },
  active: {
    borderWidth: 2,
    borderColor: '#00ff00',
  },
});

StyleSheet.create 有幾個明顯的優點:

  • 直接寫在 Render 會很亂
  • 會在 ReactNativePropRegistry 註冊產生一個 id,就不用重複建立物件,也不用重複丟過去跟 Native 的 Bridge

所有的 CSS 屬性都需要寫成 camelCase (駝峰式命名),例如:

background-color -> backgroundColor
border-width -> borderWidth
border-color -> borderColor

接著就可以在 styles 上面取用要傳入到 Component style Prop 的屬性:

<Text style={styles.base} />
<View style={styles.background} />

React Native 的 Style Prop 跟 React 有點不太一樣,它可以接 Array,屬性衝突時後面的會覆蓋掉前面的屬性:

<View style={[styles.base, styles.background]} />

而且它會忽略 Array 中的 Falsy 值,所以我們可以這樣做:

<View 
  style={[
    styles.base, 
    this.state.active && styles.active,
  ]} 
/>

必須注意的是,React Native 實作的並不是完整的 CSS 功能,而只是一個子集合,可以用的屬性可以參考 React Native Styling Cheat Sheet

React Native 主要是靠 Flexbox 來排版,而下面有實作 Flexbox 的跨平台引擎 Yoga (以前叫做 css-layout)。

Flexbox

曾經,處理 Layout 對不熟 CSS 的前端工程師來講是個刻骨銘心的痛,floatpositioninline-block 各有各的毛病,十分不直覺的潛規則一個又一個。

不管是 margin: 0 auto; 還是垂直置中的七種方式都讓當初的我充滿各種問號,一旦把東西排到對的位置,就再也不敢動它。

更別說來到了行動時代,我們還有 RWD (Responsive Web Design) 得要處理。

但現在與兩三年前不同,是個幸福的時代,一代代的 IE 漸漸走入歷史,而新的標準正在建立,迎來了新的 CSS 排版方式 Flexbox:

在寫 Flexbox 時,首先我們要分辨的是 Flex Container 與 Flex Item:

flex

它們彼此有些不同的屬性可以設定,而這邊的 Main Axis 是 Item 排列的軸線,Cross Axis 是與 Main Axis 正交的軸線。

Flex Container

container

  • flex-direction:決定 Main Axis 以及它的排列方向

flex-direction

.container {
  flex-direction: row | row-reverse | column | column-reverse;
}
  • flex-wrap:決定要不要放在一行內

flex-wrap

.container {
  flex-wrap: nowrap | wrap | wrap-reverse;
}

其中 nowarp 是單行,wrap 是要換行。

  • justify-content:Main Axis 的對齊方式

justify-content

  • align-items:Cross Axis 的對齊方式

align-items

所以,我們只要用

.container {
  justify-content: center;
  align-items: center;
}

就可以達到垂直水平置中!

例如一個這樣的範例:

export default class Demo extends Component {
  render() {
    return (
      <View style={styles.container}>
        <View style={styles.item} />
        <View style={styles.item} />
        <View style={styles.item} />
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#EADE31',
  },
  item: {
    width: 100,
    height: 100,
    margin: 10,
    backgroundColor: '#7FEA9B',
  },
});

就能輕鬆的把 Item 都放在最中間:

center

Flex Item

item

  • flex-grow:如果 Container 有多餘的空間時,分配給 Item 拉長的比例。

flex-grow

  • flex-shrink:如果 Container 空間不夠時,分配給 Item 壓縮的比例。

  • flex-basis:設定不該被拿進去分配的基本長度。flex-basis: 0; 會把全部拿去分配,flex-basis: auto; 會扣掉他原本的長度再拿去分配。

flex-basis

以上這三個可以縮寫成

.item {
  flex: <flex-grow> <flex-shrink> <flex-basis>;
}

而它的預設值為 0 1 auto;

  • align-self:個別指定 Item 的 Cross Axis 的對齊方式

align-self

到這邊就把 Flexbox 重要的部分介紹完了。

(特別感謝 CSS-Tricks 提供的圖,以及它們令人感動的 License)

Can I Use 上看得到,Flexbox 的瀏覽器支援度已經來到 97.32%。

這些是不錯的學習資源:

結語

Flexbox 乍看之下一堆屬性,但實際學起來其實是簡單明瞭的,用這樣的方式來去處理 App 的 Layout 更能比較輕鬆的面對世界上破千種以上的手機跟它各自的長寬比。尤其筆者本身是個 Web 開發者,不是 iOS、Android 的開發者,能直接開始調整樣式的這個優點是非常有價值的。


上一篇
Day 06:React Native 入門
下一篇
Day 08:常見的 React Native Component - Part I
系列文
使用 Modern Web 技術來打造 Native App30

尚未有邦友留言

立即登入留言