如何在React Native中設(shè)計(jì)主題機(jī)制

分享一篇 天地之靈 大神的文章

文章 大家可以到原文去看一看,如有冒犯擎鸠,請聯(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)一的封裝瘩扼。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末谆甜,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子集绰,更是在濱河造成了極大的恐慌规辱,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件栽燕,死亡現(xiàn)場離奇詭異按摘,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)纫谅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來溅固,“玉大人付秕,你說我怎么就攤上這事∈坦” “怎么了询吴?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵掠河,是天一觀的道長。 經(jīng)常有香客問我猛计,道長唠摹,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任奉瘤,我火速辦了婚禮勾拉,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘盗温。我一直安慰自己藕赞,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布卖局。 她就那樣靜靜地躺著斧蜕,像睡著了一般。 火紅的嫁衣襯著肌膚如雪砚偶。 梳的紋絲不亂的頭發(fā)上批销,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天,我揣著相機(jī)與錄音染坯,去河邊找鬼均芽。 笑死,一個胖子當(dāng)著我的面吹牛酒请,可吹牛的內(nèi)容都是我干的骡技。 我是一名探鬼主播,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼羞反,長吁一口氣:“原來是場噩夢啊……” “哼布朦!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起昼窗,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤是趴,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后澄惊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體唆途,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年掸驱,在試婚紗的時候發(fā)現(xiàn)自己被綠了肛搬。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡毕贼,死狀恐怖温赔,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情鬼癣,我是刑警寧澤陶贼,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布啤贩,位于F島的核電站,受9級特大地震影響拜秧,放射性物質(zhì)發(fā)生泄漏痹屹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一枉氮、第九天 我趴在偏房一處隱蔽的房頂上張望志衍。 院中可真熱鬧,春花似錦嘲恍、人聲如沸足画。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽淹辞。三九已至,卻和暖如春俘侠,著一層夾襖步出監(jiān)牢的瞬間象缀,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工爷速, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留央星,地道東北人。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓惫东,卻偏偏與公主長得像莉给,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子廉沮,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評論 2 348

推薦閱讀更多精彩內(nèi)容