React 高阶组件
一、HOC(高阶组件)
高阶组件是一个函数,接收一个组件,返回一个新组件:
jsx
const withComponent = (WrappedComponent) => {
return class extends React.Component {
render() {
return <WrappedComponent {...this.props} />
}
}
}
// 使用
const HighComponent = withComponent(MyData)const withComponent = (WrappedComponent) => {
return class extends React.Component {
render() {
return <WrappedComponent {...this.props} />
}
}
}
// 使用
const HighComponent = withComponent(MyData)请求数据的高阶组件
jsx
const withFetch = (url) => (View) => {
return class extends React.Component {
state = { data: null }
async componentDidMount() {
const res = await fetch(url)
const data = await res.json()
this.setState({ data })
}
render() {
if (!this.state.data) return <div>loading...</div>
return <View data={this.state.data} {...this.props} />
}
}
}
// 使用
export default withFetch('https://api.example.com/banner')(Banner)const withFetch = (url) => (View) => {
return class extends React.Component {
state = { data: null }
async componentDidMount() {
const res = await fetch(url)
const data = await res.json()
this.setState({ data })
}
render() {
if (!this.state.data) return <div>loading...</div>
return <View data={this.state.data} {...this.props} />
}
}
}
// 使用
export default withFetch('https://api.example.com/banner')(Banner)二、React.memo
阻止函数组件不必要的重新渲染(浅比较 props):
jsx
const App = React.memo((props) => {
return <div>{props.name}</div>
})
// 自定义比较函数
function areEqual(prevProps, nextProps) {
return prevProps.seconds === nextProps.seconds
}
export default React.memo(App, areEqual)const App = React.memo((props) => {
return <div>{props.name}</div>
})
// 自定义比较函数
function areEqual(prevProps, nextProps) {
return prevProps.seconds === nextProps.seconds
}
export default React.memo(App, areEqual)与 Redux 配合
jsx
export default connect(mapStateToProps)(React.memo(App))export default connect(mapStateToProps)(React.memo(App))三、React.PureComponent
类组件的浅比较版本:
jsx
class App extends React.PureComponent {
render() {
return <div>{this.props.name}</div>
}
}class App extends React.PureComponent {
render() {
return <div>{this.props.name}</div>
}
}注意:
- 只做浅比较,props 层级不能太深
- 传入对象时,展开后传递每个属性:
jsx
// 不推荐 — 传入整个对象
<Comment data={c} />
// 推荐 — 展开为单独属性
<Comment {...c} />// 不推荐 — 传入整个对象
<Comment data={c} />
// 推荐 — 展开为单独属性
<Comment {...c} />函数组件实现 PureComponent
jsx
const shouldComponentUpdate = (arePropsEqual) => (BaseComponent) => {
return class extends React.Component {
shouldComponentUpdate(nextProps) {
return !arePropsEqual(this.props, nextProps)
}
render() {
return <BaseComponent {...this.props} />
}
}
}
const Pure = (BaseComponent) => {
const hoc = shouldComponentUpdate((prev, next) => !shallowEqual(prev, next))
return hoc(BaseComponent)
}const shouldComponentUpdate = (arePropsEqual) => (BaseComponent) => {
return class extends React.Component {
shouldComponentUpdate(nextProps) {
return !arePropsEqual(this.props, nextProps)
}
render() {
return <BaseComponent {...this.props} />
}
}
}
const Pure = (BaseComponent) => {
const hoc = shouldComponentUpdate((prev, next) => !shallowEqual(prev, next))
return hoc(BaseComponent)
}四、lazy 和 Suspense(懒加载)
jsx
import React, { lazy, Suspense } from 'react'
const About = lazy(() => import('./pages/About'))
function App() {
return (
<Suspense fallback={<div>加载中</div>}>
<About />
</Suspense>
)
}import React, { lazy, Suspense } from 'react'
const About = lazy(() => import('./pages/About'))
function App() {
return (
<Suspense fallback={<div>加载中</div>}>
<About />
</Suspense>
)
}要点:
- lazy 和 Suspense 必须搭配使用
- fallback 必须是组件实例,
fallback={<Loading/>}而不是fallback={Loading} - Suspense 不能捕获加载错误,需配合
componentDidCatch
Webpack 魔法注释
jsx
const About = lazy(() => import(/* webpackChunkName: "about" */ './pages/About'))const About = lazy(() => import(/* webpackChunkName: "about" */ './pages/About'))带类型的 lazyImport
tsx
function lazyImport<T extends React.ComponentType<any>, I extends { [K2 in K]: T }, K extends keyof I>(
factory: () => Promise<I>, name: K
): I {
return Object.create({
[name]: React.lazy(() => factory().then(module => ({ default: module[name] })))
})
}
const { Home } = lazyImport(() => import('./Home.component'), 'Home')function lazyImport<T extends React.ComponentType<any>, I extends { [K2 in K]: T }, K extends keyof I>(
factory: () => Promise<I>, name: K
): I {
return Object.create({
[name]: React.lazy(() => factory().then(module => ({ default: module[name] })))
})
}
const { Home } = lazyImport(() => import('./Home.component'), 'Home')延迟加载(开发调试)
jsx
function slowImport(value, ms = 1000) {
return new Promise(resolve => setTimeout(() => resolve(value), ms))
}
const About = React.lazy(() => slowImport(import('./pages/About'), 1000))function slowImport(value, ms = 1000) {
return new Promise(resolve => setTimeout(() => resolve(value), ms))
}
const About = React.lazy(() => slowImport(import('./pages/About'), 1000))五、React.createPortal
将子节点渲染到父组件 DOM 层次之外:
jsx
import { createPortal } from 'react-dom'
class Modal extends React.Component {
constructor() {
super()
this.el = document.createElement('div')
this.el.setAttribute('class', 'modal-mask')
}
componentDidMount() { document.body.appendChild(this.el) }
componentWillUnmount() { document.body.removeChild(this.el) }
render() {
return createPortal(this.props.children, this.el)
}
}import { createPortal } from 'react-dom'
class Modal extends React.Component {
constructor() {
super()
this.el = document.createElement('div')
this.el.setAttribute('class', 'modal-mask')
}
componentDidMount() { document.body.appendChild(this.el) }
componentWillUnmount() { document.body.removeChild(this.el) }
render() {
return createPortal(this.props.children, this.el)
}
}六、React.Children
操作 this.props.children 的工具函数:
jsx
React.Children.map(children, fn) // 遍历
React.Children.forEach(children, fn) // 遍历(无返回值)
React.Children.count(children) // 计数
React.Children.only(children) // 验证唯一子节点
React.Children.toArray(children) // 转数组(可排序)
// 循环修改 children 属性(需先克隆)
{
React.Children.map(props.children, (node) => {
return React.cloneElement(node, {
value: '',
onChange: this.handleChange
})
})
}React.Children.map(children, fn) // 遍历
React.Children.forEach(children, fn) // 遍历(无返回值)
React.Children.count(children) // 计数
React.Children.only(children) // 验证唯一子节点
React.Children.toArray(children) // 转数组(可排序)
// 循环修改 children 属性(需先克隆)
{
React.Children.map(props.children, (node) => {
return React.cloneElement(node, {
value: '',
onChange: this.handleChange
})
})
}注意:children 是只读的,需使用 React.cloneElement 克隆后再修改。
Cao Note