高阶函数
高阶函数的维基百科定义:至少满足以下条件之一
- 接受一个或多个函数作为输入
- 输出一个函数
- JavaScript中比较常见的filter、map、reduce都是高阶函数
高阶组件
高阶组件的英文是 Higher-Order Components,简称为 HOC。官方的定义:高阶组件是参数为组件,返回值为新组件的函数。
我们可以进行如下的解析:
- 首先, 高阶组件本身不是一个组件,而是一个函数
- 其次,这个函数的参数是一个组件,返回值也是一个组件
我们来看个案例,类似这样。
函数接收组件得到一个新的组件。接收到的变量是个函数。
下面就是一个高阶组件
我们在浏览器中查看
如果我们想让名字更灵活一些。
在ES6中,如果我们是一个类表达式,类名可以不写。函数也是一样的。
我们简化成这样
整个样子我们去浏览器看一下(Anoymous是匿名,不知道的意思)
我们也可要自己定义名字(用的比较少)
如果我们要传入属性。记得在高阶组件处获取。
我们也可以在高阶组件中定义函数式组件。
高阶组件不是React的API,它其实是一种设计模式。
高阶组件并不是React API的一部分,它是基于React的 组合特性而形成的设计模式。高阶组件在一些React第三方库中非常常见。比如redux中的connect。比如react-router中的withRouter。
高阶组件的作用(应用场景)
从上面的例子我们很容易发现,高阶组件的作用之一就是劫持。 我们具体来演示一下,它的作用。
应用一:增强props
如果我们想给它们都新增一个属性比如说region,那么我每个都要这样写。
这样我们就可以定义一个高阶组件。
import React, { PureComponent } from 'react'
//定义一个高阶组件
function enhanceRegionProps(WrappedComponent){
return props => { //返回函数式组件
return <WrappedComponent {...props} region = "China"></WrappedComponent>
}
}
class Home extends PureComponent{
render(){
return(
<h2>Home:{`昵称:${this.props.nickname} 等级为:${this.props.level} 区域为:${this.props.region}`}</h2>
)
}
}
const EnhanceHome = enhanceRegionProps(Home)
class About extends PureComponent{
render(){
return(
<h2>Home:{`昵称:${this.props.nickname} 等级为:${this.props.level} 区域为:${this.props.region}`}</h2>
)
}
}
const EnhaceAbout = enhanceRegionProps(About)
export default class App extends PureComponent {
render() {
return (
<div>
App
<EnhanceHome nickname = "harry" level = {90}/>
<EnhaceAbout nickname = "turman" level = {99}/>
</div>
)
}
}
还有个增强的场景(在开发中运用的比较多):
我们想通过Context来进行共享
有没有好办法呢?当然有,就是用高阶组件。
import React, { PureComponent,createContext } from 'react'
//定义一高阶组件
function withUser(WrappedComponent){
return props => {
return(
<UserContext.Consumer>
{
user => {
return <WrappedComponent {...props} {...user}></WrappedComponent>
}
}
</UserContext.Consumer>
)
}
}
//创建Context
const UserContext= createContext({
nickname:'noname',
level:-1,
region:'China'
})
class Home extends PureComponent{
render(){
return(
<UserContext.Consumer>
{
user => {
return <h2>Home:{`昵称:${user.nickname} 等级为:${user.level} 区域为:${user.region}`}</h2>
}
}
</UserContext.Consumer>
)
}
}
class About extends PureComponent{
render(){
return(
<h2>Home:{`昵称:${this.props.nickname} 等级为:${this.props.level} 区域为:${this.props.region}`}</h2>
)
}
}
const UserHome = withUser(Home) //使用高阶组件
const UserAbout = withUser(About)
export default class App extends PureComponent {
render() {
return (
<div>
<UserContext.Provider value={ {nickname:'harry',level:90,region:'Korean' } }>
<UserHome />
<UserAbout />
</UserContext.Provider>
</div>
)
}
}
应用二:渲染判断鉴权
我们想实现这样一个场景,如果isLogin为true,就显示CartPage,反之显示LoginPage。也就是说我们要做鉴权操作。
如果每个页面都要做这种鉴权判断,非常不合适
我们可以这样做(这样也就是劫持jsx):
import React, { PureComponent } from 'react'
function withAuth(WrappedComponent){
return props => {
const {isLogin} = props
if(isLogin){
return <WrappedComponent {...props} />
} else {
return <LoginPage></LoginPage>
}
}
}
class CartPage extends PureComponent{
render(){
return <h2>CartPage</h2>
}
}
class LoginPage extends PureComponent {
render() {
return <h2>LoginPage</h2>
}
}
const AuthCartPage = withAuth(CartPage)
export default class App extends PureComponent {
render() {
return (
<div>
<AuthCartPage isLogin = {true} />
</div>
)
}
}
应用三:生命周期劫持
我们有这样一个需求。我们想知道把Home组件渲染出来花费的事件,并把它打印出来。
import React, { PureComponent } from 'react';
class Home extends PureComponent {
//即将渲染获取一个时间 beginTime
componentWillMount(){ //这个是即将挂载的意思,这个生命周期函数已经不用了
this.beginTime = Date.now()
}
render() {
return <h2>Home</h2>
}
//渲染完成再获取一个时间 endTime
componentDidMount(){ //挂载完毕
this.endTime = Date.now()
const interval = this.endTime - this.beginTime;
console.log(`Home渲染时间: ${interval}`)
}
}
class About extends PureComponent {
render() {
return <h2>About</h2>
}
}
export default class App extends PureComponent {
render() {
return (
<div>
<Home />
<About />
</div>
)
}
}
我们先用着,主要是为了案例演示。
如果我们想把相似的渲染时间都封装起来,早期可以使用mixin
,React推荐使用高阶组件。现在已经转向hooks了。
我们采用高阶组件。
import React, { PureComponent } from 'react';
function withRenderTime(WrappedComponent) {
return class extends PureComponent {
componentWillMount() { //这个是即将挂载的意思,这个生命周期函数已经不用了
this.beginTime = Date.now()
}
//渲染完成再获取一个时间 endTime
componentDidMount() { //挂载完毕
this.endTime = Date.now()
const interval = this.endTime - this.beginTime;
// 所有类都有.name属性,就是类的名字
console.log(`${WrappedComponent.name}渲染时间: ${interval}`)
}
render(){
return <WrappedComponent {...this.props}></WrappedComponent>
}
}
}
class Home extends PureComponent {
//即将渲染获取一个时间 beginTime
render() {
return <h2>Home</h2>
}
}
class About extends PureComponent {
render() {
return <h2>About</h2>
}
}
const TimeHome = withRenderTime(Home)
const TimeAbout = withRenderTime(About)
export default class App extends PureComponent {
render() {
return (
<div>
<TimeHome/>
<TimeAbout />
</div>
)
}
}
高阶函数的意义
我们会发现利用高阶组件可以针对某些React代码进行更加优雅的处理。
-
其实早期的React有提供组件之间的一种复用方式是mixin,目前已经不再建议使用
- Mixin 可能会相互依赖,相互耦合,不利于代码维护
- 不同的Mixin中的方法可能会相互冲突
- Mixin非常多时,组件是可以感知到的,甚至还要为其做相关处理,这样会给代码造成滚雪球式的复杂性
-
当然,HOC也有自己的一些缺陷
- HOC需要在原组件上进行包裹或者嵌套,如果大量使用HOC,将会产生非常多的嵌套,这让调试变得非常困难
- HOC可以劫持props,在不遵守约定的情况下也可能造成冲突
-
Hooks的出现,是开创性的,它解决了很多React之前的存在的问题
- 比如this指向问题、比如hoc的嵌套复杂度问题等等