Open diandiandida opened 4 years ago
什么是高阶组件 高阶组件是react中的一个概念,实质来讲,高阶组件就是一个高阶函数
高阶函数
什么是高阶函数,就是满足以下任意一个条件:
可以传入另一个函数作为参数(比如filter、map)
可以返回一个函数(比如bind)
日常工作中,我们在不知不觉中就已经使用了高阶函数
const arr = [1,2,3,4,5,6]; const square = d => d ** 2; arr.map(square) // [1, 4, 9, 16, 25, 36];
在这个例子中,我们的map函数通过传入的方法square实现了数组的二次方。 另一个我们经常会用到的例子:
<ul> {list ? list.map((item, index) => { return <li>{item.name}</li> })} </ul>
由这些高阶函数我们可以明白,其实高阶组件是一种用于复用组件逻辑的高级技术,它并不是 React API的一部分,而是从React 演化而来的一种模式。具体地说,高阶组件就是:接收一个组件并返回另外一个新组件的函数,并且会继承传入组件(state,props,生命周期,render)。
模式
用来做什么 简单来讲,提取重复代码,处理相同逻辑 高阶组件还有更多的功能:
提取重复代码,处理相同逻辑
操纵prop
// 操作props // 可以对原组件的props进行增删改查,通常是查找和增加,删除和修改的话,需要考虑到`不能破坏原组件`。 function Hoc(WrappedComponent) { return class extends React.Component { render() { const {userInfo,...otherprops} = this.props; return <WrappedComponent {...otherprops} /> } } }
包装组件
// 将组件包裹起来,为了封装样式、布局等目的 function HOC(WrappedComponent) { return class PP extends React.Component { render() { return ( <div style={{display: 'block'}}> <WrappedComponent {...this.props}/> </div> ) } } }
操纵生命周期和state
// 操纵组件的方法 function HOC(WrappedComponent) { return class ExampleEnhance extends WrappedComponent { ... componentDidMount() { super.componentDidMount(); } componentWillUnmount() { super.componentWillUnmount(); } render() { ... return super.render(); } } }
这里我们可以看到,我们的高阶组件继承了传入组件(一般的高阶组件是继承react的component),并且在render函数中return的是super.render(),从而实现了继承反转。因此高阶组件就可以获取到传入组件的生命周期钩子和方法。
这里举一个简单的例子: 我们先创建一个被包裹的组件:
import React, { Component } from "react"; export default class ComponentChild extends Component { constructor(props) { super(props); this.state = { message: "我是ComponentChild中的message" }; } componentDidMount() { console.log("ComponentChild Did Mount"); } clickComponent() { console.log("ComponentChild click"); } render() { return <div>{this.state.message}</div>; } }
这里我们会返回一个信息,信息存在组件的state中,并且有一个clickComponent方法和一个生命周期方法,会在控制台打印信息。
接下来实现具有继承反转能力的高阶组件:
import React from "react"; //这样的方式,外部组件的 state 可以将被继承组件的 state 和 钩子函数彻底覆盖掉。同时,外部组件也可以调用被继承组件的方法。 const messageOtherHoc = WrappedComponent => { return class extends WrappedComponent { constructor(props) { super(props); this.state = { message: "我是messageOtherHoc中的message" }; } componentDidMount() { console.log("messageOtherHoc componentDidMount"); this.clickComponent(); } render() { return ( <div> <div onClick={this.clickComponent}>messageOtherHoc 点击我</div> <div> <div>{super.render()}</div> </div> </div> ); } }; }; export default messageOtherHoc;
高阶组件中,我们会定义state,生命周期函数,并且在div中设置点击事件,调用this.clickComponent方法,显然,我们高阶组件中并没有写这样的方法;最后我们会返回传入组件的render函数,通过super找到传入组件
然后我们去调用这个高阶组件:
const Message2 = MessageOtherHoc(ComponentChild); <Message2 />
打印台信息:
显而易见,高阶组件覆盖了传入组件的生命周期方法并且调用了传入组件的clickComponent方法。
这就意味着这样的高阶组件可以访问到WrappedComponent的state,props,生命周期和render方法。如果在高阶组件中定义了与WrappedComponent同名方法,将会发生覆盖,如果需要调用他们,我们就必须手动通过super进行调用。就像例子中那样。
如何创建一个高阶组件
一个简单易懂的例子,命名和代码可能不是规范的写法,但是可以说明问题 用于返回Tips相同的展示:
import React, { Component } from "react"; function withToolTipsSubscription(WrappedComponent) { return class withToolTipsSubscription extends Component { render() { const { tipsType, ...otherProps } = this.props; return ( <fieldset> <legend>我是一个 {tipsType.type} 组件</legend> <p> <span>提示时间:{tipsType.date}</span> </p> <p> <span>提示:</span> </p> <span>WrappedComponent is:</span> <WrappedComponent {...otherProps} /> </fieldset> ); } }; } export default withToolTipsSubscription;
我们的Tip可以拥有一个共同的标题,提示时间和一些文字
在这里面我们用到了操纵props,并把props继续传递给了我们的参数组件:
const { tipsType, ...otherProps } = this.props; <WrappedComponent {...otherProps} />
AskToolTip.js
用于展示一个一个带有两个按钮的Tips
两个按钮可以和父组件进行交互,直接调用 this.props 调用父组件方法即可,无需其他操纵
import React, { Component } from "react"; import ToolTips from "./ToolTips"; class AskToolTips extends Component { constructor(props) { super(props); this.handleConfirm = this.handleConfirm.bind(this); this.handleCancel = this.handleCancel.bind(this); this.confirmIndex = 0; this.cancelIndex = 0; } handleConfirm() { this.props.onConfirm((this.confirmIndex += 1)); } handleCancel() { this.props.onCancel((this.cancelIndex += 1)); } render() { return ( <div style={{ borderWidth: "1px", borderColor: "#d3d3d3", borderStyle: "solid" }} > <div> <span>There are somthing will ask with you:</span> <p> <span>[ ConfirmTimes:{this.props.confirmTimes} ]</span> <span>[ ConfirmTimes:{this.props.cancelTimes} ]</span> </p> </div> <button onClick={this.handleConfirm}>confirm</button> <button onClick={this.handleCancel}>cancel</button> </div> ); } } export default ToolTips(AskToolTips);
这个组件的写法和平常的组件大致无二,唯一的的区别在于 export 的内容
export default ToolTips(AskToolTips);
引入并返回我们包裹的高阶组件
ErrorToolTips.js
和上面的组件类似,但是这里只接收父组件传递的内容进行展示:
import React, { Component } from "react"; import ToolTips from "./ToolTips"; class ErrorToolTips extends Component { render() { return ( <div style={{ borderWidth: "1px", borderColor: "#d3d3d3", borderStyle: "solid" }} > <span>{this.props.msg.str}</span> </div> ); } } export default ToolTips(ErrorToolTips);
HocExample.js
组件的具体调用:
import React, { Component } from "react"; import AskToolTips from "./HocEx/AskToolTips"; import ErrorToolTips from "./HocEx/ErrorToolTips"; export default class HocExample extends Component { constructor(props) { super(props); this.handlerAskConfirm = this.handlerAskConfirm.bind(this); this.handlerAskCancel = this.handlerAskCancel.bind(this); this.state = { confirmTimes: 0, cancelTimes: 0, date: new Date().toLocaleString(), msg: "我是一个错误提示框" }; } handlerAskConfirm(index) { console.log("handlerAskConfirm:" + index); this.setState((state, props) => { const msg = "睡在我下铺的兄弟Ask我:" + (state.cancelTimes + index) + "次!,Confirm:" + index + "次!Cancel:" + state.cancelTimes + "次!"; return { confirmTimes: index, msg: msg, date: new Date().toLocaleString() }; }); } handlerAskCancel(index) { console.log("handlerAskCancel:" + index); this.setState((state, props) => { const msg = "睡在我下铺的兄弟Ask我:" + (state.confirmTimes + index) + "次!,Confirm:" + state.confirmTimes + "次!Cancel:" + index + "次!"; return { cancelTimes: index, msg: msg, date: new Date().toLocaleString() }; }); } render() { return ( <fieldset> <legend>高阶组件实例</legend> <ErrorToolTips tipsType={{ type: "ErrorToolTips", date: this.state.date }} msg={{ str: this.state.msg }} /> <AskToolTips tipsType={{ type: "AskToolTips", date: this.state.date }} confirmTimes={this.state.confirmTimes} cancelTimes={this.state.cancelTimes} onConfirm={e => this.handlerAskConfirm(e)} onCancel={e => this.handlerAskCancel(e)} /> </fieldset> ); } }
我们直接调用AskToolTips和ErrorToolTips,并且传递props的方式和普通的组件没有上面不同。
效果:
高阶函数的具体应用 这里我就结合我们的 ReactDOM.createPortal 具体说一下:
Portal是一种将子节点渲染到存在于父组件以外的 DOM 节点的方案,相信大家都有所了解,这里也不做赘述,主要是通过 ReactDOM.createPortal 方法,第一个参数是任何可以渲染的react元素或者组件。第二个参数是作为渲染第一个参数的容器DOM 元素,最终生成的节点将会挂载到此dom元素节点上,是为了解决dom层级的问题
import React, { Component } from "react"; import ReactDOM from "react-dom"; const withPortal = WrappedComponent => { class AddPortal extends Component { constructor(props) { super(props); this.el = this.getDiv(); } componentWillUnmount() { document.body.removeChild(this.el); } getDiv() { const div = document.createElement("div"); const appendNode = this.props.appendNode || document.body; appendNode.appendChild(div); return div; } render() { return ReactDOM.createPortal( <WrappedComponent {...this.props} />, this.el ); } } return AddPortal; }; export default withPortal;
总结:高阶组件是一个函数,传递一个组件作为参数,并且会返回一个组件;在高阶组件中我们会对组件中公共的部分进行提取、抽象,可以对props,state进行控制,可以调用组件的生命周期方法,render方法,可以赋予组件一些具有共性的信息,但是我们不能对作为参数的组件进行修改,要保持他的纯函数的特性;通过对一系列组件的加工处理,可以大大提高我们的开发速度和销量,提高代码质量和组件易用性。
纯函数
纯函数:如果函数的调用参数相同,则永远返回相同的结果。它不依赖于程序执行期间函数外部任何状态或数据的变化,必须只依赖于其输入参数
tips:不要问我为什么不直接引用组件到高阶组件中,而不通过参数进行传递,如果这样的话,我们写每一个组件都要copy一份新的函数进行开发,何必呢
什么是高阶组件 高阶组件是react中的一个概念,实质来讲,高阶组件就是一个
高阶函数
什么是高阶函数,就是满足以下任意一个条件:
可以传入另一个函数作为参数(比如filter、map)
可以返回一个函数(比如bind)
日常工作中,我们在不知不觉中就已经使用了高阶函数
在这个例子中,我们的map函数通过传入的方法square实现了数组的二次方。 另一个我们经常会用到的例子:
由这些高阶函数我们可以明白,其实高阶组件是一种用于复用组件逻辑的高级技术,它并不是 React API的一部分,而是从React 演化而来的一种
模式
。具体地说,高阶组件就是:接收一个组件并返回另外一个新组件的函数,并且会继承传入组件(state,props,生命周期,render)。用来做什么 简单来讲,
提取重复代码,处理相同逻辑
高阶组件还有更多的功能:操纵prop
包装组件
操纵生命周期和state
这里我们可以看到,我们的高阶组件继承了传入组件(一般的高阶组件是继承react的component),并且在render函数中return的是super.render(),从而实现了继承反转。因此高阶组件就可以获取到传入组件的生命周期钩子和方法。
这里举一个简单的例子: 我们先创建一个被包裹的组件:
这里我们会返回一个信息,信息存在组件的state中,并且有一个clickComponent方法和一个生命周期方法,会在控制台打印信息。
接下来实现具有继承反转能力的高阶组件:
高阶组件中,我们会定义state,生命周期函数,并且在div中设置点击事件,调用this.clickComponent方法,显然,我们高阶组件中并没有写这样的方法;最后我们会返回传入组件的render函数,通过super找到传入组件
然后我们去调用这个高阶组件:
打印台信息:
显而易见,高阶组件覆盖了传入组件的生命周期方法并且调用了传入组件的clickComponent方法。
这就意味着这样的高阶组件可以访问到WrappedComponent的state,props,生命周期和render方法。如果在高阶组件中定义了与WrappedComponent同名方法,将会发生覆盖,如果需要调用他们,我们就必须手动通过super进行调用。就像例子中那样。
如何创建一个高阶组件
一个简单易懂的例子,命名和代码可能不是规范的写法,但是可以说明问题 用于返回Tips相同的展示:
我们的Tip可以拥有一个共同的标题,提示时间和一些文字
在这里面我们用到了操纵props,并把props继续传递给了我们的参数组件:
AskToolTip.js
用于展示一个一个带有两个按钮的Tips
两个按钮可以和父组件进行交互,直接调用 this.props 调用父组件方法即可,无需其他操纵
这个组件的写法和平常的组件大致无二,唯一的的区别在于 export 的内容
引入并返回我们包裹的高阶组件
ErrorToolTips.js
和上面的组件类似,但是这里只接收父组件传递的内容进行展示:
HocExample.js
组件的具体调用:
我们直接调用AskToolTips和ErrorToolTips,并且传递props的方式和普通的组件没有上面不同。
效果:
高阶函数的具体应用 这里我就结合我们的 ReactDOM.createPortal 具体说一下:
Portal是一种将子节点渲染到存在于父组件以外的 DOM 节点的方案,相信大家都有所了解,这里也不做赘述,主要是通过 ReactDOM.createPortal 方法,第一个参数是任何可以渲染的react元素或者组件。第二个参数是作为渲染第一个参数的容器DOM 元素,最终生成的节点将会挂载到此dom元素节点上,是为了解决dom层级的问题
总结:高阶组件是一个函数,传递一个组件作为参数,并且会返回一个组件;在高阶组件中我们会对组件中公共的部分进行提取、抽象,可以对props,state进行控制,可以调用组件的生命周期方法,render方法,可以赋予组件一些具有共性的信息,但是我们不能对作为参数的组件进行修改,要保持他的
纯函数
的特性;通过对一系列组件的加工处理,可以大大提高我们的开发速度和销量,提高代码质量和组件易用性。tips:不要问我为什么不直接引用组件到高阶组件中,而不通过参数进行传递,如果这样的话,我们写每一个组件都要copy一份新的函数进行开发,何必呢