react高阶组件的用法

具体而言,高阶组件就是一个函数,且该函数接受一个组件作为参数,并返回一个新的组件

我们需要一个抽象,允许我们在一个地方定义逻辑,并在许多组件之间共享它。这正是高阶组件擅长的地方。

优点:HOC 不会修改传入的组件,也不会使用继承来复制其行为。相反,HOC 通过将组件包装在容器组件中来组成新组件。HOC 是纯函数,没有副作用。

属性代理:高阶组件通过被包裹的React组件来操作props

开发过程中,我们都见过这样的写法,一个Provider组件,传递一个store,在它的子组件内部都可以通过this.props访问到store里面的内容,下面就来分析一下是怎么实现的。

首先介绍一种传递属性的方式,上下文: 

平时我们会看到这样的代码

定义一个store

const store = {
	name: 'ryan',
	age:10
}

定一个Provider组件,定义上下文。我门先通过上下文传递数据。

import React from "react";
import PropTypes from 'prop-types'
class Provider extends React.Component{
	getChildContext () {
		return this.props.store
	}

	/** static childContextTypes = {
		name: PropTypes.string,
		age: PropTypes.number
	} **/ // 目前class还不支持这样的方式写静态属性 ts环境可以这么写


	constructor(props){
		super(props)
		this.state = {
			name: 'provider-user'
		}
	}
	render() {
		return this.props.children
	}
}
// 这样添加静态属性
Provider.childContextTypes = {
    name: PropTypes.string,
	age: PropTypes.number
}

 

定一个BaseUser组件 

import React from "react";
import PropTypes from 'prop-types'


class BaseUser extends React.Component{
   // static contextTypes = {
   //     age: PropTypes.number
   // }
	render() {
		return (
			<div>	
                 {this.context.age}
			</div>
		);
	}
}

BaseUser.contextTypes = {
   age: PropTypes.number
}

export default BaseUser

 定一个BasePost组件

import React from "react";
import PropTypes from 'prop-types'

class BasePost extends React.Component{
    // static contextTypes = {
     //   name: PropTypes.string
    // }
	render() {
		return (
			<div>
				{this.context.name}
			</div>
		);
	}
}

BasePost.contextTypes = {
   name: PropTypes.string
}

export default BasePost

 

import React from "react";
import BaseUser from './components/base'
import BasePost from './components/post'
import Provider from './components/provider'

class App extends React.Component {
   
	render() {
		return (
			<Provider store={store}>
				<div>
					<BaseUser/>
					<BasePost/>
				</div>
			</Provider>
		);
	}
}

这样可以将store里面的数据传递到Provider组件的子组件当中。但是每一子组件都要加一段相似的代码,就是上下文,这样就会很繁琐,我们可以试着把这段代码抽离出来,来定义一个connect组件。一个高阶组件,传递一个组件并返回一个新的组件。

import React from "react";
import PropTypes from 'prop-types'
import Provider from './Provider/index'

const connect = (Com) => {
	class ConnectComponent extends React.Component {
	

		render() {
			return (
				<Com {...this.context}/>
			);
		}
	}
    ConnectComponent.contextTypes = Provider.childContextTypes;
	return ConnectComponent
}

这样,就可以去掉子组件中很类似的部分。提取公共属性,传递给props。

子组件使用一下这个高阶组件,就可以直接props访问状态。

const User = connect(BaseUser)
const Post = connect(BasePost)
import React from "react";


class BasePost extends React.Component{
   componentDidMount() {
        console.log(this.props) //{name: "ryan", age: 10}
    }
	render() {
		return (
			<div>
				{this.props.name}
			</div>
		);
	}
}
export default BasePost
import React from "react";


class BaseUser extends React.Component{
   componentDidMount() {
        console.log(this.props) //{name: "ryan", age: 10}
    }
	render() {
		return (
			<div>	
                 {this.props.age}
			</div>
		);
	}
}

export default BaseUser

可以看到,store中的属性都注入到每一个子组件中

 app组件直接渲染处理后的组件。

class App extends React.Component {
	render() {
		return (
			<Provider store={store}>
				<div>
					<User/>
					<Post/>
				</div>
			</Provider>
		);
	}
}

但是又有一个问题,这样操作会把store内部所有的状态都注入到了所有的子组件中,而有些组件并不会用到这些属性,会增传输成本, 下面就来实现一个函数,让状态的可以有选择的注入到组件中。

实现一个inject组件,这个组件接收一个key,返回一个高阶组件,接受一个组件。这里不能再用上下文,这个函数内部拿不到上下文的值,需要换一种写法灵活应用。我们需要的不只是把一个参数注入到组件,还需要在属性变化的时候更新组件:

import React,{Component} from "react";


// 用于预先 将业务组件,进行数据的封装,便于我们 方便获取数据
let store = {
	name: 'ryan',
	age: 10
}
const inject = key => Com  => {
	class connectComponent extends Component {
        
		constructor(props){
			super(props)
			this.state = {
				[key]: store[key]
			}
		}
                componentDidMount() {
			let that = this
			window.store = new Proxy(store, {
				get: function (target, key, receiver) {
					return Reflect.get(target, key, receiver);
				},
				set: function (target, key, value, receiver) {
					that.setState({
						[key]:value
					})
					return Reflect.set(target, key, value, receiver);
				}
			})
		}
		render() {
			return <Com {...this.state}/>
		}

	}
	return connectComponent
}




export default inject

返回一个新的组件。inject组件内部用proxy监听store的变化。如果改变就更新组件的state,是组件重新渲染,听起来是不是有点像mobx,redux一类的状态管理器。 

我们来实现一下app组件

@inject('age')
class User extends Component{
	render() {
		return <div>{this.props.age}</div>
	}
}
// 相当于 User = inject('age)(User)

class App extends Component{
	render() {
		return <User/>
	}
}


export default App

反向继承: 高阶组件继承于被包裹的React组件

一个高阶组件,继承传入的组件,并返回一个新的组件。

import React,{Component} from "react";

// 用于预先 将业务组件,进行数据的封装,便于我们 方便获取数据
// 反向继承 交互的封装,
const loading = Com  => {
	class LoadingComponent extends Com {
		 constructor (props) {
            super(props)
            this.state = {
                name: '麦乐'
            }
        }
		showLoading(){
			console.log('loading')
		}
		hideLoading(){
			console.log('hide')
		}
	}
	return LoadingComponent
}



@loading
class User extends Component{
 constructor (props) {
            super(props)
            this.state = {
                name: 'maile'
                age: 18
            }
        }
     showLoading() {
        console.log('_loading')
    }
	render() {
		return <div>user</div>
	}

	componentDidMount() {
        console.log(this.state.name) // 麦乐 本组件内状态失效
        console.log(this.state.age) // undefined
		this.showLoading() // loading 本组件内部的同名函数不再生效
		this.hideLoading() // hide
	}
}

class App extends Component{
	render() {
		return <User/>
	}
}


export default App

这是因为loading返回的组件继承了user组件,user组件内部同名属性和方法将会被覆盖。如果想要user组件内部的状态生效,loding高阶组件内部就不能设置状态,使用的时候灵活应用。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值