阅读这篇文章之前,可以先拜读一下这篇文章 React 进阶之高阶组件 了解一下高阶组件的概念及几种使用方式。

高阶函数科普

引自 React 进阶之高阶组件

什么是高阶函数

我们都知道高阶函数是什么, 高阶组件其实是差不多的用法,只不过传入的参数变成了react组件,并返回一个新的组件.
A higher-order component is a function that takes a component and returns a new component.

什么是反向继承

//IIHOC
import React from 'react';

const iiHoc = WrappedComponent => class extends WrappedComponent {
render() {
console.log(this.state, 'state');
return super.render();
}
}

export default 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 => {
return class extends SomeComponent {
render () {
return <SomeComponent {...this.props} /> // u can add more props here
}
}
}

最为核心的就是以上代码了,我们可以在给 StackNavigator 设置页面参数(这里我是通过 getScreens 方法来生成的)加入页面 class 的时候,我们可以通过高阶组件生成一个新的组件。

最开始版本的代码是这样的

addProps = SomeComponent => {
return class extends Component {
render () {
return <SomeComponent {...this.props} /> // u can add more props here
}
}
}

使用的是属性代理模式,需要做的就两点:

  1. 将高阶组件所收到的所有 props 照搬传递给 SomeComponent
  2. 将我们想要添加的属性可以像如下方式添加
    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’ 隐藏默认导航栏即可。

donation