高阶组件是 React 中复用组件逻辑的一种技巧,高阶组件是一个函数,接收需要包装的组件,返回值为增强后的组件。
实现思路:
高阶组件内部创建一个类组件,在这个类组件中去提供复用状态的逻辑代码,通过 prop 将复用的状态组件传递给被包装组件。
创建高阶组件步骤:
创建一个函数,名称约定以 with
开头
给函数设置形参用于接收传过来的组件,参数以大写字母开头(作为参数组件使用)
在函数内部创建一个类组件,在类组件中提供复用的状态逻辑代码;并将类组件作为返回值返回
在类组件中渲染参数组件,同时将类组件的状态通过 prop 传递给参数组件
调用高阶组件,传入需要增强的组件,通过返回值拿到增强后的组件,并将其渲染到页面中
示例:
import React from 'react'
import ReactDOM from 'react-dom'
import img from './images/tianshi.gif'
// 高阶组件
function withMouse (WrappedComponent) {
// 类组件
class Mouse extends React.Component {
// 状态
state = {
x: 0,
y: 0
}
handleMouseMove = e => {
// 修改状态
this.setState({
x: e.clientX,
y: e.clientY
})
}
render () {
console.log(this.props)
// 将当前组件的 state 传递给传入的需要包装的组件
return <WrappedComponent {...this.state}></WrappedComponent>
}
// 组件挂载后注册鼠标移动事件
componentDidMount () {
window.addEventListener('mousemove', this.handleMouseMove)
}
// 卸载组件时解绑鼠标移动事件
componentWillUnmount () {
window.removeEventListener('mousemove', this.handleMouseMove)
}
}
return Mouse
}
// 测试高阶组件1
const Test = props => {
console.log('Test:', props)
return (
< p > x: {props.x}, y: {props.y}</p >
)
}
// 测试高阶组件2
const Cat = props => (
<img
src={img}
alt="cat"
style={{
position: 'absolute',
top: props.y - 64,
left: props.x - 64
width: 128,
height: 128
}} />
)
// 调用高阶组件,获取返回后的包装组件
const MouseTest = withMouse(Test)
const MouseCat = withMouse(Cat)
// 根组件
class App extends React.Component {
render () {
return (
<div>
<h1>App 组件</h1>
{/* 使用包装后的组件 */}
<MouseTest www="123456"></MouseTest>
<MouseCat></MouseCat>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('root'))
使用高阶组件实现了组件复用但是会存在组件名称相同的问题,这是因为 react 默认使用组件的名称作为 displayName
解决办法:为高阶组件设置 displayName
来区分不同的组件,便于设置调试信息
//设置类组件的 displayName
Mouse.displayName = `WithMouse${getDisplayName(WrappedComponent)}`
// 获取组件名称
function getDisplayName (WrappedComponent) {
return WrappedComponent.displayName || WrappedComponent.name || 'Component'
}
高阶组件不会向下传递 props 所以会存在 props 丢失的问题
解决办法:在类组件渲染 WrappedComponent
时,将 state
和 this.props
一起传递给组件
render () {
console.log('Mouse 组件的 props:', this.props)
// 将当前组件的 props 向下传递
return <WrappedComponent {...this.state} {...this.props}></WrappedComponent >
}
最终完整代码:
import React from 'react'
import ReactDOM from 'react-dom'
import img from './images/tianshi.gif'
// 高阶组件
function withMouse (WrappedComponent) {
// 类组件
class Mouse extends React.Component {
// 状态
state = {
x: 0,
y: 0
}
handleMouseMove = e => {
// 修改状态
this.setState({
x: e.clientX,
y: e.clientY
})
}
render () {
console.log(this.props)
// 将当前组件的 state 和 props传递给传入的需要包装的组件
return <WrappedComponent {...this.state} {...this.props}></WrappedComponent>
}
// 组件挂载后注册鼠标移动事件
componentDidMount () {
window.addEventListener('mousemove', this.handleMouseMove)
}
// 卸载组件时解绑鼠标移动事件
componentWillUnmount () {
window.removeEventListener('mousemove', this.handleMouseMove)
}
}
//设置类组件的 displayName
Mouse.displayName = `WithMouse${getDisplayName(WrappedComponent)}`
// 获取组件名称的函数
function getDisplayName (WrappedComponent) {
return WrappedComponent.displayName || WrappedComponent.name || 'Component'
}
return Mouse
}
// 测试高阶组件1
const Test = props => {
console.log('Test:', props)
return (
< p > x: {props.x}, y: {props.y}</p >
)
}
// 测试高阶组件2
const Cat = props => (
<img
src={img}
alt="cat"
style={{
position: 'absolute',
top: props.y - 64,
left: props.x - 64
width: 128,
height: 128
}} />
)
// 调用高阶组件,获取返回后的包装组件
const MouseTest = withMouse(Test)
const MouseCat = withMouse(Cat)
// 根组件
class App extends React.Component {
render () {
return (
<div>
<h1>App 组件</h1>
{/* 使用包装后的组件 */}
<MouseTest www="123456"></MouseTest>
<MouseCat></MouseCat>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('root'))