props-overrider
v0.0.3
Published
```js import { defaultPropsWithScope, ScopeWrapper } from 'props-overrider'
Downloads
6
Readme
Usage
import { defaultPropsWithScope, ScopeWrapper } from 'props-overrider'
defaultPropsWithScope(Button, {
styles: newStyle,
style() {
return this.props.width === 'fixed' ? Style.fixedRaw : {}
},
}, {
item: {
style: {
backgroundColor: '#654321',
}
}
})
<ScopeWrapper scope='item'>
<SomeComponentThatContainsButtonInside />
</ScopeWrapper>
Why
RN 的样式是通过组件的 style props 来设置的,虽然属性与 CSS 类似,但实际上工作原理不同,因此覆盖样式会遇到一些比较麻烦的问题。
CSS 因为其独立于 html 存在,因此我们可在任意位置进行样式的重载和覆盖,只需要确保样式能正确匹配元素以及优先级正确即可,非常方便。但 RN 的 style 关联在组件实例上,就严格受到 js 作用域和参数传递的限制。
当所有的组件都在开发者自己的控制范围内时,这个问题还是比较好解决的,因为我们总是可以修改组件以及行为来满足我们对样式的需求。但当我们使用一些三方库作为基础组件(如 antd.mobile)时,这个问题就变得有点纠结了。
考虑如下场景:
class A extends Component {
render() {
// render some element
}
}
class B extends Component {
render() {
return (
<View><A /></View>
)
}
}
class C extends Component {
render() {
return (
<View><B /></View>
)
}
}
组件 A 、B 、C 都来自于三方库,我们用到了组件 C 然后想要覆盖组件 A 的样式。最直观也是最笨的方法是,把三个组件全部重写一遍:
class MyA extends Component {
render() {
return <A style={myOverrideStyle} />
}
}
class MyB extends Component {
render() {
return (
<View><MyA /></View> // 为了使用 MyA ,只能把 B 整个抄过来,只为了替换掉里面对 A 的使用
)
}
}
class MyC extends Component {
render() {
return (
<View><MyB /></View> // 并且还进一步的要传染到 C
)
}
}
稍微好一点的办法是,利用 React 的 defaultProps :
// 通常我们是这么用 defaultProps 的
class SomeComp extends Component {
static defaultProps = {
return { style: { color: 'blue' } }
}
}
// 但实际上我们可以直接覆盖 A 的 defaultProps 方法
const oldDefaultProps = A.defaultProps
A.defaultProps = Object.assign({}, oldDefaultProps, { style: { color: 'blue' } })
用这种方式,我们可以在全局层面直接替换掉 A 的默认 style 样式,从而避免对 B 和 C 的重写。但问题还没有得到完全的解决,现在假设我们有一个第三方组件 D 也使用了组件 A ,而我们希望 A 在 D 中的样式和 A 在 C 中的样式是不同的。这种情况在 CSS 中很常见,我们只需要在父 class 下覆盖样式即可,但是在 RN 的场景下,一种方式是借助 context 和一点点 trick :
class SubA extends A {}
SubA.prototype.render = A.prototype.render
A.contextTypes = { scope: React.PropTypes.string }
A.prototype.render = function render() {
const scope = this.context.scope || 'GLOBAL'
const style = getStyleByScope(scope)
return <SubA {...this.props} style={style} />
}
在考虑更多的细节并且通用化到除了 style 之外的 props 之后(有可能第三方组件会使用 styles 来一次传入多组 style),最终解决方案请参考代码中的 src/index.js