大家好,我是蓝胖子的小叮当,今天分享的是JavaScript的第十二章高阶函数、高阶组件,大家在阅读期间有任何的意见或建议都可以留言给我哈!
12.1前言
高阶函数和高阶组件是两种基于抽象思想的代码复用方式,它们在函数式编程和组件化开发中扮演重要角色;高阶函数是 JavaScript 函数式编程的基础工具,而高阶组件是前端框架组件化的逻辑复用方案;
12.2高阶函数简介
我们日常开发中需要处理的只有"数据"和"关系",函数就是关系。
定义:就是以函数作为输入或输出的函数称为高阶函数;函数也是一种数据类型,同样可以作为参数,传递给另外一个参数使用, 最典型的就是作为回调函数。同理函数也可以作为返回值传递回来。
核心思想:通过函数进行抽象和包装、实现逻辑复用和行为增强。
12.3高阶函数常见场景和实现
12.3.1函数增强
给原函数添加额外功能(如日志、缓存、错误处理),不修改原函数本身
js
// 高阶函数:添加错误捕获能力
function withErrorHandling(fn) {
return function(...args) {
try {
return fn(...args);
} catch (error) {
console.error('函数执行出错:', error);
return null; // 错误时返回默认值
}
};
}
// 原函数:可能抛出错误
function divide(a, b) {
if (b === 0) throw new Error('除数不能为0');
return a / b;
}
// 增强后的函数
const safeDivide = withErrorHandling(divide);
console.log(safeDivide(6, 2)); // 3
console.log(safeDivide(6, 0)); // 打印错误,返回 null
12.3.2函数柯里化
将多参数函数转换为一系列单参数函数的高阶函数
js
// 高阶函数:实现柯里化
function curry(fn) {
return function curried(...args) {
// 如果参数数量足够,直接执行原函数
if (args.length >= fn.length) {
return fn(...args);
}
// 否则返回新函数,等待接收更多参数
return (...nextArgs) => curried(...args, ...nextArgs);
};
}
// 原函数
function add(a, b, c) {
return a + b + c;
}
// 柯里化后
const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAdd(1, 2)(3)); // 6
12.3.3内置高阶函数
JavaScript 数组方法如 map、filter、reduce 都是高阶函数,它们接收回调函数作为参数
js
const numbers = [1, 2, 3, 4];
// map 接收回调函数,返回新数组
const doubled = numbers.map(n => n * 2); // [2,4,6,8]
12.4高阶组件(HOC)概览
12.4.1基本概念
- 高阶组件(HOC)不是组件,而是一个函数,它会接收一个组件作为参数并返回一个经过改造的新组件。
- 一个组件包含了 props、state、ref、生命周期方法、static方法和元素树,所以高阶组件改造的就是组件内的这六大部分。
- 高阶组件的实现方式包括属性代理和反向继承两种。
12.4.2解决的问题
1.抽取重复代码,实现组件复用,常见场景:页面复用。 2.条件渲染,控制组件的渲染逻辑(渲染劫持),常见场景:权限控制。 3.捕获/劫持被处理组件的生命周期,常见场景:组件渲染性能追踪、日志打点。
12.5高阶组件常见场景和实现
12.5.1属性代理
- 操作props
我们看到之前要传递给被包裹组件的属性首先传递给了高阶组件返回的组件,这样我们就获得了props的控制权(这也就是为什么这种方法叫做属性代理)。我们可以按照需要对传入的props进行增加、删除、修改。
js
//原组件
class Btn extends React.Component {
render() {
return <button>{this.props.name}</button>;
}
}
//高阶组件
function HOC(Wrapped) {
class Enhanced extends React.Component {
constructor(props) {
super(props);
this.state = { name: "strick" };
}
render() {
return <Wrapped {...this.state} />;
}
}
return Enhanced;
}
const EnhancedBtn = HOC(Btn);
- 获得refs
我们在属性代理中,可以轻松的拿到被包裹的组件的实例引用(ref),我们就可以拿到组件的实例,进而实现调用实例方法的操作。
js
import React, { Component } from 'React';
const HOC = (WrappedComponent) =>
class wrapperComponent extends Component {
storeRef(ref) {
this.ref = ref;
}
render() {
return <WrappedComponent
{...this.props}
ref = {::this.storeRef}
/>;
}
}
- 抽象state
属性代理的情况下,我们可以将被包裹组件(WrappedComponent)中的状态提到包裹组件中,一个常见的例子就是实现不受控组件到受控的组件的转变。
js
class WrappedComponent extends Component {
render() {
return <input name="name" {...this.props.name} />;
}
}
const HOC = (WrappedComponent) =>
class extends Component {
constructor(props) {
super(props);
this.state = {
name: '',
};
this.onNameChange = this.onNameChange.bind(this);
}
onNameChange(event) {
this.setState({
name: event.target.value,
})
}
render() {
const newProps = {
name: {
value: this.state.name,
onChange: this.onNameChange,
},
}
return <WrappedComponent {...this.props} {...newProps} />;
}
}
- 获得原组件的static方法
js
import * as React from 'react';
import * as styles from './index.module.less';
function HOC (WrappedComponent: any) {
/* 省略无关代码... */
function wrappedComponentStaic () {
WrappedComponent.sayHello();
}
return (props: any) => (
<div className={styles.hocWrapper}>
<WrappedComponent
inputRef={(el: any) => { inputElement = el; }}
{...props}
/>
/* 省略无关代码... */
<input
type="button"
value="调用子组件static"
onClick={wrappedComponentStaic}
className={styles.callButton}
/>
</div>
);
}
export default HOC;
- 用其他元素包裹传入组件
将被包裹组件包裹起来,来实现布局或者是样式的目的
js
render(){
<div>
<WrappedComponent {...this.props} />
</div>
}
12.5.2反向继承
- 劫持原组件生命周期方法
js
function HOC(WrappedComponent){
// 继承了传入组件
return class HOC extends WrappedComponent {
// 注意:这里将重写 componentDidMount 方法
componentDidMount(){
...
}
render(){
//使用 super 调用传入组件的 render 方法
return super.render();
}
}
}
- 渲染劫持
js
const HOC = (WrappedComponent) =>
class extends WrappedComponent {
render() {
if (this.props.isRender) {
return super.render();
} else {
return <div>暂无数据</div>;
}
}
}
12.6属性代理和反向继承的区别
- 属性代理是从"组合"的角度出发,这样有利于从外部去操作 WrappedComponent,可以操作的对象是 props,或者在 WrappedComponent 外面加一些拦截器,控制器等。
- 反向继承则是从"继承"的角度出发,是从内部去操作 WrappedComponent,也就是可以操作组件内部的 state ,生命周期,render函数等等。
12.7个人理解
高阶组件并不是组件而是一个函数,它接收一个组件为参数,通过抽象、包装和扩展后返回一个功能加强的组件。由此可知高阶组件的实现方式包括属性代理和反向继承两种。
代理的话 可知一个组件由props、state、dom元素实例、static方法这几部分组成,所以代理包括以下几项功能 操作props、抽取state、获得refs实例、获得原组件的static方法
继承的话 则是通过继承的方式从内部操作原组件内部的state、生命周期
好啦,关于高阶函数、高阶组件的知识点就总结到这里,如果有什么疑问、意见或建议,都可畅所欲言,谢谢大家,我也将持续更新。
预告:不清楚柯里化的含义?欢迎收看JavaScript基础的下一章:函数柯里化curry