第二章 设计高质量的React组件——《深入浅出React和Redux》读书笔记
第2章 设计高质量的React组件
作为一个合格的开发者,不要只满足于编写出了可以运行的代码,而要了解代码背后的工作原理;不要只满足于自己编写的程序能够运行,还要让自己的代码可读而且易于维护。这样才能开发出高质量的软件。
在这一章里,我们将深入介绍构建高质量React组件的原则和方法,包括以下内容:
划分组件边界的原则;
React组件的数据种类;
React组件的生命周期。
本章的内容只是React组件设计的基础知识,因为React应用都是围绕组件的设计,所以关于组件的设计介绍将贯穿全书。
2.1 易于维护组件的设计要素
当开发者发现一个组件功能太多代码量太大的时候,就要考虑拆分这个组件,用多个小的组件来代替。每个小的组件只关注实现单个功能,但是这些功能组合起来,也能满足复杂的实际需求。
这就是“分而治之”的策略,把问题分解为多个小问题,这样既容易解决也方便维护,虽然“分而治之”是一个好策略,但是不要滥用,只有必要的时候才去拆分组件,不然可能得不偿失。
拆分组件最关键的就是确定组件的边界,每个组件都应该是可以独立存在的,如果两个组件逻辑太紧密,无法清晰定义各自的责任,那也许这两个组件本身就不该被拆开,作为同一个组件也许更合理。
虽然组件是应该独立存在的,但是并不是说组件就是孤岛一样的存在,不同组件之间总是会有通信交流,这样才可能组合起来完成更大的功能。
作为软件设计的通则,组件的划分要满足高内聚(High Co-hesion)和低耦合(Low Coupling)的原则。
高内聚指的是把逻辑紧密相关的内容放在一个组件中。react的JSX把html,css,js都放在同一个JavaScript文件中,所以说React天生具有高内聚的特点。
低耦合指的是不同组件之间的依赖关系要尽量弱化,也就是每个组件要尽量独立。保持整个系统的低耦合度,需要对系统中的功能有充分的认识,然后根据功能点划分模块,让不同的组件去实现不同的功能,这个功夫还在开发者身上,不过,React组件的对外接口非常规范,方便开发者设计低耦合的系统。
2.2 React组件的数据
“差劲的程序员操心代码,优秀的程序员操心数据结构和它们之间的关系。”—Linus Torvalds,Linux创始人
如何组织数据是程序的最重要问题。
React组件的数据分为两种,prop和state,无论prop或者state的改变,都可能引发组件的重新渲染,那么,设计一个组件的时候,什么时候选择用prop什么时候选择用state呢?其实原则很简单,prop是组件的对外接口,state是组件的内部状态,对外用prop,内部用state。
2.2.1 React的prop
1. 给prop赋值
注:当prop的类型不是字符串类型时,在JSX中必须用花括号{}把prop值包住,所以style的值有两层花括号,外层花括号代表是JSX的语法,内层的花括号代表这是一个对象常量。
当外部世界要传递一些数据给React组件,一个最直接的方式就是通过prop;同样,React组件要反馈数据给外部世界,也可以用prop。
2. 读取prop值
注:一定要记得在构造函数的第一行通过super调用父类也就是React.Component的构造函数。如果在构造函数中没有调用super(props),那么组件实例被构造之后,类实例的所有成员函数就无法通过this.props访问到父组件传递过来的props值。
3. propTypes检查
2.2.2 React的state
1.初始化state
2.读取和更新state
2.2.3 prop和state的对比
总结一下prop和state的区别:
prop用于定义外部接口,state用于记录内部状态;
prop的赋值在外部世界使用组件时,state的赋值在组件内部;
组件不应该改变prop的值,而state存在的目的就是让组件来改变的。
组件的state,就相当于组件的记忆,其存在意义就是被修改,每一次通过this.setState函数修改state就改变了组件的状态,然后通过渲染过程把这种变化体现出来。
2.3 组件的生命周期

可以结合这篇文章来看
2.3.1 装载过程
当组件第一次被渲染的时候,依次调用的函数是如下这些:
constructor
getInitialState
getDefaultProps
componentWillMount
render
componentDidMount
1. constructor
一个React组件需要构造函数,往往是为了下面的目的:
初始化state
绑定成员函数的this环境
2. getInitialState和getDefaultProps
3. render
通常一个组件要发挥作用,总是要渲染一些东西,render函数并不做实际的渲染动作,它只是返回一个JSX描述的结构,最终由React来操作渲染过程。
render函数应该是一个纯函数,完全根据this.state和this.props来决定返回的结果,而且不要产生任何副作用。在render函数中去调用this.setState毫无疑问是错误的,因为一个纯函数不应该引起状态的改变。
4. componentWillMount和componentDidMount
componentDidMount被调用的时候,render函数返回的东西已经引发了渲染,组件已经被“装载”到了DOM树上。
2.3.2 更新过程
当props或者state被修改的时候,就会引发组件的更新过程。
更新过程会依次调用下面的生命周期函数,其中render函数和装载过程一样,没有差别。
componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate
render
componentDidUpdate
2.3.3 卸载过程
2.4 组件向外传递数据
解决这个问题的方法,依然是利用prop。组件的prop可以是任何JavaScript对象,而在JavaScript中,函数是一等公民,函数本身就可以被看做一种对象,既可以像其他对象一样作为prop的值从父组件传递给子组件,又可以被子组件作为函数调用,这样事情就好办了。
2.5 React组件state和prop的局限
组件之间通过状态提升来传递数据。使用React的state来存储状态的一个缺点,那就是数据的冗余和重复。
数据如果出现重复,带来的一个问题就是如何保证重复的数据一致,如果数据存多份而且不一致,那就很难决定到底使用哪个数据作为正确结果了。
图2-5 组件状态不一致的困惑
除了state,利用prop在组件之间传递信息也会遇到问题。设想一下,在一个应用中,包含三级或者三级以上的组件结构,顶层的祖父级组件想要传递一个数据给最低层的子组件,用prop的方式,就只能通过父组件中转,如图2-6所示。
也许中间那一层父组件根本用不上这个prop,但是依然需要支持这个prop,扮演好搬运工的角色,只因为子组件用得上,这明显违反了低耦合的设计要求。第3章中我们会探讨如何解决这样的困局。
图2-6 跨级传递prop的困局
另一种思路,就是干脆不要让任何一个React组件扮演“领头羊”的角色,把数据源放在React组件之外形成全局状态,如图2-7所示,让各个组件保持和全局状态的一致,这样更容易控制。
图2-7 React中提取出来
图2-6中所示,全局状态就是唯一可靠的数据源,这就是Flux和Redux中Store的概念。
2.6 本章小结
在这一章中,我们学习了构建高质量组件的原则,应用Re-act一样要以构建高内聚低耦合的组件为目标,而保证组件高质量的一个重要工作就是保持组件对外接口清晰简洁。
React利用prop来定义组件的对外接口,用state来代表内部的状态,某个数据选择用prop还是用state表示,取决于这个数据是对外还是对内。
我们还介绍了React的生命周期,了解了装载过程、更新过程和卸载过程涉及的所有生命周期函数。