什么是高阶组件(HOC)
Higher-Order Components就是一个函数,传给它一个组件,它返回一个新的组件。
高阶组件:就相当于手机壳,通过包装组件,增强组件功能。
实现步骤:
- 首先创建一个函数
- 指定函数参数,参数应该以大写字母开头
- 在函数内部创建一个类组件或函数组件,提供复用的状态逻辑代码,并返回
- 在该组件中,渲染参数组件,同时将状态通过prop传递给参数组件(可选,如有)
- 调用该高阶组件,传入要增强的组件,通过返回值拿到增强后的组件,并将其渲染到页面
(HOC)作用:
- 进行权限控制
- 路由限制
- 访问统计
- 统一布局
传统函数方式调用
# 定义高阶组件
// 定义一个函数,在函数内部创建一个相应类组件
function HocComp(Cmp) {
// 该组件提供复用状态逻辑
class Hoc extends React.Component {
state = {
a: 0,
b: 0
}
render() {
// 把当前组件的状态通过属性传给参数组件
return <Cmp {...this.state} />
}
}
return Hoc
}
# 使用高阶组件
function Cmp(props) {
return (
<p>
a的值:{props.a}
<hr/>
b的值:{props.b}
</p>
)
}
// 组件来进行包装
let Mycmp = HocComp(Cmp)
class App extends React.Component {
constructor(props) {
super(props)
}
render() {
return (
<div>
高阶组件
<Mycmp></Mycmp>
</div>
)
}
}
// 布局的高阶组件
// 定义一个函数,返回一个增强后的组件
// 参数就是一个组件
// import React,{Fragment} from 'react';
import React from 'react';
// 定义函数
const layoutFn = Cmp => {
// 类组件
class Layout extends React.Component {
state = {}
static getDerivedStateFromProps(nextProps, nextState) {
if (nextState.phone) {
return null
}
return nextProps
}
render() {
return (
<>
<header>我是一个头部显示</header>
{/* <Cmp name={this.props.name} age={this.props.age} /> */}
{/* <Cmp {...this.props} /> */}
<Cmp {...this.state} />
</>
)
}
componentDidMount() {
this.setState(state => ({
phone: state.phone.slice(0, 3) + '****' + state.phone.slice(7)
}))
}
}
// 返回
return Layout
}
export default layoutFn
import React, { Component } from 'react';
import C1 from './components/C1';
import C2 from './components/C2';
import layout from './hoc/layout'
// 记忆组件
import memoizeOne from 'memoize-one'
/* const Cmp1 = layout(C1)
const Cmp2 = layout(C2) */
class App extends Component {
state = {
show: [true, false],
x: 1,
y: 2
}
sum() {
console.log('sum');
return this.state.x + this.state.y
}
// 计算属性
getSum = memoizeOne((a, b) => {
console.log('aaaa');
return a + b
})
render() {
return (
<div>
<button onClick={() => {
this.setState(state => ({ show: [true, false] }))
}}>C1页面</button>
<button onClick={() => {
this.setState(state => ({ show: [false, true] }))
}}>C2页面</button>
<hr />
{
// this.state.show[0] ? <Cmp1 /> : <Cmp2 />
this.state.show[0] ? <C2 name='张三' age={30} phone="13512512535" /> : <C1 />
}
<hr />
<h3>{this.sum()}</h3>
<h3>{this.sum()}</h3>
<hr />
// 只会打印3次
<h1>{this.getSum(this.state.x, this.state.y)}</h1>
<h1>{this.getSum(this.state.x, this.state.y)}</h1>
<h1>{this.getSum(this.state.x, this.state.y)}</h1>
<h1>{this.getSum(this.state.x, this.state.y)}</h1>
<h1>{this.getSum(2, 2)}</h1>
<h1>{this.getSum(2, 2)}</h1>
<h1>{this.getSum(2, 2)}</h1>
<h1>{this.getSum(2, 2)}</h1>
<h1>{this.getSum(2, 2)}</h1>
<h1>{this.getSum(this.state.x, this.state.y)}</h1>
<h1>{this.getSum(this.state.x, this.state.y)}</h1>
<h1>{this.getSum(this.state.x, this.state.y)}</h1>
<h1>{this.getSum(this.state.x, this.state.y)}</h1>
<h1>{this.getSum(this.state.x, this.state.y)}</h1>
<h1>{this.getSum(this.state.x, this.state.y)}</h1>
</div>
);
}
}
export default App;
使用装饰器调用
装饰器是一种函数,写成 @函数名。它可以放在类和类方法的定义前面。react脚手架创建的项目默认是不支持装饰器,需要手动安装相关模块和添加配置文件。
-
安装相关模块
npm i -D customize-cra react-app-rewired
-
修改package.json文件中scripts命令
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-scripts test",
"eject": "react-scripts eject"
}
- 在项目根目录中添加config-overrides.js配置文件(此文件可以理解为就是webpack.config.js的扩展文件)
const path = require('path')
const {addDecoratorsLegacy, override} = require('customize-cra')
const customize = () => (config) => {
config.resolve.alias['@'] = path.join(__dirname, 'src')
return config
}
module.exports = override(
addDecoratorsLegacy(),
customize()
)
memoization(计算属性-记忆组件)
连续两次相同传参,第二次会直接返回上次的结果,每次传参不一样,就直接调用函数返回新的结果,会丢失之前的记录,并不是完全记忆,可以在它的参数中传入state数据从而实现了类似Vue中的计算属性功能。
# 安装
npm i -S memoize-one
# 引入
import memoizeOne from 'memoize-one'
# 使用
getValue = memoizeOne((x,y) => x+y)
# 调用
render(){
let total = this.getValue(this.state.x, this.state.y)
return <div>{total}</div>
}
memo
React.memo()是一个高阶函数,它与 React.PureComponent类似,但是一个函数组件而非一个类。如果你的组件在相同 props的情况下渲染相同的结果,那么你可以通过将其包装在 React.memo 中调用,以此通过记忆组件渲染结果的方式来提高组件的性能表现。这意味着在这种情况下,React 将跳过渲染组件的操作并直接复用最近一次渲染的结果。
import React from 'react'
export default React.memo(
props => {
console.log('child---render')
return (
<div>{props.title}</div>
)
}
)
/* 缓存数据 */
import memoizeOne from 'memoize-one';
import React, {useState} from 'react';
const Child = function(props) {
console.log(0);
const getNewString = memoizeOne((str) => {
console.log(1);
return str + ' world!'
})
return (
<div>
<h1>Child </h1>
{ getNewString(props.title)}
</div>
)
}
function Memoization() {
let [title, setTitle] = useState("hello")
return (
<div>
<Child title={title}></Child>
<button onClick={() => setTitle("hello ")}>Click</button>
</div>
);
}
export default Memoization;
总结
HOC 更多的使用在组件封装,自定义的高阶组件是最常用的。比如,我们想要我们的组件通过自动注入一个版权信息时,主要用于代码复用,或者权限的验证,因为高阶函数本质上仍然是函数,在进入高阶组件之后渲染组件之前,可以进行逻辑判断是否进行渲染,或者渲染不同的页面。需要高阶组件带给我们极大方便的同时,我们也要遵循一些 约定:
- props 保持一致
- 你不能在函数式(无状态)组件上使用 ref 属性,因为它没有实例
- 不要以任何方式改变原始组件 WrappedComponent
- 透传不相关 props 属性给被包裹的组件 WrappedComponent
- 不要再 render() 方法中使用高阶组件
- 使用 compose 组合高阶组件
- 包装显示名字以便于调试