基于react的组件库主题设计方案
需求背景
设计目标
性能
可维护性
可配置
易用性
粒度细分
样式提取
颜色:品牌色、默认背景色、通用背景色、基本文本颜色、辅助文本颜色、链接色
文本:文本大小,字重,字体间距等
按钮:圆角大小,按钮尺寸,边框尺寸等
图片:图片尺寸,圆角大小等
技术选型
如何生成一份全局样式配置表
组件如何获取样式配置表
如何生成一份全局样式配置表
借助gulp/webpack等打包工具相关的插件,配置需要定制的样式变量,在打包时覆盖对应变量值。
重写样式,覆盖样式配置表,生成新的全局样式配置表。
组件如何获取样式配置表
通过 props 属性自上而下(由父及子)进行传递
Context 提供了一种在组件之间共享值的方式,不必显式地通过组件树的逐层传递 props
设计方案
重写样式,覆盖样式配置表,生成新的全局样式配置表
组件通过Context提高的组件之间共享值的方式,获取样式配置表
生成样式配置表
样式优先级
深色主题
和 浅色主题
,还有与主题切换无关的 其他样式
, 在业务侧未指定主题时,组件库默认使用浅色主题的颜色配置表+其他可配置的默认样式值,如字体大小,字重等,业务侧可以重写样式,最终生成的样式表作为提供者Provider给到各个组件使用。value={}
给业务侧赋值给组件库,业务侧可以在对象中传入指定的主题,比如 value={theme:"light"}
或者 value={theme:"dark"}
,我们提供一个便利,业务侧可以直接传入 value="light"
或 value="dark"
。如果希望针对某个样式值进行重写,可以 value={textBaseColor:"#555555"}
。
function getStyle (style) {
if (style === "light") {
return useTheme(lightStyleSheet);
} else if (style === "dark") {
return useTheme(darkStyleSheet);
} else {
const themeStyle = style && style.theme === "dark" ? darkStyleSheet : lightStyleSheet;
return useTheme({ ...themeStyle, ...style });
}
};
useTheme
是一个合并样式的方法,参数是样式对象。
function useTheme(args = {}) {
return Object.assign({}, defaultStyle, args);
}
defaultStyle
指向了默认的样式表。Context传递共享值
const ThemeContext = React.createContext(defaultTheme);
// ThemeProvider:将样式合集写入value提供给消费者
const ThemeProvider = (props: ThemeProviderProps) => {
let style = getStyle(props.value);
return <ThemeContext.Provider value={style}>{props.children}ThemeContext.Provider>;
};
class ThemeConsumer extends React.Component {
render() {
return (
<ThemeContext.Consumer>
// children是一个函数,而非组件
{style => {
return this.props.children(style);
}}
ThemeContext.Consumer>
);
}
}
组件接收样式表
<Provider theme={{theme: "dark",defaultFontSize: 18}}>
<HiText />
<HiList />
Provider>
<Consumer>
{(themeStyle) => {
return (
<Text style={{fontSize: themeStyle.defaultFontSize}}>
Text 组件
Text>
);
}}
Consumer>
<Consumer>
{(themeStyle) => {
return (
<View>
<Text style={{fontSize: themeStyle.defaultFontSize}}>
List 组件
Text>
<HiText />
View>
);
}}
Consumer>
强制模式
renderChildren(style: any) {
let children = this.props.children(style);
if (children && children.props && children.props.theme) { // 判断局部逐渐是否传递了主题属性
let partStyle = getStyle(children.props.theme);
children = this.props.children(partStyle);
}
return children;
}
暴露样式表
class ThemeProvider extends Component<ThemeProviderProps> {
static styleConfig: DefaultStyle;
render() {
const style = getStyle(this.props.value);
ThemeProvider.styleConfig = style; // 暴露主题配置表
......
}
}
如何使用
Provider引入
// app.js
theme="dark">
theme 属性使用
theme="light">
theme={{ hiFontSizeM: 20 }}>
theme={{ theme: "dark", hiFontSizeM: 20 }}>
默认使用主题背景色。优先级:style 属性 > 更改配置表定制背景色 > 默认主题背景色
// 更改配置表定制背景色:背景色使用的是样式表中的 hiBgColor 值
theme={{ hiBgColor: "#666666" }}>
// style属性更改背景色
style={{ backgroundColor: "#666666" }}>
强制模式
import HiText from "../HiText";
theme="dark">default(Text 28 A)
theme={{ hiBgColor: "#666666" }}>default(Text 28 A)
获取样式配置表
// 引入 Provider
let styleConfig = Provider.styleConfig;
重点问题解决
兼容新旧SDK
const ThemeContext = React.createContext ? React.createContext(defaultStyle) : null;
const IS_SUPPORT_THEME = ((HippyReact && HippyReact.version && versionCompare("2.0.3", HippyReact.version))) && ThemeContext;
const ThemeProvider = (props: ThemeProviderProps) => {
const style = getStyle(props.value);
if (IS_SUPPORT_THEME) {
// 支持主题切换,使用Context API
return (
<View {...props} style={[{ backgroundColor: style.hiBgColor || "#FFFFFF" }, props.style]} key={style.type}>
<ThemeContext.Provider value={style}>{props.children}ThemeContext.Provider>
View>
);
} else {
// 不支持主题切换,返回Provider下的children内容
return (
<View {...props} style={[{ backgroundColor: style.hiBgColor || "#FFFFFF" }, props.style]} key={style.type}>
{props.children}
View>
);
}
};
class ThemeConsumer extends React.Component<ThemeConsumerProps> {
static defaultProps: ThemeConsumerProps = {
children: () => {}
};
constructor(props: ThemeConsumerProps) {
super(props);
this.state = {};
}
renderChildren(style: any) {
let children = this.props.children(style);
if (children && children.props && children.props.theme) {
let partStyle = getStyle(children.props.theme);
children = this.props.children(partStyle);
}
return children;
}
render() {
if (IS_SUPPORT_THEME) {
// 支持主题切换,children是一个方法,style是从provider获取到的样式表,作为参数给到children
return (
<ThemeContext.Consumer>
{style => {
return this.renderChildren(style);
}}
ThemeContext.Consumer>
);
} else {
// 不支持主题切换,children也是一个方法,这里style作为参数直接执行方法渲染children
return this.renderChildren(defaultStyle);
}
}
}