说明与目录
在学习本章内容之前, 最好是具备 react 中'插槽(children)'及'组合与继承' 这两点的知识积累.
详情请参照 React 精要面试题讲解(四) 组合与继承不得不说的秘密.
哦不好意思忘记了, 四还没写呢.==! 回头补上.
__首先, 我们要知道高阶组件能够做到什么: 对复用 UI, 数据逻辑等进行封装, 对参数组件进行制式处理, 从而让参数组建具备特定的 ui 或功能__
那么本节的学习目录:
高阶函数的认知
类的修饰器 (decorator) 的认知(类比高阶函数)
高阶组件的认知(类比修饰器)
高阶组件的两种形式(类比插槽)
高阶组件之组合 (代理) 类型的高阶组件
高阶组件之继承类型的高阶组件
高阶组件的认知总结
必须深刻认知, 以上内容是循序渐进且相互关联的关系, 按照流程, 我们将彻底把高阶组件安排的明明白白, 玩个透彻.
1. 高阶函数的认知
在本系列之前的学习中, 你应当明白 -- 组件的本质是函数.
那么什么是高阶函数?
高阶函数也是一个函数,__它接受函数为参数, 返回处理过后的函数或者一个新函数__.
那么我们知道, 高阶函数的作用是对参数函数进行统一的加工处理.
形如:
- // 这里我们写了一个函数作为之后的参数
- // 思考一下为什么把参数函数定义在上面
- function testedFunc(){
- this.a = 1;
- }
- // 在此定义一个高阶函数
- function highOrderFunc(func){
- func.prototype.aaa = 3;
- return func;
- }
- // 这里测试一下我们写的高阶函数
- var newFunc = highOrderFunc(testedFunc);
- // 打印看看处理后的函数原型及实例
- console.log(newFunc.prototype,1);
- console.log(new newFunc(),2);
- (别那么懒, 赶紧复制粘贴 f12)
打印结果如下:
那么我们知道了, 高阶函数 作用是 处理 (加工) 传入的函数, 以达成某种目的...
2. 类的修饰器 (decorator) 的认知
ES6 增添了 class 类, 我们知道, 类的本质也是函数.
- class testClass{
- a = 1;
- }
- console.log( typeof testClass)
打印结果如下:
那么类的修饰器 --decorator 是个怎样的东西咧?
__类的修饰器是 es6 的提案之一, 在 es7 标准中实现. 修饰器也是一个函数, 把传入的原有类修饰一下, return 出处理后的类或新类.__
这时候我们脑海中应该闪过一个词 -- 高阶类...(??? 黑人问号)
不觉得太难听了吗?
ok, 我们还是以代码来演示一下:
- // 注意, 下面这种 @xx 的写法是修饰器的标准写法, 是属于 es7 的产物, 要用 babel 哦~
- // 定义一个修饰器 decF
- function decF(adornedClass){
- return class extends adornedClass{
- b= 2
- }
- }
- // 使用方式 1 : @decF
- @decF
- class foo{
- a = 1
- }
- console.log( new foo(),1)
- class bar{
- a='bar'
- }
- // 使用方式 2 : decF();
- const newBar = decF(bar);
- console.log( new newBar(),2);
打印如下:
瞧啊, 修饰器就是这么个东西.
要注意的是, 类的修饰器能否修饰函数? 为什么?
- // 可以自己去找答案, 再讲别的就跑题了.
- ___
3. 高阶组件的认知(类比修饰器)
那么经过类的修饰器的认知, 高阶组件的概念就很明朗了.
React 高阶组件 (high-order-component, 简称 hoc) 就是一个类的修饰器啊... 它接受一个组件类为参数, 返回出一个新的组件类;
形如:
- // 用法 1 高阶函数式写法
- hoc(WrapedComponent);
- // 用法 2 decorator 修饰器写法
- @hoc
- class A extends React.Component{
- //...
- }
大家有木有很眼熟啊 ?
ok, 我们写一个常规的高阶组件并暴露出去;
- export default WrapedComponent => class NewComponent extends React.Component{
- render(){
- return <WrapedComponent a = '添加了一个属性' />
- }
- }
这种箭头函数的写法好理解吧.
如代码所示, 我们写了一个高阶组件, 返回的新组件 NewComponent 里, 用组合的形式使用了传入的组件 WrapedComponent.(所以不明白组合与继承的童鞋, 赶紧补一补再来看啊)
这里有人问 connect 为啥两个括号啊
- // 形如
- connect(func1,func2 )( WrapedComponent)
OK , 我们也手写一下它.
- export default (func1,func2)=>WrapedComponent=>class NewComponent extends React.Component{
- // ....
- }
这个两层箭头函数好理解吧?
顺便说下, 一般像 connect 这样多嵌套了一层的高阶函数, 我称之为二阶高阶函数. 此后类推, 三个括号就叫三阶高阶函数...
4. 高阶组件的两种形式(类比插槽)
同 children 一样, 高阶组件也存在组合 (也可称之为代理) 和继承两种形式.
那么高阶组件和 children 插槽有什么关系呢?
我们来类比以下代码:
- // 写一个组合形式的具备插槽功能的组件 Provider
- class Provider extends React.Component{
- render(){
- return (
- <div>
- {this.props.children}
- </div>
- )
- }
- }
- // 下面是使用的方式
- import ComA from './ComA'
- const useB = (<Provider> <ComA a='使用 Provider 时添加了 a 属性'/> </Provider>)
- // 写一个组合形式的高阶组件
- const hocAddA = WrapedComponent => class NewComponent extends React.component{
- render(){
- return (
- <div>
- <WrapedComponent a ='定义 hocAddA 时添加了 a 属性'/>
- </div>
- )
- }
- }
- // 我们来尝试使用它
- const NewA = hocAddA(ComA);
- // 或者
- @hocAddA
- class ComB ...
ok, 上述两种代码在使用后的表现几乎是一致的(同样实现了给 ComA 添加属性 a 的功能).
但是注意, 插槽 (children) 的实现, 不关心插槽组件的功能变化. 只是把插槽当作当前组件的子组件去使用(这就是组合).
- // 同样的, 我现在这样使用
- import ComA from './ComA'
- const useB = (<Provider> <ComA b='使用 Provider 时添加了 b 属性'/> </Provider>)
而高阶函数, 在定义时就写死了参数组件的功能变化.
传入组件, 得出的组件只会添加属性 a.
当然, 我们也可以通过二阶高阶函数实现 用参数控制参数组件的功能变化:
定义一个二阶高阶组件
- const hocAddProps = props => WrapedComponent => class NewComponent extends React.Component{
- render(){
- return (
- <div>
- <WrapedComponent {...props}/>
- </div>
- )
- }
- }
- // 于是我们这样使用它
- const propsAdded = {
- a: '添加了一个属性 a',
- b: '添加了一个属性 b'
- }
- const NewA = hocAddProps(propsAdded)(ComA)
诸如此类. 实际上组合形式的高阶组件能做到的事, 用 children 基本都能做到.
那么组合形式的高阶组件和继承形式的高阶组件的区别在哪呢?
组合形式(也称之为代理形式): 返回的新组件, 继承的还是 React.Component, 只是把参数组件作为新组件的子组件去使用, 能够实现给参数组件进行包装, 属性的增删改, 状态抽离等功能.
继承形式: 返回的新组件, 继承的是 参数组件 , 从而实现以参数组件为模版, 改写参数组件的功能.
上述划重点, 要考.
我们再回过头来思考类的修饰器 -- 返回一个新的类或改写参数类.
是不是一样的道理啊.
所以说高阶组件啥的, 还是 JS 啊, 最多加了 jsx 的语法嘛.
5. 高阶组件之组合 (代理) 类型的高阶组件
上述我们已经知道了组合 (代理) 类型的高阶组件的概念和思想, 以及它能实现的功能.
那么我们上 demo 代码
- import React,{Component,createRef} from 'react';
- export default title=>WrapedComponent=> class NewComponent extends Component{
- // 抽离状态
- state={
- value:''
- }
- // 访问 refs
- myref=createRef();
- handleInputChange=(e)=>{
- this.setState({
- value:e.target.value
- })
- }
- render(){
- const {wap,...otherprops} = this.props;
- const newProps = {
- value:this.state.value,
- onChange:this.handleInputChange
- }
- // 包装组件
- return (
- <div>
我是组件 NewComponent, 是典型的代理形式的高阶组件, 我除了做自己的事, 还可以对 我的参数组件:
1 增加 / 删减 props 2 抽离状态 3 访问 ref 4 包装组件
- <div > 我的 title:{title}</div>
- <WrapedComponent {...otherprops} ref={this.myref} inputProps={newProps}/>
- </div>
- )
- }
- }
这里要单独说一下上述功能中的状态抽离.
状态抽离 (状态提升): 把参数组件(即代理形式中使用的子组件) 的状态提升到 NewComponent(即代理形式中的当前组件, 也就是父组件) 中, 这样一来, 子组件只负责 UI 渲染, 而父组件通过 props 传递 state 实现数据的控制
也就是说, NewComponent 成为参数组件的容器组件, 参数组建单纯作为 UI 组件
ps: 容器组件和 UI 组件的概念是相对的. 例如 把 B 的状态抽离到父组件 A 上, 那么 A 相对于 B 来说是 B 的容器组件, 要这么去理解. 后续讲 react-redux 中会提到.
___
6. 高阶组件之继承类型的高阶组件
同样的, 上述我们已经知道了 继承类型的高阶组件的概念和思想, 那么我们也直接上 demo 代码
- import React from 'react'
- // 这个是给返回的新组件起名用的函数, 有兴趣可以结合调试器玩玩.
- function getDisplayName(WrapedComponent){
- return WrapedComponent.displayName||WrapedComponent.name||'component'
- }
- export default color=>WrapedComponent=> class NewComponent extends WrapedComponent{
- // static displayName = `E(${getDisplayName(Inconponent)})`
- ;
- aaa = '我改写了参数组件中的 aaa 属性'
- compoenentDidMount(){
- console.log('我不仅可以改写属性和方法, 我还能改写钩子')
- }
- render(){
- const {wap,...otherprops} = this.props;
- const element = super.render();
- console.log(element);
- const newStyle = {
- color:element.type==='div'?color:null
- }
- const newProps = {
- ...otherprops,
- style:newStyle
- }
- // 我甚至还改写了参数组件的 UI
- return React.cloneElement(element,newProps,element.props.children)
- }
- }
如上述代码所示(跟着敲一下啊懒虫), 我们成功做到了以参数组件为模版, 改写了参数组件中已定义的属性, 方法, 钩子, 甚至 UI, 增添了参数组件中未定义的属性, 方法, 钩子等.
当然, 同官方文档中 '组合和继承' 这一章中的思想一致, 绝大部分情况下, 我们用不到继承类型的高阶组件, 也不提倡这种形式的用法(其实我个人觉得挺好玩的).
7. 高阶组件的认知总结
那么我们通过以上学习, 已经完完整整掌握了高阶组件的使用.
在日常项目中, 我们也可以在合适的场景中使用高阶组件完成对应的需求.
回顾最上面提到过的高阶组件的使用场景:
__对复用 UI, 数据逻辑等进行封装, 对参数组件进行制式处理, 从而让参数组建具备特定的 ui 或功能__
再回顾下上述讲到过的高阶函数, 类的修饰器等 --
你 get 到了吗?
面试中会问到高阶组件的问题, 消化掉这一篇, 那么你便可以连续不断的给面试官讲上半个小时征服他.
最后, 如果本章内容对你的 react 学习有帮助, 记得点个关注, 等待更新哦.
来源: https://www.cnblogs.com/sanchang/p/10614074.html