组件(Component)由若干个React元素组成,包含属性、状态和生命周期等部分,满足独立、可复用、高内聚和低耦合等设计原则,每个React应用程序都是由一个个的组件搭建而成,即组成React应用程序的最小单元正是组件。
## 一、构建
  目前推崇的构建组件的方式总共有两种:类和函数,而用React.createClass()构建组件的方式已经过时,本节也不会对其做讲解。
**1)类组件**
  通过ES6新增的类构建而成的组件必须继承自React.Component,并且需要定义render()方法。此方法用于组件的输出,即组件的渲染内容,如下代码所示。注意,render()是一个纯函数,不会改变组件的状态,并且其返回值有多种,包括React元素、布尔值、数组等。
~~~js
class Btn extends React.Component {
render() {
return <button>提交</button>;
}
}
~~~
**2)函数组件**
  使用函数构建的组件只关注用户界面的展示,既无状态,也无生命周期。其功能相当于类组件的render()方法,但能接收一个属性对象(props),下面是一个简单的函数组件。
~~~js
function Btn(props) {
return <button>{props.text}</button>;
}
~~~
  与类组件不同,函数组件在调用时不会创建新实例。
## 二、state(组件状态)
  组件中的state用于记录其内部状态,这类有状态组件会随着state的变化修改其最终的呈现。
**1)初始化**
  在组件的构造函数constructor()中可以像下面这样,通过this.state初始化组件的内部状态,其中this.state必须是一个对象。
~~~js
class Btn extends React.Component {
constructor() {
super();
this.state = {
text: "提交"
};
}
render() {
return <button>{this.state.text}</button>;
}
}
~~~
  注意,在初始化之前要先调用super(),因为ES6对两个类的this的初始化顺序做了规定,先父类,再子类,所以super()方法要在使用this之前调用。
  如果要读取this.state中的数据,那么可以像上面的代码那样通过成员访问运算符得到。但如果要更新this.state中的数据,那么就得用setState()方法,而不是用运算符。
**2)setState()**
  此方法能接收2个参数,第一个是函数或对象,第二个是可选的回调函数,会在更新之后触发。下面用示例讲解第一个参数的两种情况(省略了构造函数以及初始化的代码),当它是函数时,能接收2个参数,第一个是当前的state,第二个是组件的props(将在下一节讲解),此处函数的功能是交替变换按钮的文本。
~~~js
class Btn extends React.Component {
change() {
this.setState((state, props) => {
return { text: state.text == "点击" ? "提交" : "点击" };
});
}
render() {
return <button onClick={this.change.bind(this)}>{this.state.text}</button>;
}
}
~~~
  当setState()方法的第一个参数是对象时,可以将要更新的数据传递进来,就像下面这样(省略了render()方法)。
~~~js
class Btn extends React.Component {
change() {
this.setState({ text: "点击" });
}
}
~~~
  setState()是一个异步方法,React会将多个setState()方法合并成一个调用,也就是说,在调用setState()后,不能马上反映出状态的变化。例如this.state.text的值原先是“提交”,在像下面这样更新状态后,打印出的值仍然是“提交”。
~~~js
this.setState({text: "点击"});
console.log(this.state.text); //"提交"
~~~
  setState()方法在将新数据合并到当前状态之后,就会自动调用render()方法,驱动组件重新渲染。由此可知,在render()方法中不允许调用setState()方法,以免造成死循环。
  在后面的生命周期一节中会讲解组件在各个阶段可用的回调函数。其中有些回调函数得在render()方法被执行后再被调用,因此在它们内部通过this.state得到的将是更新后的内部状态。
## 三、props(组件属性)
  props(properties的缩写)能接收外部传递给组件的数据,当组件作为React元素使用时,props就是一个由元素属性所组成的对象。以Btn组件为例,它的props的结构如下所示,其中children是一个特殊属性,后续将会单独讲解。
~~~js
<Btn name="strick" digit={0}>提交</Btn>
props = { name: "strick", digit: 0, children: "提交" }
~~~
**1)读取**
  每个组件都会有一个构造函数,而它的参数正是props。由于React组件相当于一个纯函数,因此props不能被修改,它的属性都是只读的,像下面这样赋值势必会引起组件的副作用,因而React会马上终止程序,直接抛出错误。
~~~js
class Btn extends React.Component {
constructor(props) {
super(props);
props.name = "freedom"; //错误
}
}
~~~
  因为有此限制,所以若要修改props中的某个属性,通常会先将它赋给state,再通过state更新数据,如下所示。
~~~js
class Btn extends React.Component {
constructor(props) {
super(props);
this.state = {
name: props.name
};
}
}
~~~
  在构造函数之外,可通过this.props访问到传递进来的数据。
**2)defaultProps**
  组件的静态属性defaultProps可为props指定默认值,例如为组件设置默认的name属性,当props.name缺省时,就能用该值,如下所示。
~~~js
class Btn extends React.Component {
constructor(props) {
super(props);
}
render() {
return <button>{this.props.name}</button>;
}
}
Btn.defaultProps = {
name: "freedom"
};
~~~
**3)children**
  每个props都会包含一个特殊的属性:children,表示组件的内容,即所包裹的子组件。例如下面这个Btn组件,其props.children的值为“搜索”。
~~~js
<Btn>搜索</Btn>
~~~
  children可以是null、字符串或对象等数据类型,并且当组件的内容是多个子组件时,children还能自动变成一个数组。
  官方通过React.Children给出了专门处理children的辅助方法,例如用于遍历的forEach(),如下代码所示,其余方法可参考表1。
~~~js
React.Children.forEach(props.children, child => {
console.log(child);
});
~~~
:-: ![](https://box.kancloud.cn/25b4bc53b224d6d8069a5124255d19e5_720x252.jpg)
:-: 表1 辅助方法
  当children是数组时,React.Children中的map()和forEach()两个方法与数组中的功能类似,并且count()方法的返回值与数组的length属性相同。但当children是其它类型时,用React.Children中的辅助方法会比较保险,例如children为一个子组件“strick”,调用count()方法得到的值为1,而调用length属性得到的值为6,这与当前子组件的数量不符。
**4)校验属性**
  自React v15.5起,官方弃用了React.PropTypes,改用prop-types库。此库能校验props中属性的类型,例如将Btn组件的age属性限制为数字,可以像下面这样设置。
~~~js
Btn.propTypes = {
age: PropTypes.number
}
~~~
  在引入该库后,就会有一个全局对象PropTypes。除了数字类型之外,PropTypes还提供了其它类型的校验,具体对应关系可参考表2。
:-: ![](https://box.kancloud.cn/1ee7b12271bf7c3d1301c4f91b085b39_720x668.jpg)
:-: 表2 对应关系
  当组件的属性是对象或数组时,PropTypes能校验其成员的类型,例如要求数组的成员都得是字符串、对象的某个属性必须是布尔值,可以像下面这样操作。
~~~js
Btn.propTypes = {
names: PropTypes.arrayOf(PropTypes.string),
person: PropTypes.shape({ isMan: PropTypes.bool })
}
~~~
  PropTypes还能在其任意属性后加isRequired标记,例如PropTypes.number.isRequired表示必须传数字类型的属性,并且不能缺省。下面示例中的school属性,不限制类型,只要有值就行。
~~~js
Btn.propTypes = {
school: PropTypes.any.isRequired
}
~~~
  此节只列出了prop-types库中的部分功能,其余功能可参考官方文档。
**5)数据流**
  在React中,组件之间的数据是自顶向下单向流动,即父组件通过props将数据传递给子组件(如图3所示),以此实现它们之间的对话和联系。
:-: ![](https://box.kancloud.cn/77911b1a427870b40e224b3e16e127f5_1590x760.png =400x)
:-: 图3 单向数据流
  举个简单的例子,有两个组件Container和Btn,其中Container是父组件,Btn是子组件,Container组件会将它的text属性传递给Btn组件,以此完成数据的流动。为了便于观察,省略了两个组件的构造函数,具体如下所示。
~~~js
class Btn extends React.Component {
render() {
return <button>{this.props.text}</button>;
}
}
class Container extends React.Component {
render() {
return <Btn text="提交" />;
}
}
~~~
## 四、列表和Keys
  在组件中渲染列表数据是非常常见的需要,例如输出多个按钮,如下代码所示。
~~~js
class Btns extends React.Component {
constructor(props) {
super(props);
}
render() {
const list = this.props.names.map(value => <button>{value}</button>);
return <div>{list}</div>;
}
}
ReactDOM.render(
<Btns names={[1,2,3]}>按钮列表</Btns>,
document.getElementById("container")
);
~~~
  在上面的render()方法中,先通过map()方法遍历传递进来的names属性,再为这个数组的每个元素加上标签,最后得到元素列表list后,将其作为返回值输出。不过,此时会收到一个要求为列表中的子元素添加key属性的警告,如图4所示。
:-: ![](https://box.kancloud.cn/39224d01bf8c24601fdc6f5ace6982e9_1269x155.png =600x)
图4 key属性的警告
  在React中,Keys会作为元素的身份标识,能够帮助React识别出发生变化的元素,从而只渲染这些元素。每个元素的key属性在当前列表中要保持唯一性,即在其兄弟元素之间要独一无二,如下代码所示(省略了组件的构造函数)。
~~~js
class Btns extends React.Component {
render() {
const list1 = this.props.names.map(value => <button key={value}>{value}</button>);
const list2 = this.props.names.map(value => <button key={value}>{value}</button>);
return (
<div>
<section>{list1}</section>
<section>{list2}</section>
</div>
);
}
}
~~~
  在上面的render()方法中,有两个元素列表:list1和list2,虽然它们包含相同key属性的元素,但分别被嵌到了两个元素中,从而将两者隔离,达成了key属性唯一的目标。由此可知,key属性不是全局唯一的。
  注意,一般不建议用数组的索引作为key属性的值,因为一旦数组中元素的位置发生变化,其索引也会跟着改变,不利于渲染优化。
  关于在何时设置key属性,有个简单的规则可以参考,那就是当元素位于map()方法内时,需要为该元素添加key属性。
*****
> 原文出处:
[博客园-React躬行记](https://www.cnblogs.com/strick/category/1455720.html)
[知乎专栏-React躬行记](https://zhuanlan.zhihu.com/pwreact)
已建立一个微信前端交流群,如要进群,请先加微信号freedom20180706或扫描下面的二维码,请求中需注明“看云加群”,在通过请求后就会把你拉进来。还搜集整理了一套[面试资料](https://github.com/pwstrick/daily),欢迎浏览。
![](https://box.kancloud.cn/2e1f8ecf9512ecdd2fcaae8250e7d48a_430x430.jpg =200x200)
推荐一款前端监控脚本:[shin-monitor](https://github.com/pwstrick/shin-monitor),不仅能监控前端的错误、通信、打印等行为,还能计算各类性能参数,包括 FMP、LCP、FP 等。
- ES6
- 1、let和const
- 2、扩展运算符和剩余参数
- 3、解构
- 4、模板字面量
- 5、对象字面量的扩展
- 6、Symbol
- 7、代码模块化
- 8、数字
- 9、字符串
- 10、正则表达式
- 11、对象
- 12、数组
- 13、类型化数组
- 14、函数
- 15、箭头函数和尾调用优化
- 16、Set
- 17、Map
- 18、迭代器
- 19、生成器
- 20、类
- 21、类的继承
- 22、Promise
- 23、Promise的静态方法和应用
- 24、代理和反射
- HTML
- 1、SVG
- 2、WebRTC基础实践
- 3、WebRTC视频通话
- 4、Web音视频基础
- CSS进阶
- 1、CSS基础拾遗
- 2、伪类和伪元素
- 3、CSS属性拾遗
- 4、浮动形状
- 5、渐变
- 6、滤镜
- 7、合成
- 8、裁剪和遮罩
- 9、网格布局
- 10、CSS方法论
- 11、管理后台响应式改造
- React
- 1、函数式编程
- 2、JSX
- 3、组件
- 4、生命周期
- 5、React和DOM
- 6、事件
- 7、表单
- 8、样式
- 9、组件通信
- 10、高阶组件
- 11、Redux基础
- 12、Redux中间件
- 13、React Router
- 14、测试框架
- 15、React Hooks
- 16、React源码分析
- 利器
- 1、npm
- 2、Babel
- 3、webpack基础
- 4、webpack进阶
- 5、Git
- 6、Fiddler
- 7、自制脚手架
- 8、VSCode插件研发
- 9、WebView中的页面调试方法
- Vue.js
- 1、数据绑定
- 2、指令
- 3、样式和表单
- 4、组件
- 5、组件通信
- 6、内容分发
- 7、渲染函数和JSX
- 8、Vue Router
- 9、Vuex
- TypeScript
- 1、数据类型
- 2、接口
- 3、类
- 4、泛型
- 5、类型兼容性
- 6、高级类型
- 7、命名空间
- 8、装饰器
- Node.js
- 1、Buffer、流和EventEmitter
- 2、文件系统和网络
- 3、命令行工具
- 4、自建前端监控系统
- 5、定时任务的调试
- 6、自制短链系统
- 7、定时任务的进化史
- 8、通用接口
- 9、微前端实践
- 10、接口日志查询
- 11、E2E测试
- 12、BFF
- 13、MySQL归档
- 14、压力测试
- 15、活动规则引擎
- 16、活动配置化
- 17、UmiJS版本升级
- 18、半吊子的可视化搭建系统
- 19、KOA源码分析(上)
- 20、KOA源码分析(下)
- 21、花10分钟入门Node.js
- 22、Node环境升级日志
- 23、Worker threads
- 24、低代码
- 25、Web自动化测试
- 26、接口拦截和页面回放实验
- 27、接口管理
- 28、Cypress自动化测试实践
- 29、基于Electron的开播助手
- Node.js精进
- 1、模块化
- 2、异步编程
- 3、流
- 4、事件触发器
- 5、HTTP
- 6、文件
- 7、日志
- 8、错误处理
- 9、性能监控(上)
- 10、性能监控(下)
- 11、Socket.IO
- 12、ElasticSearch
- 监控系统
- 1、SDK
- 2、存储和分析
- 3、性能监控
- 4、内存泄漏
- 5、小程序
- 6、较长的白屏时间
- 7、页面奔溃
- 8、shin-monitor源码分析
- 前端性能精进
- 1、优化方法论之测量
- 2、优化方法论之分析
- 3、浏览器之图像
- 4、浏览器之呈现
- 5、浏览器之JavaScript
- 6、网络
- 7、构建
- 前端体验优化
- 1、概述
- 2、基建
- 3、后端
- 4、数据
- 5、后台
- Web优化
- 1、CSS优化
- 2、JavaScript优化
- 3、图像和网络
- 4、用户体验和工具
- 5、网站优化
- 6、优化闭环实践
- 数据结构与算法
- 1、链表
- 2、栈、队列、散列表和位运算
- 3、二叉树
- 4、二分查找
- 5、回溯算法
- 6、贪心算法
- 7、分治算法
- 8、动态规划
- 程序员之路
- 大学
- 2011年
- 2012年
- 2013年
- 2014年
- 项目反思
- 前端基础学习分享
- 2015年
- 再一次项目反思
- 然并卵
- PC网站CSS分享
- 2016年
- 制造自己的榫卯
- PrimusUI
- 2017年
- 工匠精神
- 2018年
- 2019年
- 前端学习之路分享
- 2020年
- 2021年
- 2022年
- 2023年
- 日志
- 2020