一个高阶组件就是一个函数,这个函数接受一个组件作为输入,然后返回一个新的组件作为结果,而且,返回的新组件拥有了输入组件所不具有的功能。我们可以这么打比方,每个组件最后都返回了一个jsx,而jsx实质上一个对象,相当于我们传入一个对象,最后返回了一个新对象,它具有参数对象不具有的功能
// 删除user这个props
function removeUserProp(WrapperComponent){
return class WrappingComponent extends React.Component{
render(){
const {user,...otherProps} = this.props
return <WrapperComponent {...otherProps}>
}
}
}
定义高阶组件的意义何在呢?
首先,重用代码 有时候很多 React 组件都需要公用同样一个逻辑,比如说 react-redux中容器组件的部分,没有必要让每个组件都实现一遍 shouldComponentUpdate 这些生命周期函数,把这部分逻辑提取出来,利用高阶组件的方式应用出去,就可以减少很多组件的重复代码
其次,修改现有 React 组件的行为 有些现成 React 组件并不是开者自己开发的,来自于第3方,或者,即使是我们自己开发的,但是我们不想去触碰这些组件的内部逻辑,这时候高阶组件有了用武之地 通过一个独立于原有组件的函数,可以产生新的组件,对原有组件没有任何侵害。
根据返回的新组件和传人组件参数的关系,高阶组件的实现方式可以分为两大类:
代理方式的高阶组件
继承方式的高阶组件
代理方式的高阶组件
上面的 removeUserProp 例子就是一个代理方式的高阶组件,特点是返回的新组件类直接继承自 React. Component 新组件扮演的角色是传入参数组件的一个“代理”,在新组建的 render 函数中,把被包裹组件渲染出来,除了高阶组件自己要做的工作,其余功能全都转手给了被包裹的组件。
代理方式的高阶组件,可以应用在下列场景中:
操纵 prop
访问 ref
抽取状态
包装组件
- 操纵 prop
// 添加新props
const addNewProp = (WrapperComponent,newProps) => {
return class WrappingComponent extends React.Component{
render(){
return <WrapperComponent {...this.props} {...newProps}>
}
}
}
- 访问 ref
// 获取refs
const refsHOC = (WrapperComponent) => {
return class HOCComponent extends React.Component{
constructor(){
super(...arguments)
this.linkRef = this.linkRef.bind(this)
}
linkRef(wrappedInstance){
this._root = wrappedInstance
}
render(){
const props = {...this.props,ref:this.linkRef}
return <WrapperComponent {...props}>
}
}
}
- 抽取状态
const doNothing = () => ({})
function connect(mapStateToProps=doNothing,mapDispatchToProps=doNothing){
return function(WrapperComponent){
class HOCComponent extends React.Component{
//定义声明周期函数
constructor(){
super(...arguments)
this.onChange = this.onChange.bind(this)
this.store = {}
}
componentDidMount(){
this.context.store.subscribe(this.onChange)
}
componentWillUnMount(){
this.context.store.unsubscribe(this.onChange)
}
onChange(){
this.setState({})
}
render(){
const store = this.context.store
const newProps = {
...this.props,
...mapStateToProps(store.getState()),
...mapDispatchToProps(store.dispatch())
}
return <WrapperComponent {...newProps}>
}
}
HOCComponent.contextTypes = {
store:React.PropTypes.object
}
return HOCComponent
}
}
function getDisplayName(WrappedComponent){
return WrappedComponent.displayName || WrappedComponent.name || Component
}
- 包装组件
const styleHOC = (WrappedComponent,style) => {
return class HOCComponent extends React.Component{
render(){
return(){
<div style={style}>
<WrappedComponent {...this.props} />
</div>
}
}
}
}
继承方式的高阶组件
继承方式的高阶组件采用继承关系关联作为参数的组件和返回的组件,假如传入的组件参数是 WrComponeappednt ,那么返回的组件就直接继承自 WrappedComponent
function removeUserProp(WrapperComponent){
//继承于参数组件
return class NewComponent extends WrapperComponent{
render(){
const {user,...otherProps} = this.props
this.props = otherProps
//调用WrapperComponent的render方法
// 只是一个render函数,不是一整个生命周期
return super.render()
}
}
}
继承方式的高阶组件可以应用于下列场景:
操纵 prop
操纵生命周期函数
- 操纵 prop
const modifyPropsHOC = (WrappedComponent) => {
return class NewComponent extends WrappedComponent{
render(){
const elements = super.render()
const newStyle = {
color:(elements && elements.type === 'div') ? 'red' : 'green'
}
const newProps = {...this.props,style:newStyle}
return React.cloneElement(elements,newProps,elements.props.children)
}
}
}
- 操纵生命周期函数:修改参数组件的生命周期
const onlyForLoggedHOC = (WrappedComponent) => {
return class NewComponent extends WrappedComponent{
render(){
if(this.props.loggedIn){
return super.render()
}else{
return null
}
}
}
}
const cacheHOC = (WrappedComponent) => {
return class NewComponent extends WrappedComponent{
shouldComponentUpdate(nextProps,nextState){
return !nextProps.userCache
}
}
}
以函数为子组件
高阶组件并不是唯一可用于提高 React 组件代码重用的方法 在上 节的介绍中可以体会到,高阶组件扩展现有组件功能的方式主要是通过 props ,增加 props 或者减少props ,或者修改原有的 props 以代理方式的高阶组件为例,新产生的组件和原有的组件说到底是两个组件,是父子关系,而两个 React 组件之间通信的方式自然是 props 因为每个组件都应该通过 propTypes 声明自己所支持的 props 高阶组件利用原组件的 props扩充功能,在静态代码检查上也占优势>
但是,高阶组件也有缺点,那就是对原组件的 props 有了固化的要求 也就是说,能不能把一个高阶组件作用于某个组件 ,要先看一下这个组件 是不是能够接受高阶组件传过来的 props ,如果组件 并不支持这些 props ,或者对这些 props 的命名有不同,或者使用方式不是预期的方式,那也就没有办法应用这个高阶组件。
“以函数为子组件”的模式就是为了克服高阶组件的这种局限而生的 在这种模式下,实现代码重用的不是一个函数,而是一个真正的 React 组件,这样的 React 组件有个特点,要求必须有子组件的存在,而且这个子组件必须是一个函数 在组件实例的生命周期函数中, this props children 引用的就是子组件, render 函数会直接this.props.children当做函数来调用,得到的结果就可以作为 render 返回结果的一部分
class CountDown extends React.Component{
constructor(){
super(...arguments)
this.state = {count:this.props.startCount}
}
componentDidMount(){
this.intervalHandle = setInterval(() => {
const newCount = this.state.count - 1
if(newCount >= 0){
this.setState({count:newCount})
}else{
window.clearInterval(this.intervalHandle)
}
},1000)
}
componentWillUnMount(){
if(this.intervalHandle){
window.clearInterval(this.intervalHandle)
}
}
render(){
return this.props.children(this.state.count)
}
}
CountDown.propTypes = {
children:PropTypes.func.isRequired,
startCount:PropTypes.number.isRequired
}
<CountDown>
{
(count) => <div>count</div>
}
</CountDown>
<CountDown>
{
(count) => <div>{count > 0 ? count : 'happy new year'}</div>
}
</CountDown>
<CountDown>
{
(count) => <Bomb countdown={count}>
}
</CountDown>
以函数为子组件这种方法非常合适做动画,作为子组件的函数主要专注于参数来渲染就可以了;但是它难以做性能优化,因为子组件是函数,没有生命周期,无法利用shouldComponentUpdate