阅读这篇文章之前,可以先拜读一下这篇文章 React 进阶之高阶组件 了解一下高阶组件的概念及几种使用方式。
高阶函数科普
什么是高阶函数
我们都知道高阶函数是什么, 高阶组件其实是差不多的用法,只不过传入的参数变成了react组件,并返回一个新的组件.
A higher-order component is a function that takes a component and returns a new component.
什么是反向继承
//IIHOC |
可以看到,上面的代码通过继承WrappedComponent,本来是一种嵌套的关系,结果II返回的组件却继承了WrappedComponent,这看起来是一种反转的关系。 通过继承WrappedComponent,除了一些静态方法,包括生命周期,state,各种function,我们都可以得到。
从理论到需求
以下内容是我在项目中第二次使用到高阶函数,第一次在另一篇文章里—— 高阶函数-属性代理在多表单中的应用
使用背景
本次实践是在 React Native 开发中使用 react-navigation
发生的。项目的路由管理大致是分为三个路由 —— 注册逻辑路由、主页面路由、首次注册配置路由,然后再顶层容器里通过登陆状态控制使用具体哪一个路由。
在某一个路由的顶层容器中,代码如下:const SCREENS = {
LoginScreen,
...
}
/**
* 本模块的数据逻辑:
* 1. 凡是点下一步的页面数据才会被保存
*/
@observer
export default class extends PureComponent {
constructor (props) {
super(props)
this.setting = new SettingStore()
}
addProps = (SomeComponent) => {
return class extends SomeComponent {
render () {
return <SomeComponent {...this.props} /> // u can add more props here
}
}
}
getScreens () {
const screens = {}
Object.keys(SCREENS).forEach(key => {
screens[key] = {
screen: this.addProps(SCREENS[key])
}
})
return screens
}
render () {
const Navigator = StackNavigator(
this.getScreens(),
{
initialRouteName: settingEntrance
}
)
return ( // 直接包裹 Navigator 无法传递 store
<Provider setting={this.setting}>
<Navigator />
</Provider>
)
}
}
这个页面的主要功能就是组织并生成子路由,这里需要做的主要是利用了 mobx-react 提供的 Provider 组件来向所有的子孙元素传递我们生成的局部全局 store。那么问题来了,我们如果想给所有子元素(注意不是子孙元素)添加任意的配置属性怎么办?
在查了 react-navigation
文档之后,得到了这样一个属性,screenProps
, 具体使用方法是<Navigator screenProps={...} />
大致意思是有了这个属性我们可以在任意一个页面中使用 this.props.screenProps
来在每个页面中访问该全局属性。按这样还是不够爽,这样所有的属性都需要使用 this.props.screenProps.xxx
来访问,我们想直接通过 this.props.xxx
来访问,此时就想到了高阶函数。
show me the code
addProps = SomeComponent => { |
最为核心的就是以上代码了,我们可以在给 StackNavigator
设置页面参数(这里我是通过 getScreens
方法来生成的)加入页面 class 的时候,我们可以通过高阶组件生成一个新的组件。
最开始版本的代码是这样的addProps = SomeComponent => {
return class extends Component {
render () {
return <SomeComponent {...this.props} /> // u can add more props here
}
}
}
使用的是属性代理模式,需要做的就两点:
- 将高阶组件所收到的所有 props 照搬传递给
SomeComponent
- 将我们想要添加的属性可以像如下方式添加
addProps = SomeComponent => {
let that = this
return class extends Component {
render () {
return <SomeComponent {...this.props} xxx={that.xxx}/> // u can add more props here
}
}
}
到这里,我以为万事大吉了,然而 …. 在渲染的时候,发现我设置的 NavigationBar 的标题怎么也渲染不出来,似乎所有页面中关于
navigationOptions 静态属性的设置都无法生效。
反向继承的救场
此时突然想到,属性代理本质上是返回了一个全新的 Component,此时原组件的静态属性、生命周期等一系列内容都被屏蔽,导致上层的高阶组件、对组件的操作都拿不到应有的内容。而反向继承恰好可以解决这个问题。
最终的代码将是这样的:addProps = SomeComponent => {
let that = this
return class extends SomeComponent {
render () {
return <SomeComponent {...this.props} xxx={that.xxx}/> // u can add more props here
}
}
}
此时需要注意的是,这里我们利用反向继承是为了保留 SomeCompoennt 所有的内容,同时为了满足我们注入 props 的目的,只好不再调用 super.render(), 此时我们借用了属性代理的方式返回了父类组件,因此还得手动传入 props (尴尬)。
结语
当然,如果你不想用官方提供的 navigationbar,不论是反向继承还是属性代理就已经无所谓了,此时只需要在 StackNavigator 配置文件里加上 headerMode: ‘none’ 隐藏默认导航栏即可。