乐趣区

锵哥带你读好书系列之深入浅出React和Redux第六章React高级组件

每一种选择都有不同的结局,就如走不同的路就会有不同的风景。所以,如果想看灿烂的风景,不妨沉思片刻再做选择。

前言:

   当你开始为自己写的代码感到太 low 而发愁的时候,恭喜你,你开始有了提升的潜意识,你欠缺的是一些技巧的指引。
干货来袭!今天我们要来学习 React 的高级写法了!

正文:

章节:《深入浅出 React 和 Redux》(第六章:React 高级组件)

1. 重复是优秀系统设计的大敌

2. 如何构建更易于复用,更灵活的 React 高级组件,包含两种方式
A: 高阶组件的概念及应用
B: 以函数为子组件的模式

3. 这两种方式的最终目的都是为了重用代码,只是策略不同,各有优劣,开发者在实际工作中要根据实际情况决定采用何种方式

4. 高阶组件(Higher Order Component, HOC)并不是 React 提供的某种 API,而是使用 React 的一种模式,用于增强现有组件的功能

5. 简单来说,一个高阶组件就是一个函数,这个函数接受一个组件作为输入,然后返回一个新的组件作为结果,而且,返回的新组件拥有了输入组件所不具有的功能。

6. 这里提到的组件指的并不是组件实例,而是一个组件类,也可以是一个无状态组件的函数

7.const {user, …otherProps} = this.props
这是一个利用 ES6 语法的技巧,可以简洁地把一个对象中特定字段过滤掉,结果赋值给一个新的对象。经过上面的语句,otherProps 里面就有 this.props 中所有的字段,处理 user。

8. 定义高阶组件的意义何在呢?
A: 重用代码。有时候很多 React 组件都需要公用同样一个逻辑,比如说 react-redux 中容器组件的部分,没有必要让每个组件都实现一遍 shouldComponentUpdate 这些生命周期函数,把这部分逻辑提取出来,利用高级组件的方式应用出去,就可以减少很多组件的重复代码
B: 修改现有 React 组件的行为。有些现成 React 组件并不是开发者自己开发的,来自于第三方,或者,即使是我们自己开发的,但是我们不想去触碰这些组件的内部逻辑,这时候高阶组件有了用武之地。通过一个独立于原有组件的函数,可以产生新的组件,对原有组件没有任何侵害

9. 根据返回的新组件和传入组件参数的关系,高阶组件的实现方式可以分为两类:
A: 代理方式的高阶组件
B: 继承方式的高阶组件

10. 上面的 removeUserProp 例子就是一个代理方式的高阶组件,特点是返回的新组件类直接继承自 React.Component 类。新组件扮演的角色是传入参数组件的一个“代理”,在新组建的 render 函数中,把被包裹组件渲染出来,除了高阶组件自己要做的工作,其余功能全部转手给了被包裹的组件。

11. 虽然这样写代码更少,但是为了代码逻辑更加清晰,在本章其他的例子中,我们还是统一让高阶组件返回一个 class,而不只是一个函数

12. 代理方式的高阶组件,可以应用在下列场景中:
A: 操纵 prop
B: 访问 ref
C: 抽取状态
D: 包装组件

13. 继承方式的高阶组件采用继承关系关联作为参数的组件和返回的组件,假如传入的组件参数是 WrappedComponent,那么返回的组件就直接继承自 WrappedComponent

14. 代理方式和继承方式最大的区别,是使用被包裹组件的方式。

15. 在代理方式下,render 函数中的使用被包裹组件是通过 JSX 代码:
return <WrappedComponent {…otherProps} />

16. 在继承方式下,render 函数中渲染被包裹组件的代码如下:
return super.render();

17. 需要注意,在代理方式下 WrappedComponent 经历了一个完整的生命周期,但在继承方式下 super.render 只是一个生命周期中的一个函数而已;在代理方式下产生的新组件和参数组件是两个不同的组件,一次渲染,两个组件都要经历各自的生命周期,在继承方式下两者合二为一,只有一个生命周期。

18. 继承方式的高阶组件可以应用于下列场景
A: 操作 prop
B: 操纵生命周期函数

19. 使用 React.cloneElement 让组件重新绘制,这样虽然可行,但是过程是在非常复杂,唯一用得上的场景就是高阶组件需要根据参数组件 WrappedComponent 渲染结果来决定如何修改 props。否则,实在没有必要用继承方式来实现这样的高阶组件,使用代理方式实现操纵 prop 的功能更加清晰。

20 因为继承方式的高阶函数返回的新组件继承了参数组件,所以可以重新定义任何一个 React 组件的生命周期。

21. 这是继承方式高阶函数特用的场景,代理方式无法修改传入组件的生命周期函数,所以不具备这个功能

22. 各方面看来代理方式都要优于继承方式

23. 业界有一句老话:“优先考虑组合,然后才考虑继承”(Composition over Inheritance)。前人诚不欺我,我们应该尽量使用代理方式来构建高阶组件

24. 每个高阶组件都会产生一个新的组件,使用这个新组件就丢失掉了参数组件的“显示名”,为了方便开发和维护,往往需要给高阶组件重新定义一个“显示名”,不然,在 debug 和日志中看到的组件名就会莫名其妙。增加“显示名”的方式就是给高阶组件类的 displayName 赋上一个字符串类型的值

25. 但是 Mixin 只能在用 React.createClass 方式创建的组件类中才能使用,不能在通过 ES6 语法创建的 React 组件中使用

26. 高阶组件也有缺点,那就是对原组件的 props 有了固化的要求。也就是说,能不能把一个高阶组件作用于某个组件 X,要先看一下这个组件 X 是不是能够接受高阶组件传过来的 props,如果组件 X 并不支持 props,或者对这些 props 的命名有不同,或者使用方式不是预期的方式,那也就没有办法应用这个高阶组件。

27.“以函数为子组件”的模式就是为了克服高阶组件的这种局限而生的。

28 从上面三个使用样例可以看得出来,利用这种模式非常灵活,因为 AddUserProp 预期子组件时一个函数,而函数使得一切皆有可能。

29. 作为 AddUserProp 子组件的函数,成为了连接父组件和底层组件的桥梁。一个函数可以包含各种逻辑,这样就给使用 AddUserProp 提供了最大的灵活性。“以函数为子组件”模式没有高阶组件那么多分类和应用场景,因为以函数为连接桥梁的方式已经提供了无数种用例。

30. 值得一说的是,setInterval 帮我们把第一个函数参数中的环境 this 设为组件实例,我们之所以能够在那个函数中直接通过 this 访问 this.state 和 this.setState,是因为我们 setInterval 第一个参数是 ES6 的箭头函数形式,箭头函数会自动将自身的 this 绑定为所处环境的 this,因为这个箭头函数所处环境是 componentDidMount,所以 this 自然就是组件实例本身。

31. 这种“以函数为子组件”的模式非常适合于制作动画,类似 CountDown 这样的例子决定动画每帧什么时候绘制,绘制的时候是什么样的数据,作为子组件的函数只要专注于使用参数来渲染就可以了

32 实际上,React 实际中的动画库 react-motion 就大量使用了“以函数为子组件”的模式

33.“以函数为子组件”模式可以让代码非常灵活,但是凡事都有优点也有缺点,这种模式也不例外,这种模式的缺点就是难以做性能优化。

34. 每次外层组件的更新过程,都要执行一个函数获取子组件的实际渲染结果,这个函数确实提供了灵活性。但是也因为每次渲染都要调用函数,无法利用 shouldComponentUpdate 来避免渲染浪费,使用高阶组件则可以直接使用 shouldComponentUpdate 来避免无谓的重新渲染。

35. 虽然“以函数为子组件”有这样性能上的潜在问题,但是它依然是一个非常棒的模式,实际上,在 react-motion 动画库中大量使用了这种模式,也没有发现特别大的性能问题。要知道,对于动画功能,性能是最重要的因素,react-motion 的用户群没有反映存在性能问题,可见这种模式是性能和灵活性的一个恰当折中

36. 高阶组件和“以函数为子组件”的模式,这两种用法的目的都是为了重用代码,当我们发现有的功能需要在多个组件中重复时,就可以考虑建一个高阶组件或者应用“以函数为子组件”的模式。

37. 和高阶组件相比,“以函数为子组件”的模式更加灵活,因为有函数的介入,连接两个组件的方式可以非常自由。

观后感回放:

粉丝路人甲:“曾经我以为前端就是写写页面,现在我错了,安西教练我想写代码!”
锵哥:“前端的学习曲线是从开始的平滑到后面的急速陡峭,别小看它!”
粉丝路人甲:“时代变化太快,跟紧锵哥的步伐!”
锵哥:“一起共勉,互相加油!”
粉丝路人甲:????”

广告:

本人从事全栈工程师,目前主要工作能力涵盖的范围有:android,ios,h5,pcWeb,react,vue,node,java 服务端,微信服务号,微信小程序,支付宝生活号,支付宝小程序。

本公众号会不定期的将自己的研发感悟,以及心得笔记无私奉献给大家。还等啥,赶快上车吧,铁子们!!!????(还会有其他的福利哦!快来吧)

官方订阅号:锵哥的觉悟
微信号:DY_suixincq
二维码:

退出移动版