导航


HTML

CSS

JavaScript

浏览器 & 网络

版本管理

框架

构建工具

TypeScript

性能优化

软实力

算法

UI、组件库

Node

业务技能

针对性攻坚

AI


高阶函数

高阶函数:如果一个函数符合下面2个规范中的任何一个,那该两数就是高阶函数

  1. 若A函数,按收的参数是一个函数。那么A就可以称之为高阶函数
  2. 若A函数,调用它所产生的返回值依然是一个函数,那么A就可以称之为高阶函数

常见的高阶函数有:Promise、 setTimeout、arr.map() 等等

函数的柯里化:通过函数的调用,继续返回函数的方式,实现多次接受参数最后统一处理的函数编码形式。

高阶组件(High Order Component 简称 HOC)

  1. HOC 不是 React 提供的 API, 而是一种高级的设计模式
  2. HOC 是一个函数,接收组件作为参数,返回一个新组件
  3. 普通组件返回的是 UI,HOC 返回的是一个新组件
  4. HOC 不能修改参数组件,只能传入组件所需的 props
  5. HOC 是一个没有副作用的纯函数
  6. HOC 除了必须填入被包裹的组件参数以外,其余参数根据需求增加
  7. HOC 不关心数据如何使用,包裹组件不关心数据从哪里来(HOC 管理数据,包裹组件负责数据渲染)
  8. HOC 和包裏组件直接唯一的契合点就是 props

使用场景/总结:

核心代码

具体代码地址: https://github.com/evestorm/learning-js/blob/master/React/React本尊/learning/base/js/23高阶组件/App.jsx

components/listHOC.jsx

/**
 * @param WrapperComponent 要渲染的 React 组件
 * @param fetchListData 请求数据的方法
 */
function listHOC(WrapperComponent, fetchListData) {
  return class extends React.Component {
    // 存储数据
    state = {
      listData: []
    }
    async componentDidMount() {
      // 根据传参,获取数据
      const result = await fetchListData(this.props.field);
      this.setState({
        listData: result.data
      })
    }
    // 管理数据
    removeStudent = (id) => {
      this.setState({
        listData: this.state.listData.filter(v => v.id !== id),
      })
    }
    likeTeacher = (id) => {
      this.setState({
        listData: this.state.listData.map(v => {
          v.id === id ? v.like++ : '';
          return v;
        })
      })
    }
    render() {
      // 把数据传递给组件
      return (
        <>
          {
            this.props.field === 'student'
              ? <WrapperComponent data={ this.state.listData } removeStudent={ this.removeStudent.bind(this) } />
              : <WrapperComponent data={ this.state.listData } likeTeacher={ this.likeTeacher.bind(this) } />
          }
        </>
      )
    }
  }
}
export default listHOC

AppHOC.jsx

// 改造后
import StudentList from './components/StudentList';
import TeacherList from './components/TeacherList';
import { fetchListData } from './model/index';
import listHOC from './components/listHOC';
// 返回新的组件
const StudentListHoc = listHOC(StudentList, fetchListData);
const TeacherListHoc = listHOC(TeacherList, fetchListData);

class App extends React.Component {
//  state = {
//    studentList: [],
//    teacherList: []
//  }
//  async componentDidMount() {
//    const studentList = await fetchListData('student');
//    const teacherList = await fetchListData('teacher');
//    this.setState({
//      studentList: studentList.data,
//      teacherList: teacherList.data
//    })
//  }
//  removeStudent = (id) => {
//    this.setState({
//      studentList: this.state.studentList.filter(v => v.id !== id),
//    })
//  }
//  likeTeacher = (id) => {
//    this.setState({
//      teacherList: this.state.teacherList.map(v => {
//        v.id === id ? v.like++ : '';
//        return v;
//      })
//    })
//  }
  render() {
    return (
      <div className="app">
        {/* <StudentList data={ this.state.studentList } removeStudent={ this.removeStudent.bind(this) } />
        <TeacherList data={ this.state.teacherList } likeTeacher={ this.likeTeacher.bind(this) } /> */}
        <StudentListHoc field="student" />
        <TeacherListHoc field="teacher" />
      </div>
    )
  }
}
ReactDOM.render(<App />, document.getElementById('app'));

HOC 配合 柯里化

改造 listHOC.jsx 使其成为柯里化函数

function listHOC(WrapperComponent) {
  // 返回一个函数,柯里化
  return function(fetchListData) {
    return class extends React.Component {
      state = {
        listData: []
      }
      async componentDidMount() {
        const result = await fetchListData(this.props.field);
        this.setState({
          listData: result.data
        })
      }
      removeStudent = (id) => {
        this.setState({
          listData: this.state.listData.filter(v => v.id !== id),
        })
      }
      likeTeacher = (id) => {
        this.setState({
          listData: this.state.listData.map(v => {
            v.id === id ? v.like++ : '';
            return v;
          })
        })
      }
      render() {
        return (
          <>
            {
              this.props.field === 'student'
                ? <WrapperComponent data={ this.state.listData } removeStudent={ this.removeStudent.bind(this) } />
                : <WrapperComponent data={ this.state.listData } likeTeacher={ this.likeTeacher.bind(this) } />
            }
          </>
        )
      }
    }
  }
}
export default listHOC

对 StudentList.jsx 进行改造,返回一个 listHOC 返回的函数

import listHOC from "./listHOC2";
class StudentList extends React.Component {
  render() {
    return (
      <table border="1">
        <thead>
          <tr>
            <th>ID</th>
            <th>Name</th>
            <th>Grade</th>
            <th>tools</th>
          </tr>
        </thead>
        <tbody>
          {
            this.props.data.map((item) => {
              return (
                <tr key={ item.id }>
                  <td>{ item.id }</td>
                  <td>{ item.name }</td>
                  <td>{ item.grade }</td>
                  <td>
                    <button
                      onClick={ () => this.props.removeStudent(item.id) }
                    >删除</button>
                  </td>
                </tr>
              )
            })
          }
        </tbody>
      </table>
    )
  }
}
export default listHOC(StudentList)

改造后的 App.jsx

// 改造后:HOC + 柯里化
import StudentList from './components/StudentList2';
import TeacherList from './components/TeacherList2';
import { fetchListData } from './model/index';

const StudentListHoc = StudentList(fetchListData);
const TeacherListHoc = TeacherList(fetchListData);

class App extends React.Component {
//  state = {
//    studentList: [],
//    teacherList: []
//  }
//  async componentDidMount() {
//    const studentList = await fetchListData('student');
//    const teacherList = await fetchListData('teacher');
//    this.setState({
//      studentList: studentList.data,
//      teacherList: teacherList.data
//    })
//  }
//  removeStudent = (id) => {
//    this.setState({
//      studentList: this.state.studentList.filter(v => v.id !== id),
//    })
//  }
//  likeTeacher = (id) => {
//    this.setState({
//      teacherList: this.state.teacherList.map(v => {
//        v.id === id ? v.like++ : '';
//        return v;
//      })
//    })
//  }
  render() {
    return (
      <div className="app">
        {/* <StudentList data={ this.state.studentList } removeStudent={ this.removeStudent.bind(this) } />
        <TeacherList data={ this.state.teacherList } likeTeacher={ this.likeTeacher.bind(this) } /> */}
        <StudentListHoc field="student" />
        <TeacherListHoc field="teacher" />
      </div>
    )
  }
}
ReactDOM.render(<App />, document.getElementById('app'));

高阶组件使用注意点

如何排除参数组件不需要的属性

App注意事项.jsx

import MyInput from "./components/MyInput";
import InputHOC from "./components/InputHOC";
const MyInputHOC = InputHOC(MyInput);
class App extends React.Component {
  state = {
    a: 1,
    b: 2,
    c: 3
  }
  render() {
    return (
      <div className="app">
        {/* 给 HOC 传入全部参数 */}
        <MyInputHOC { ...this.state } />
      </div>
    )
  }
}
ReactDOM.render(<App />, document.getElementById('app'));

InputHOC.jsx

function InputHOC(WrapperComponent) {
  class InputHocComponent extends React.Component {
    state = {
      inputValue: ''
    }
    valueInput(e) {
      this.setState({
        inputValue: e.target.value
      })
    }
    render() {
      // 如何排除参数组件不需要的属性
      // MyInput 组件除了 a 属性都需要,所以可以用剩余参数传递
      const { a, ...props } = this.props;
      return (
        <WrapperComponent
          inputValue={ this.state.inputValue }
          valueInput={ this.valueInput.bind(this) }
          { ...props }
        />
      )
    }
  }
  InputHocComponent.displayName = 'InputHOC';
  return InputHocComponent;
}
export default InputHOC;

高阶组件不能修改参数组件,可能会导致参数组件内部的逻辑的执行失效

MyInput.jsx

例如组件内部写了 componentDidUpdate 方法

class MyInput extends React.Component {
  componentDidUpdate() {
    console.log('MyInput组件内部: 我更新了');
  }
  render() {
    return (
      <div>
        <h1>{ this.props.inputValue }</h1>
        <p>总计: { this.props.b + this.props.c }</p>
        <input type="text"
          value={ this.props.inputValue }
          onChange={ this.props.valueInput }
        />
      </div>
    )
  }
}

InputHOC.jsx

然后在 HOC 中通过原型的方式覆盖了组件内部的 componentDidUpdate

function InputHOC(WrapperComponent) {
  // 会覆盖 MyInput 组件内的 componentDidUpdate
  // 因为这相当于修改了 MyInput 的 componentDidUpdate 方法
  // 所以得写在新返回的 InputHocComponent 组件里边去,因为这是个新组件,不会与 WrapperComponent 干扰
  // 所以:高阶组件不能修改参数组件,可能会导致参数组件内部的逻辑的执行失效
  // WrapperComponent.prototype.componentDidUpdate = function() {
  //   console.log('InputHOC: 我更新了');
  // }
  class InputHocComponent extends React.Component {
    state = {
      inputValue: ''
    }
    // 这样写就行,组件内和HOC的 componentDidUpdate 都能执行
    // 所以:一切的功能可以在组件内实现
    componentDidUpdate() {
      console.log('InputHOC: 我更新了');
    }
    valueInput(e) {
      this.setState({
        inputValue: e.target.value
      })
    }
    render() {
      // 如何排除参数组件不需要的属性
      // MyInput 组件除了 a 属性都需要,所以可以用剩余参数传递
      const { a, ...props } = this.props;
      return (
        <WrapperComponent
          inputValue={ this.state.inputValue }
          valueInput={ this.valueInput.bind(this) }
          { ...props }
        />
      )
    }
  }
  InputHocComponent.displayName = 'InputHOC';
  return InputHocComponent;
}
export default InputHOC;

高阶组件可以接受类组件,也可以接受函数组件

使用函数组件实现之前的 MyInput.jsx

// 使用 函数组件 实现 MyInput 类组件
function MyInput(props) {
  // 这样写,就能在组件更新时,调用 log
  React.useEffect(() => {
    console.log('MyInput组件内部: 我更新了');
  }, [props.inputValue]);
  return (
    <div>
      <h1>{ props.inputValue }</h1>
      <p>总计: { props.b + props.c }</p>
      <input type="text"
        value={ props.inputValue }
        onChange={ props.valueInput }
      />
    </div>
  )
}
export default MyInput;

总结