0.0.3 • Published 7 years ago

props-overrider v0.0.3

Weekly downloads
3
License
ISC
Repository
-
Last release
7 years ago

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