React Native踩坑日記 —— tailwind-rn


項目背景

在項目的初始階段,我們需要建立自己的design system,我們spike了一些方案,tailwind-rn就是其中一種,如果有用到或者即將用到tailwind-rn的,可以進來看一看,避免踩坑。

后來覺得項目比較簡單,tailwind對新上項目小伙伴確實不太友好,所以我們最終沒有采用。

簡介

GitHub - vadimdemedes/tailwind-rn: 🦎 Use Tailwind CSS in React Native projects

Tailwind 提倡了原子型的CSS類,旨在靈活、復用,減少CSS重復,同時對於強迫症患者也有一定的治療效果(畢竟有時候想類名是一件頭疼的事)。當然,對於初學者有一定的熟悉成本,你需要要知道它的一些規則,熟悉它的命名系統等等。不太了解的可以自行google一下,這里不再贅述tailwind的使用。

tailwind-rn 就是基於tailwind的實現,使用tailwind生成的css類,然后再進行一些處理(CSS聲明的轉換、去除一些在RN上不適用的CSS聲明等等),最終轉化成適用於RN的Styles格式。

All styles are generated from Tailwind CSS source and not hard-coded, which makes it easy to keep this module up-to-date with latest changes in Tailwind CSS itself.

使用

我們大致來看看,tailwind-rn應該怎么在RN中使用。

import React from 'react';
import {SafeAreaView, View, Text} from 'react-native';
import tailwind from 'tailwind-rn';

const App = () => (
	<SafeAreaView style={tailwind('h-full')}>
		<View style={tailwind('pt-12 items-center')}>
			<View style={tailwind('bg-blue-200 px-3 py-1 rounded-full')}>
				<Text style={tailwind('text-blue-800 font-semibold')}>
					Hello Tailwind
				</Text>
			</View>
		</View>
	</SafeAreaView>
);

export default App;

tailwind這個方法是從tailwind.ts中重新暴露出來的,伴隨暴露的還有一個getColor方法。

import { create } from 'tailwind-rn';
import styles from './styles.json';

const { tailwind, getColor } = create(styles);

tailwind('text-blue-500 text-opacity-50');
//=> {color: 'rgba(66, 153, 225, 0.5)'}

styles.json是通過cli創建出來的,這個文件就是tailwind CSS類 → RN Styles 的映射。所以如果開啟了purge功能,同時又添加了一些自定義的Style,需要每次都手動執行cli的命令行重新生成新的styles.json。

簡便的方法是,可以監聽tailwind.config.js是否更改,然后自動生成styles.json,因為一般自定義的Style都會更改這個文件。

一些優點

purge功能

打開purge配置,能讓你盡可能地生成最小化的原子型RN styles,最大化的減少體積。建議在開發的時候不要開啟,在打包的時候執行一次就好了。

// Default tailwind config
// Refer to https://unpkg.com/browse/tailwindcss@2.2.9/stubs/defaultConfig.stub.js

module.exports = {
    purge: {
        enabled: true,
        content: ['../../src/**/*.ts', '../../src/**/*.tsx']
    },
    variants: {
        extend: {}
    },
    plugins: []
};

自定義theme

借助於tailwind的design system,tailwind-rn也同樣地能夠適用,你可以什么都不需要配置,直接使用它內置的設計系統,也可以新增、覆蓋一些數值,來定義符合自己的設計系統。

theme: {
  fontSize: {
    xs: 12,
    sm: 14,
    base: 16,
    lg: 18,
  },
  extend: {
		// 自定義的extend,會在生成tailwind默認的同時,額外生成自定義的類
    colors: {
      brand: {
        primary: {
          100: '#2c28f7'
        },
        secondary: {
          100: '#146d23'
        },
      }
    },
    padding: {
      7.5: 30
    },
    width: {
      12.5: 50,
      '3/7': '42.857143%',
      '4/7': '57.142857%'
    },
    borderWidth: {
      1.5: 6
    }
  }
}

一些坑

不支持各個邊框(上、下、左、右)顏色屬性

tailwind只提供整個邊框顏色的支持,但是對於上、下、左、右四個邊的邊框是不支持的。

border-black {
	--tw-border-opacity: 1;
	border-color: rgba(0, 0, 0, var(--tw-border-opacity));
}

Tailwind ships with 73 colors out of the box and 5 breakpoints, which means there are already 365 border color utilities. If we added per-side border colors, that would jump to 1825 classes.

簡單搜索一下,官方也有人提了這個issue,官方給出的答案是因為如果添加了四個邊框的顏色支持,tailwind可能需要額外增加1825個css聲明,所以暫時沒有在考慮的范圍之內。【issue鏈接

給出的解決方案是使用@apply創建一個新的component class

.code-block {
    @apply .border .border-grey-light;
    border-left-width: config('borderWidths.4');
    border-left-color: config('borderColors.grey-dark');
}

tailwind-rn本身沒有@apply 的方式去重新定義一個自定義的類,官方給出的解釋是使用@apply的本身其實就是使用tailwind('xx')【盡管我覺得有點扯,我其實是希望tailwind-rn能直接幫我把自定義的類打進styles.json,而不是我自己再手動定義一個tailwind('xx'),然后再手動引入】

I think tailwind() function itself is an equivalent of @apply already. @apply py-2 px-2 bg-blue-500 is the same as tailwind('py-2 px-2 bg-blue-500'). Support @apply

所以在RN上面的實現也是類似的

arrow: {
  ...tailwind(['w-0', 'h-0', 'border-solid', 'border-1.5']),
  borderTopColor: getColor('bg-black-medium'),
  borderLeftColor: getColor('bg-white-dark'),
  borderBottomColor: getColor('bg-black-medium'),
  borderRightColor: getColor('bg-black-medium')
}

不支持StyleSheet.hairLineWidth

React Native定義的邏輯像素單位是沒有單位,為什么

因為RN是個跨平台的框架,在IOS上通常以邏輯像素單位pt描述尺寸,在Android上通常以邏輯像素單位dp描述尺寸,RN選哪個都不好,既然大家意思相同,干脆不帶單位,在哪個平台渲染就默認用哪個單位。
RN提供給開發者的就是已經通過DPR(設備像素比)轉換過的邏輯像素尺寸,開發者無需再關心因為設備DPR不同引起的尺寸數值計算問題

通過上面的解釋,所以width為1的,在ios上代表的是1pt,在android上代表的是1dp,表現為的設備像素在二倍屏上是即是2物理像素,三倍屏則是3物理像素,而一像素邊框其實是代表的物理像素,所以ios在三倍屏上要想顯示一像素的邊框,對應的應該是0.33333pt.

由於我們需要使用RN的hairLineWidth來幫我們自動根據設備來計算,所以就不能使用配置文件來處理,所以解決的方案也比較硬核,就是直接往styles.json中塞值。

自定義一個custom.style.ts,專門用來處理一些tailwind-rn理不了的類聲明。

// custom.styles.ts

import { StyleSheet } from 'react-native';

export const customStyles = {
  'border-hair': {
	    borderWidth: StyleSheet.hairlineWidth
  },
  'border-t-hair': {
	    borderTopWidth: StyleSheet.hairlineWidth
  },
  'border-b-hair': {
      borderBottomWidth: StyleSheet.hairlineWidth
  },
  'border-l-hair': {
      borderLeftWidth: StyleSheet.hairlineWidth
  },
  'border-r-hair': {
      borderRightWidth: StyleSheet.hairlineWidth
  },
  'width-hair': {
      width: StyleSheet.hairlineWidth
  }
};

export type CustomStylesKey = keyof typeof customStyles;

然后在tailwind.ts中merge一下

// tailwind.ts
import { create } from 'tailwind-rn';
import { assign } from 'lodash';

const { tailwind } = create(assign(styles, customStyles));

不支持智能提示

在現在主流的IDE上,都存在tailwind的智能提示插件,但是對於tailwind-rn的提示卻不友好,要解決也挺簡單

  • 自己實現一個插件,兼容各個IDE
  • 重新定義下類型,一個討巧的做法,這里講一下這種方法

編輯器不支持智能提示,我們可以利用Typescript的類型系統來稍微改造一下,讓其能夠自己推斷

// tailwind.ts

import { create } from 'tailwind-rn';
import styles from 'src/styles/styles.json';
import { assign } from 'lodash';
import { customStyles, CustomStylesKey } from 'src/styles/custom.style';

const { tailwind } = create(assign(styles, customStyles));

export type TailwindKey = keyof typeof styles | CustomStylesKey;

export default (keys: TailwindKey[]) => tailwind(keys.join(' '));

這里強行將之前的string變成了一個數組,目的就是為了讓IDE去識別自己定義的tailwind key類型

// 推薦使用
tailwind('h-11 bg-red-100')

// 改造之后
tailwind(['h-11', 'bg-red-100'])

getColor與purge沖突

當使用tailwind-rn提供的getColor方法,並開啟了purge配置時

// tailwind.config.js
module.exports = {
    purge: {
        enabled: true,
        content: ['../../src/**/*.ts', '../../src/**/*.tsx']
    },
    // ...
}

由於tailwind默認不支持邊框顏色,所以我不得不使用RN提供的方法。但是這樣使用,我就需要使用getColor方法。

// PageA.styles.ts
const styles = StyleSheet.create({
    container: {
        ...tailwind('w-11 h-11 bg-black text-white'),
        borderTopColor: getColor("blue-500")
    }
})

但是在我使用purge之后,tailwind掃描了默認已經在使用的CSS類,所以blue-500沒有被識別,也沒有被打包到styles.json中。

這就是問題所在。解決的方法也比較簡單,就是使用tailwind提供的css類

// PageA.styles.ts
const styles = StyleSheet.create({
    container: {
        ...tailwind('w-11 h-11 bg-black text-white'),
        borderTopColor: getColor("bg-blue-500 bg-opacity-50")
    }
})

源代碼中的getColor是會默認掃描background,所以默認拼接了bg-,那么干掉就成了

// Pass the name of a color (e.g. "bg-blue-500") and receive a color value (e.g. "#4399e1"),
// or a color and opacity (e.g. "bg-black bg-opacity-50") and get a color with opacity (e.g. "rgba(0,0,0,0.5)")
const getColor = name => {
		// const style = tailwind(name.split(' ').map(className => `bg-${className}`).join(' '));
    const style = tailwind(name);
    return style.backgroundColor;
};

針對這個問題,我給官方提了個PR,但是不知道何時才能merge了。

Purge function will conflict with getColor by Rynxiao · Pull Request #96 · vadimdemedes/tailwind-rn

顯然修改源代碼是不可靠的,一下次的更新可能就會干掉你原先apply的代碼,所以我們自己實現一遍就好。

// tailwind.ts

export const getColor = (keys: TailwindKey[]) => {
    const style = tailwind(key.join(' '));
    return style.backgroundColor;
};

總結

  • 使用初期確實挺煩的,一個類一個類去找,但是熟悉了它的命名規范之后,其實寫起來還挺順暢的。
  • 有一些坑,但都不是不能解決的問題,大不了使用原生的RN Style擼一擼。

參考鏈接


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM