專案背景
在專案的初始階段,我們需要建立自己的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 astailwind('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擼一擼。