React 高阶组件
一. 高阶组件的意义
1. 代码复用 : 高阶组件的基本功能,提取多个组件相似度高的部分,在通过高阶组件扩展。
2. 条件渲染: 控制组件的渲染逻辑, 常见case: 鉴权
3. 生命周期捕获/劫持: 借助父子组件生命周期规则捕获子组件的生命周期;
二. 高阶组件遵循的一些原则
1. 不要修改原始组件
2. props保持一致
3. 保持可组合性
4. displayName 为了方便调试,最常见的高阶组件命名方式是将子组件名字包裹起来。
5. 不要render 方法内部使用高阶组件 (render中的高阶组件会在每次render时重新mount, 之前组件内部的state也会丢失)
三. 使用方法对比
高阶组件使用有几种不同的方式,可以从如下几个方面来分析他们之间的差异。
一个React组件包含以下几个重要部分:
· props · state · ref ·生命周方法 ·static 方法 · React元素树
注: 目前官方推荐ref属性接收一个回调函数,这个函数执行的时机为:
1. 组件被挂载后,回调函数被立即执行,回调函数的参数为该组件的具体实例;
2. 组件被卸载或者原有的ref属性本身发生变化时,回调也会被立即执行,此时回调函数参数为null, 以确保内存不泄露。
所以不同方式的对比可以从以下几个方面进行(元组件即传入组件):
1. 原组件所在位置: 如能否被包裹或包裹其它组件;
2. 能否读取到或操作员组件的props;
3. 能否读取,操作(编辑,删除)元组件的state;
4. 能否通过ref访问到原组件中的dom元素;
5. 是否影响原组件某些生命周期等方法;
6. 是否取到原组件static方法;
7. 能否劫持原组件生命周期方法;
8. 能否渲染劫持;
四. HOC Demo举例
如下代码定义了一个子组件及多个不同类型的HOC组件来测试,对子组件的内部属性,方法及状态等的访问情况。
/**
* 高阶组件Demo
*/
import React, { Component } from 'react'
class HOCDemo extends Component{
static sayHello() {
console.log('hello from hocdemo static....')
}
constructor(props) {
super(props)
this.state={
name: "HOC_DEMO",
age: 66
}
console.log('HOCDemo Son constructor')
this.focus = this.focus.bind(this)
}
componentWillMount(){
console.log('HOCDemo Son componentWillMount')
!!this.props.name && this.setState({
age: this.props.age,
name: this.props.name
})
}
componentDidMount() {
console.log('HOCDemo Son componentDidMount')
}
componentWillReceiveProps(nextProps) {
console.log('HOCDemo Son componentWillReceiveProps')
console.log(nextProps)
}
focus(){
this.inputElement.focus()
}
render(){
const outerStyle = {
margin: "auto",
text: "center"
}
const inputStyle = {
border: 0,
padding: '2px'
}
return (
<div style={outerStyle}>
<p>姓名:{this.state.name}</p>
<p>
年龄:
<input
style={inputStyle}
value={this.state.age}
onChange={()=>{}}
ref={(input) => {
this.inputElement = input;
!!this.props.inputRef && this.props.inputRef(input)
}}
/>
</p>
<p>
<input
type="button"
value="focus input"
onClick={this.focus}
/>
</p>
</div>
)
}
}
// HOC:
// 父组件操作子组件里的input (父组件通过传回调函数给子组件,子组件通过该回调数将input给到父组件)
const EnhanceWrapper = (WrappedComponent) => {
let inputElement = null
function handlerClick (){
inputElement.focus()
}
function wrappedComponentStaic(){
WrappedComponent.sayHello()
}
return props => (
<div>
<WrappedComponent
inputRef={(el)=>{ inputElement = el}}
{...props}
/>
<input
type="button"
value="focus子组件input"
onClick={handlerClick}
/>
<input
type="button"
value="调用子组件static"
onClick={wrappedComponentStaic}
/>
</div>
)
}
/* HOC : 返回一个类
√ 原组件所在位置(能否被包裹或包裹其他组件)
√ 能否取到或操作原组件的props
乄 能否取到或操作state
乄 能否通过ref访问到原组件中的dom元素
√ 是否影响原组件生命周期等方法
√ 是否取到原组件static方法
X 能否劫持原组件生命周期
乄 能否渲染劫持
*/
const EnhanceWrapperClass = (WrappedComponent) => {
return class WrapperComponent extends Component{
static wrappedComponentStaic(){
WrappedComponent.sayHello()
}
constructor(props) {
super(props)
console.log('wrappercomponent parent constructor')
this.handleClick = this.handlerClick.bind(this)
}
componentWillMount(){
console.log('WrapperComponent parent componentWillMount')
}
componentDidMount(){
console.log('wrapperComponent parent componentDidMount')
}
handlerClick(){
this.inputElement.focus()
}
render(){
return (
<div>
<WrappedComponent
inputRef={(el)=>{ this.inputElement = el}}
{...this.props}
/>
<input
type="button"
value="focus子组件input"
onClick={this.handlerClick.bind(this)}
/>
<input
type="button"
value="调用子组件static"
onClick={this.constructor.wrappedComponentStaic}
/>
</div>
)
}
}
}
/* HOC:
继承原组件后,返回一个新class component
// 此种方式最大特点是允许 HOC 通过 this 访问到 WrappedComponent,所以可以读取和操作state/ref/生命周期方法
√ 原组件所在位置(能否被包裹或包裹其他组件)
√ 能否取到或操作原组件的props
√ 能否取到或操作state
√ 能否通过ref访问到原组件中的dom元素
√ 是否影响原组件生命周期等方法
√ 是否取到原组件static方法
√ 能否劫持原组件生命周期
√ 能否渲染劫持
*/
const EnhanceWrapperExtendSon = (WrappedComponent) => {
return class WrapperComponent extends WrappedComponent {
constructor(props){
super(props)
console.log('WrapperComponent parent constructor')
console.log("HOC parent this", this)
}
componentDidMount() {
// 通过super可以调用继承类的内部方法 (这里不能用this 的原因是, 内部自己调用自己没有推出条件,造成死循环)
super.componentDidMount()
// 父类自己的DidMount 输出
console.log('WrapperComponent parent componentDidMount')
}
handlerClick(){
// 这里通过this 可以拿到所继承类的内部属性(inputElement)及方法
this.inputElement.focus()
}
render(){
return (
<div>
{super.render()}
<p>姓名: {this.state.name}</p>
<input
type="button"
value="focus子组件input"
onClick={this.handlerClick.bind(this)}
/>
<input
type="button"
value="调用子组件static"
onClick={WrapperComponent.sayHello}
/>
</div>
)
}
}
}
const WrapperComponent = EnhanceWrapperExtendSon(HOCDemo)
export default WrapperComponent
五. 现实应用场景实战
未完待续.....