分享一篇 天地之靈 大神的文章
文章 大家可以到原文去看一看,如有冒犯擎鸠,請聯(lián)系刪除辛掠,謝謝。
1:樣式變量
web上,我們從css到scss/sass/less懒豹,然后我們才有變量可以用。然而在RN里驯用,我們的樣式本來就是JS化的脸秽,我們可以自由的使用JavaScript的變量以及其它特性來完成樣式化:
// theme.js
var globalTextColor = '#000000',
module.exports = {
backgroundColor: '#FFFFFF',
title: {
size: 32,
color: globalTextColor
},
content: {
size: 16,
color: globalTextColor
}
};
var {StyleSheet} = React;
var theme = require("./theme")
module.exports = StyleSheet.create({
titleText : {
fontWeight: 'bold',
...theme.title
},
container1: {
background: theme.backgroundColor
},
container2: {
background: theme.backgroundColor
},
content: {
...theme.content
}
})
這樣做,我們已經(jīng)可以把一些需要經(jīng)常修改的樣式(如顏色蝴乔、字體记餐、尺寸等)和一些結(jié)構(gòu)性的樣式(譬如flex、position)等拆分開來薇正,并且可以定義一些可以影響到多個樣式的變量(如上文的globalTextColor)片酝。對于日常的樣式迭代,這已經(jīng)可以滿足一部分需求了挖腰,我們還可以根據(jù)自己的需要進(jìn)行一些定制的預(yù)計(jì)算雕沿。而且這應(yīng)當(dāng)是最易理解的方式。不過這并不能實(shí)現(xiàn)用戶切換主題的需求猴仑。
2:在組件上使用多個樣式
類似ReactJS中我們使用classnames插件审轮,ReactNative天然就支持使用數(shù)組提供多個樣式的方式:
<View style={[styles.base, styles.background]} />
所以我們也可以在組件里分開引用默認(rèn)樣式和主題化的樣式:注意這里theme返回的不是上文中的一個純對象,而是另一個stylesheet
var React = require('React');
var styles = require('./styles');
var theme = require('../theme');
class Article extends React.Component {
render(){
return (
<View style={[style.container, theme.container]}>
<Text style={[styles.title, theme.title]}>
{this.props.title}
</Text>
<Text style={[styles.content, theme.content]}>
{this.props.content}
</Text>
</View>
);
}
};
3. 附加樣式
首先辽俗,當(dāng)我們實(shí)現(xiàn)一個通用組件的時候疾渣,應(yīng)當(dāng)讓它可以在默認(rèn)的style的基礎(chǔ)上允許附加style(也即:允許外部進(jìn)一步指定style屬性):
var styleSheet = StyleSheet.create({
container: {
flex: 1
}
})
class Article extends React.Component{
render(){
var {
title, content,
style, titleStyle, contentStyle,
...others
} = this.props;
if (Array.isArray(style)){
style = [styleSheet.container, ...style];
} else {
style = [styleSheet.container, style];
}
//或者合并成這樣一句: style=[styleSheet.container].concat(style);
return (
<View style={style} {...others}>
<Title style={titleStyle}>
{title}
</Title>
<Content style={contentStyle}>
{content}
</Content>
</View>
)
}
}
// Now you can use in this way:
<Article style={theme.article} title="Title" content="Foo" />
如果所有組件代碼都支持附加樣式,我們就比較方便通過props來傳遞額外的樣式崖飘,這樣榴捡,我們就可以引入一個新的黑科技:
4. 疊加默認(rèn)屬性黑魔法: 使用增強(qiáng)組件(EnhancedComponent)模式
如果我們可以給一個組件疊加一個默認(rèn)樣式,就可以讓我們的許多工作簡單的多(譬如上文的Title和Content朱浴,可以在Text的基礎(chǔ)上直接疊加默認(rèn)屬性和樣式得到)
export var Title = enhanceComponent(Text, {
style: styleSheet
});
export var Content = enhanceComponent(Text, {
style: {
fontSize: 16,
}
});
實(shí)際上吊圾,這個enhanceComponent很容易實(shí)現(xiàn):
function enhanceComponent(Component, props){
var {style, ...others} = props;
return class extends React.Component {
render(){
var newProps = {...props};
for (var k : this.props){
if (/[sS]tyle$/.test(k) && newProps[k]) {
// merge style
newProps[k] = [].concat(newProps[k], this.props[k]);
} else if (k != 'children') {
// assign normal prop.
newProps[k] = this.props[k];
}
}
return (
<Component {...newProps}>
{this.props.children}
</Component>
)
}
};
}
所有名字以style或Style結(jié)尾的的屬性被當(dāng)做樣式處理(處理類似titleStyle的情況)
5. 主題化組件
在3和4的基礎(chǔ)上达椰,結(jié)合ES6的一部分語法,我們應(yīng)當(dāng)可以以一種反向的方法來提供主題:提供一套主題化的組件街夭。在此之前砰碴,我們的Article還需要做一些修改來適應(yīng):
var styleSheet = StyleSheet.create({
container: {
flex: 1
}
})
class Article extends React.Component{
render(){
var {
title, content, style,
createTitle, createContent
...others
} = this.props;
if (Array.isArray(style)){
style = [styleSheet.container, ...style];
} else {
style = [styleSheet.container, style];
}
//或者合并成這樣一句: style=[styleSheet.container].concat(style);
return (
<View style={style} {...others}>
{createTitle ? createTitle(title) : <Title>
{title}
</Title>}
{createContent ? createContent(content) : <Title>
{content}
</Title>}
</View>
)
}
}
// Now you can use in this way:
<Article title="Title" content="Foo" createTitle={title=><Title style={theme.title}>{title}</Title>}/>
上面這個修改增加了createTitle和createContent函數(shù),而取消了titleStyle和contentStyle(不夠通用)板丽,實(shí)際上這也接近大部分內(nèi)置庫或第三方庫所提供自定義化組件的方式(譬如為ListView提供ScrollBar)呈枉。
然后,我們可以編寫這樣一個themedComponents.js
// themedComponents.js
var themes = require("./themes");
import Article, {Title, Content} from './components/Article';
export var ThemedTitle = enhanceComponent(Title, {
style: themes.title
});
export var ThemedContent = enhanceComponent(Text, {
style: themes.content
});
export var ThemedArticle = enhanceComponent(Article, {
style: themes.article,
createTitle: title=>(<ThemedTitle>title</ThemedTitle>),
createContent: content=>(<ThemedContent>title</ThemedContent>),
});
我主要推薦這種3+4+5的方案埃碱,因?yàn)樗梢宰钚∠薅鹊男薷囊延械慕M件猖辫,也最大程度實(shí)現(xiàn)了組件的隔離性和執(zhí)行結(jié)果的可預(yù)知性。
6. 另一種思路
實(shí)際上如果主題是一個非常通用的需求砚殿,那么采用context/props/全局訂閱的方式傳遞主題也是個不錯的選擇啃憎。由于context變動并不一定能觸發(fā)一次render(React沒有承諾這一點(diǎn)),所以我們傾向于使用后兩種方案似炎。相比較而言辛萍,全局訂閱如果過多,可能會影響性能羡藐,所以用redux來管理全局theme贩毕,再通過props傳遞應(yīng)該是一個不錯的選擇:
function themeReducer(state, action){
state = state || defaultTheme;
if (action.type == 'CHANGE_THEME'){
return action.newTheme;
}
return state;
}
我們對應(yīng)編寫我們的可被theme的Component:
class Title extends React.Component {
render(){
var {theme, children} = this.props;
return (
<Text style={theme && theme.title}>
{children}
</Text>
)
}
}
class Content extends React.Component {
render(){
var {theme, children} = this.props;
return (
<Text style={theme && theme.content}>
{children}
</Text>
)
}
}
class Article extends React.Component {
render(){
return (
var {theme} = this.props;
<View style={[style.container, theme && theme.container]}>
<Title theme={theme}>
{this.props.title}
</Title>
<Content theme={theme}>
{this.props.content}
</Content>
</View>
);
}
};
這意味著你要重新改動一遍你的所有組件,但除此以外這可能是所有方案中最干凈的一個仆嗦。并且辉阶,因?yàn)樯婕暗阶咏M件的theme屬性,這個方案并不能簡單的使用enhanceComponent的方法進(jìn)行統(tǒng)一的封裝瘩扼。