1. 程式人生 > >我的前端故事----來聊聊怎麽寫react-native上的樣式吧

我的前端故事----來聊聊怎麽寫react-native上的樣式吧

ont vertical short 事情 控制 left ots sheet native

我遇到了什麽問題?

不久之前我重構了一個古老的項目,總結了一些js方面的想法,不過對於一個前端項目而言不僅僅只由js組成的嘛,上學的時候老師和我說HTML+CSS+JS對應的是頁面的骨架、皮膚和肌肉。既然骨架我們有了,肌肉也聊完了,今天我們就來聊聊“皮膚”吧。

由於我重構的是一個react-native項目,所以我們先來說說在react-native上是怎麽寫樣式的吧,和傳統的web不一樣的是,在react-native上面是沒有css代碼,不過得益於Yoga,我們可以在客戶端上像寫css一樣的去書寫我們的樣式。我們來看看react-native文檔上是怎麽說的吧:

在React Native中,你並不需要學習什麽特殊的語法來定義樣式。我們仍然是使用JavaScript來寫樣式。所有的核心組件都接受名為style的屬性。這些樣式名基本上是遵循了web上的CSS的命名,只是按照JS的語法要求使用了駝峰命名法,例如將background-color改為backgroundColor。

style屬性可以是一個普通的JavaScript對象。這是最簡單的用法,因而在示例代碼中很常見。你還可以傳入一個數組——在數組中位置居後的樣式對象比居前的優先級更高,這樣你可以間接實現樣式的繼承。

沒錯,你幾乎不需要什麽成本就可以按照寫css一樣的寫法去寫我們的rn樣式,我們來看一下文檔中的例子:

import React, { Component } from ‘react‘;
import { AppRegistry, StyleSheet, Text, View } from ‘react-native‘;

export default class LotsOfStyles extends Component {
  render() {
    return (
      <View>
        <Text style={styles.red}>just red</Text>
        <Text style={{
            color: ‘blue‘,
            fontWeight: ‘bold‘,
            fontSize: 30,
        }}>just bigblue</Text>
        <Text style={[styles.bigblue, styles.red]}>bigblue, then red</Text>
        <Text style={[styles.red, styles.bigblue]}>red, then bigblue</Text>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  bigblue: {
    color: ‘blue‘,
    fontWeight: ‘bold‘,
    fontSize: 30,
  },
  red: {
    color: ‘red‘,
  },
});

AppRegistry.registerComponent(‘LotsOfStyles‘, () => LotsOfStyles);

在上面的demo中,我們有兩種方式去寫我們的樣式,它和我們在寫css時候遇到的外聯式樣式、內聯式樣式很相似,而項目中我們總是習慣將樣式和頁面分離,然後把他們都放在另外一個style.js文件中。這是個非常不錯的習慣,但是也造成了一些困擾。

面對一個頁面,我該怎麽去模塊化它的樣式呢?在之前的項目中雖然做到了樣式和頁面的分離,讓頁面“看起來”幹凈了很多,但是在 style.js 文件中仍然是雜亂的代碼,大量重復的變量、重復的內容、重復的聲明。。。這個時候又有同學說了,我們可以把這些公共的變量、代碼分離出來放到一個主題文件中呀,於是項目中除了各個頁面的style.js之外又在全局出現了一個theme.js文件,在這裏大家愉快的把諸如顏色、大小、布局等公共的代碼放了進來。

這看似解決了style.js裏重復冗余的代碼,但是也會讓我的import變得混亂:

import { StyleSheet } from ‘react-native‘;
import {
  px,
  COLOR_BG_RED,
  COLOR_BG_GREEN,
  STYLE_FR_VC_HSB,
  STYLE_FR_VC_HC,
  STYLE_FR_VC_HFS,
} from ‘MyStyle‘;

export default StyleSheet.create({
    // TODO
});

看到這裏,我想你除了知道我import進來了兩個顏色之外,對於其它變量會一頭霧水吧,除非你去MyStyle模塊裏面親眼看一下才會知道真正引入進來的是些什麽了,如果這裏的樣式特別的多的話,除了再新建一個sytle.js之外,你就只能每次回到頭部去看看自己引入了些什麽。這是我不能忍受的。。。

為你的樣式分類

除了由於一次性引入太多的公共樣式導致我要來回滑動之外,當我再去寫一個新的styel.js文件時,復制這麽多引入也是一件頭疼的事情,那麽我能不能每次只需要寫一行import呢?如果我的樣式都是按固定規則分類放好的是不是每次就可以只import這幾個類了呢?

經常寫css的同學一定註意過樣式的書寫順序,某一類的屬性寫在一起,雖然在web中,這樣寫是為了優化css引擎,但是這也體現出了樣式是有一定類型的,控制顏色的、控制邊距的、控制布局的,那麽我們的公共變量是不是也可以按照這樣的規則來聲明呢?

import { color, size, layout } from ‘MyStyle‘;

這樣我們文件的頭部是不是就清晰多了呢?在寫代碼的時候,也不需要再關心我之前引入了些什麽了,只要只要關註我們要寫什麽就行了:

export default StyleSheet.create({
    lines: {
      height: px(88),
      backgroundColor: color.background,
      borderLeftWidth: size.border,
      borderRightWidth: size.border,
      borderBottomWidth: size.border,
      borderColor: color.border,
      // 子元素橫向排列,垂直居中,水平分布,中間用空格填滿,最兩邊元素各自靠邊
      ...layout.flex.vchbs,
    },
});

在我的項目中默認邊框的大小就是一個像素(1px),那麽只要在最外層聲明了 size.border的大小,後面寫代碼的時候就可以暢行無阻的書寫下去了,其實我們已經模塊化了,只是我們還不夠徹底,不徹底就代表著我們的代碼不完美,而且可復用性差,就如上面的demo,如果我們這裏需要一個三面的邊框,那麽其它組件需不需要呢?如果需要的話是不是也可以像我這樣寫呢?

當然是不可以!為什麽?因為我們是在復用這個邊框,所以我們就不該再寫一份一模一樣的代碼了,而是應該寫類似這樣的:

export default StyleSheet.create({
    lines: {
      height: px(88),
      backgroundColor: color.background,
      // 一個邊框粗細為1px的紅色邊框
      ...layout.border
      // 子元素橫向排列,垂直居中,水平分布,中間用空格填滿,最兩邊元素各自靠邊
      ...layout.flex.vchbs,
    },
});

這樣我們的代碼不僅少了很多,結構也清楚了,而且到時候替換或者修改的時候也容易一些了,不過寫成這樣就結束了嘛?當然不是了,我們現在有一個紅色的邊框,所以我們在layout模塊下新增了一個border屬性,那麽如果我們有一個藍色的邊框呢?一個綠色的粗邊框呢?我們會一直往layout模塊上新增屬性嘛?那最後你知道layout上面究竟有多少屬性嘛?那不就又回到一開始了嘛。。。

所以,我的建議是,處於根節點的模塊最好控制在3個左右:

  • color:用於存放整個項目的全部顏色,這也代表著,在組件的style內部,我們不應該再顯示的書寫諸如backgroundColor: ‘#fff‘這樣的代碼了。
  • size:用於存放整個項目的通用大小,比如說行高、間距、字體大小等公共的數值參數。
  • layout:用於存放整個項目的公共布局,例如控制布局的flex屬性、通用的padding、margin、position定位。

那麽第二級中的屬性我也建議控制在5個左右:

  • 顏色:邊框顏色、背景顏色、字體顏色。。。
  • 大小:邊框大小、間距大小、字體大小。。。
  • 布局:flex布局、position定位。。。

這樣雖然增加了深度,但是分類清晰,結構明確,復用性也比較高。雖然可能會增加項目新建時的成本(創建各種分類),但是會給後續的開發、遷移、重構、復用等帶來極大的便捷。但這就結束了嘛?有的同學和我說,我有很多的邊框啊,我有很多樣式要復用啊,到最後我的layout也會大到看不懂啊。。。還有的同學說我沒有那麽多可復用的樣式啊,那是不是你總結的思路就用不上了啊。當然不是咯,我們只完成了樣式模塊化的第一步(抽離樣式),接下來開始第二步。

該怎麽更便捷的寫樣式?

現在很多web開發者在書寫css的時候已經不再去寫原生的css了吧,而是采用例如scss、less這樣的預編譯語言去寫樣式了,那麽這些預編譯語言給我們帶來了哪些方便呢?我想大多數同學第一時間都會想到Mixin

利用混合器,可以很容易地在樣式表的不同地方共享樣式。如果你發現自己在不停地重復一段樣式,那就應該把這段樣式構造成優良的混合器,尤其是這段樣式本身就是一個邏輯單元,比如說是一組放在一起有意義的屬性。

在react-native上面,我們的樣式代碼是js代碼,所以很天然的就自帶預編譯,不需要其它額外的語言去處理它,要做的只是判斷你的屬性是否需要一個Mixin。

判斷一組屬性是否應該組合成一個混合器,一條經驗法則就是你能否為這個混合器想出一個好的名字。如果你能找到一個很好的短名字來描述這些屬性修飾的樣式,比如rounded-cornersfancy-font或者no-bullets,那麽往往能夠構造一個合適的混合器。如果你找不到,這時候構造一個混合器可能並不合適。

那麽在js上面,我該如何實現一個Mixin呢?太簡單了!我們只需要一個函數就可以了,沒錯,只需要一個返回對象的函數就可以做到這樣的效果了,加上ES7的拓展運算符,我們就可以做到一個混合器的效果:

export default StyleSheet.create({
    lines: {
      height: px(88),
      backgroundColor: color.background,
      ...layout.border(1px, ‘#fff‘)
    },
});

常寫react-native的同學一定都頭疼過這樣一個問題吧,就是我們並不能像寫css樣式一樣在一行中把所有的屬性都寫完,在css中我們如果想要聲明一個四面邊框的大小,可以這樣寫:

.border {
    border: 10px 5px 10px 5px;
}

那麽在我們寫樣式的時候是不是也可以這樣寫:

export default StyleSheet.create({
    lines: {
      height: px(88),
      backgroundColor: color.background,
      ...layout.border(10px, 5px, 10px, 5px),
    },
});

我們可以通過函數的不同數量的參數來模擬傳統css開發的簡寫屬性,很多時候我們更習慣在View上面去做樣式的運算,利用react-native樣式的覆蓋數組去不斷的覆蓋之前的樣式來達到運算的結果,這就導致View中除了需要計算你的組件要不要展示、如何展示之外,還要去計算樣式該如何寫,既然我們要做樣式和頁面的分離,那就應該做徹底一些,將樣式的計算也放在style.js中。

總結

最後總結一下我們所做的:

  • 分離樣式和頁面
  • 提取項目級的公共屬性
  • 歸類提取的公共樣式
  • 通過混合器去創造模板樣式

我建議無論你的項目多大,代碼多少,前三步都應該是一個必備的環節,可能你的項目不復雜,暫時用不到第四點,但前三條無論如何都應該盡早的去完善,這不僅僅能幫助你實現後續的叠代,也能在你的腦中保留出一個對於項目完整結構的印象,要知道樣式是寄生於頁面的,清楚了樣式,那麽頁面如何你也多少會爛熟於心了。而相比於通過梳理js的邏輯去了解整個項目,我想通過頁面也許會更快吧,這對剛剛接手項目的新同學來說,是非常友善的。

最後的最後

一般到這裏,就該放上自己開源的項目地址或者安利一波作者寫的庫了,不過和上一篇一樣,這裏我們只討論思路,表述想法,而具體的實踐和代碼還是要靠我們自己在項目中不斷的總結和積累~

我相信很多同學對於我提到的前三點都會很快的理解,而對於第四點可能就有些懵了,該怎麽去理解這個混合器呢?我該怎麽用js去實現一個呢?下面我就用一段代碼來舉個例子,該如何實現一個Mixin:

const layout = {
  // 這裏的形參順序遵循css中的 “上、右、下、左”
  margin(...arg) {
    let margin = {};
    switch (arg.length) {
      case 1:
        margin = {
          marginTop: arg[0],
          marginRight: arg[0],
          marginBottom: arg[0],
          marginLeft: arg[0],
        };
        break;
      case 2:
        margin = {
          marginVertical: arg[0],
          marginHorizontal: arg[1],
        };
        break;
      case 3:
        margin = {
          marginTop: arg[0],
          marginHorizontal: arg[1],
          marginBottom: arg[2],
        };
        break;
      case 4:
        margin = {
          marginTop: arg[0],
          marginRight: arg[1],
          marginBottom: arg[2],
          marginLeft: arg[3],
        };
        break;
      default:
        break;
    }
    return margin;
  },
};

這是一個最簡易的Mixin,你可以根據你的需求去寫更多這樣的Mixin,其實我個人覺得在項目一開始的時候是不一定需要這個的,這個存在的意義是對於復雜樣式書寫的,更多的情況下,你的項目只要做到了前三點,在樣式這一塊就已經非常的整潔、完善了,多數情況下你不需要Mixin就能組織好你的代碼。

好了,以上就是這次我想和大家聊的關於react-native中樣式的話題了,我們下次見~

我的前端故事----來聊聊怎麽寫react-native上的樣式吧