一. 定义
-
官方定义:参数为组件,返回值为新组件的函数
-
本质:是函数而非组件,是对原有组件进行拦截封装的新组件,本质上是一种设计模式而非React API
md
- 特点:
- 接收一个组件作为参数
- 返回一个新组件
- 对新组件进行拦截和增强
-
调用方式:const EnhancedComponent = higherOrderComponent(WrappedComponent)
-
常见应用:
- Redux中的connect函数(返回高阶组件)
- React Router中的withRouter函数
-
实现原理:
- 结构:接收一个组件作为参数,返回一个新的增强组件
- 命名规范:可通过displayName属性修改组件调试名称
- 继承方式:新组件通常继承自PureComponent以获得性能优化
基础示例
jsx
import React, { PureComponent } from 'react'
// 定义一个高阶组件
function hoc(Cpn) {
// 1.定义类组件
class NewCpn extends PureComponent {
render() {
return <Cpn name="why"/>
}
}
// 设置 displayName动态命名
NewCpn.displayName = `HOC(${Cpn.displayName || Cpn.name || 'Component'})`;
return NewCpn
// 定义函数组件
// function NewCpn2(props) {
// }
// return NewCpn2
}
class HelloWorld extends PureComponent {
render() {
return <h1>Hello World</h1>
}
}
//直接命名
HelloWorld.displayName = 'HelloWorldComponent';
const HelloWorldHOC = hoc(HelloWorld)
export class App extends PureComponent {
render() {
return (
<div>
<HelloWorldHOC/>
</div>
)
}
}
export default App
props增强
js
import { PureComponent } from 'react'
// 定义组件: 给一些需要特殊数据的组件, 注入props
function enhancedUserInfo(OriginComponent) {
class NewComponent extends PureComponent {
constructor(props) {
super(props)
this.state = {
userInfo: {
name: "clare",
level: 1
}
}
}
render() {
return <OriginComponent {...this.props} {...this.state.userInfo}/>
}
}
return NewComponent
}
export default enhancedUserInfo
jsx
import React, { PureComponent } from 'react'
import enhancedUserInfo from './hoc/enhanced_props'
import About from './pages/About'
const Home = enhancedUserInfo(function(props) {
return <h1>Home: {props.name}-{props.level}-{props.banners}</h1>
})
export class App extends PureComponent {
render() {
return (
<div>
<Home banners={["轮播1", "轮播2"]}/>
</div>
)
}
}
export default App
Context共享
js
import { createContext } from "react"
const ThemeContext = createContext()
export default ThemeContext
js
import ThemeContext from "../context/theme_context"
function withTheme(OriginComponment) {
return (props) => {
return (
<ThemeContext.Consumer>
{
value => {
return <OriginComponment {...value} {...props}/>
}
}
</ThemeContext.Consumer>
)
}
}
export default withTheme
jsx
import React, { PureComponent } from 'react'
import withTheme from '../hoc/with_theme'
import ThemeContext from '../context/theme_context'
// export class Product extends PureComponent {
// render() {
// return (
// <div>
// Product:
// <ThemeContext.Consumer>
// {
// value => {
// return <h2>theme:{value.color}-{value.size}</h2>
// }
// }
// </ThemeContext.Consumer>
// </div>
// )
// }
// }
// export default Product
export class Product extends PureComponent {
render() {
const { color, size } = this.props
return (
<div>
<h2>Product: {color}-{size}</h2>
</div>
)
}
}
export default withTheme(Product)
jsx
import React, { PureComponent } from 'react'
import ThemeContext from './context/theme_context'
import Product from './pages/Product'
export class App extends PureComponent {
render() {
return (
<div>
<ThemeContext.Provider value={{color: "red", size: 30}}>
<Product/>
</ThemeContext.Provider>
</div>
)
}
}
export default App
登录鉴权
js
function loginAuth(OriginComponent) {
return props => {
// 从localStorage中获取token
const token = localStorage.getItem("token")
if (token) {
return <OriginComponent {...props}/>
} else {
return <h2>请先登录, 再进行跳转到对应的页面中</h2>
}
}
}
export default loginAuth
jsx
import React, { PureComponent } from 'react'
import loginAuth from '../hoc/login_auth'
export class Cart extends PureComponent {
render() {
return (
<h2>Cart Page</h2>
)
}
}
export default loginAuth(Cart)
jsx
import React, { PureComponent } from 'react'
import Cart from './pages/Cart'
export class App extends PureComponent {
constructor() {
super()
// this.state = {
// isLogin: false
// }
}
loginClick() {
localStorage.setItem("token", "hhh")
this.setState({ isLogin: true })
//this.forceUpdate() //强制刷新用的较少
}
render() {
return (
<div>
App
<button onClick={e => this.loginClick()}>登录</button>
<Cart/>
</div>
)
}
}
export default App
二. 缺陷
- 嵌套问题:需要包裹原组件,大量使用会产生深层嵌套
- 调试困难:多层嵌套让props来源难以追踪
- props劫持:可能意外覆盖传入的props(如name属性被覆盖)
- 适用场景:类组件中仍常见,函数组件推荐使用Hooks
三. 其余高阶组件函数
memo组件作用
当父组件重新渲染时,React 默认会递归渲染所有子组件。memo
可以阻止子组件在 props 没有变化 时的重新渲染。
diff
- 功能:类似PureComponent,对props进行浅比较(shallow compare),
- 原理:比较前后props差异决定是否重新渲染
- 本质:就是一个高阶组件,接收组件返回增强后的组件
jsx
import { useState, memo } from 'react';
// 使用 memo 包装子组件
const ChildComponent = memo(function ChildComponent({ name }) {
console.log('ChildComponent 渲染了'); // 只有 name 变化时才会打印
return <div>Hello, {name}!</div>;
});
function ParentComponent() {
const [count, setCount] = useState(0);
const [name, setName] = useState('Alice');
return (
<div>
<button onClick={() => setCount(count + 1)}>
计数: {count}
</button>
<button onClick={() => setName('Bob')}>
改名
</button>
<ChildComponent name={name} />
</div>
);
}
注意事项
jsx
// 注意:如果传递对象、数组或函数,memo 可能失效
const ChildComponent = memo(function ChildComponent({ user, onClick }) {
console.log('ChildComponent 渲染了');
return <div onClick={onClick}>Hello, {user.name}!</div>;
});
function ParentComponent() {
const [count, setCount] = useState(0);
// 每次都会创建新的对象和函数,导致 memo 失效
return (
<div>
<button onClick={() => setCount(count + 1)}>计数</button>
<ChildComponent
user={{ name: 'Alice' }} // 每次都创建新对象
onClick={() => {}} // 每次都创建新函数
/>
</div>
);
}
解决 :使用 useMemo
和 useCallback
来保持引用稳定。
useMemo
vs useCallback
对比
特性 | useMemo |
useCallback |
---|---|---|
缓存函数 | useMemo(() => fn, deps) |
useCallback(fn, deps) |
缓存对象 | useMemo(() => obj, deps) |
不适用 |
返回值 | 缓存函数返回的值 | 直接缓存函数本身 |
等价关系 | useCallback(fn, deps) = useMemo(() => fn, deps) |
forwardRef作用
diff
- 问题背景:函数组件无实例,无法直接绑定ref
- 解决方案:使用forwardRef将ref作为第二个参数传递
- 限制:仅适用于函数组件,类组件使用会报错
其余用法可查看: juejin.cn/post/718658...
jsx
import { useRef, forwardRef } from 'react';
// 简单的按钮组件
const FancyButton = forwardRef((props, ref) => {
return (
<button ref={ref} style={{ padding: '10px 20px' }}>
{props.children}
</button>
);
});
function App() {
const buttonRef = useRef(null);
const focusButton = () => {
buttonRef.current.focus(); // 聚焦按钮
};
return (
<div>
<FancyButton ref={buttonRef}>点击我</FancyButton>
<button onClick={focusButton}>让上面按钮获得焦点</button>
</div>
);
}