React躬行记(3)——组件
组件(Component)由若干个React元素组成,包含属性、状态和生命周期等部分,满足独立、可复用、高内聚和低耦合等设计原则,每个React应用程序都是由一个个的组件搭建而成,即组成React应用程序的最小单元正是组件。
一、构建
目前推崇的构建组件的方式总共有两种:类和函数,而用React.createClass()构建组件的方式已经过时,本节也不会对其做讲解。
1)类组件
通过ES6新增的类构建而成的组件必须继承自React.Component,并且需要定义render()方法。此方法用于组件的输出,即组件的渲染内容,如下代码所示。注意,render()是一个纯函数,不会改变组件的状态,并且其返回值有多种,包括React元素、布尔值、数组等。
class Btn extends React.Component {
render() {
return <button>提交</button>;
}
}
2)函数组件
使用函数构建的组件只关注用户界面的展示,既无状态,也无生命周期。其功能相当于类组件的render()方法,但能接收一个属性对象(props),下面是一个简单的函数组件。
function Btn(props) {
return <button>{props.text}</button>;
}
与类组件不同,函数组件在调用时不会创建新实例。
二、state(组件状态)
组件中的state用于记录其内部状态,这类有状态组件会随着state的变化修改其最终的呈现。
1)初始化
在组件的构造函数constructor()中可以像下面这样,通过this.state初始化组件的内部状态,其中this.state必须是一个对象。
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(将在下一节讲解),此处函数的功能是交替变换按钮的文本。
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()方法)。
class Btn extends React.Component {
change() {
this.setState({ text: "点击" });
}
}
setState()是一个异步方法,React会将多个setState()方法合并成一个调用,也就是说,在调用setState()后,不能马上反映出状态的变化。例如this.state.text的值原先是“提交”,在像下面这样更新状态后,打印出的值仍然是“提交”。
this.setState({text: "点击"});
console.log(this.state.text); //"提交"
setState()方法在将新数据合并到当前状态之后,就会自动调用render()方法,驱动组件重新渲染。由此可知,在render()方法中不允许调用setState()方法,以免造成死循环。
在后面的生命周期一节中会讲解组件在各个阶段可用的回调函数。其中有些回调函数得在render()方法被执行后再被调用,因此在它们内部通过this.state得到的将是更新后的内部状态。
三、props(组件属性)
props(properties的缩写)能接收外部传递给组件的数据,当组件作为React元素使用时,props就是一个由元素属性所组成的对象。以Btn组件为例,它的props的结构如下所示,其中children是一个特殊属性,后续将会单独讲解。
<Btn name="strick" digit={0}>提交</Btn>
props = { name: "strick", digit: 0, children: "提交" }
1)读取
每个组件都会有一个构造函数,而它的参数正是props。由于React组件相当于一个纯函数,因此props不能被修改,它的属性都是只读的,像下面这样赋值势必会引起组件的副作用,因而React会马上终止程序,直接抛出错误。
class Btn extends React.Component {
constructor(props) {
super(props);
props.name = "freedom"; //错误
}
}
因为有此限制,所以若要修改props中的某个属性,通常会先将它赋给state,再通过state更新数据,如下所示。
class Btn extends React.Component {
constructor(props) {
super(props);
this.state = {
name: props.name
};
}
}
在构造函数之外,可通过this.props访问到传递进来的数据。
2)defaultProps
组件的静态属性defaultProps可为props指定默认值,例如为组件设置默认的name属性,当props.name缺省时,就能用该值,如下所示。
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的值为“搜索”。
<Btn>搜索</Btn>
children可以是null、字符串或对象等数据类型,并且当组件的内容是多个子组件时,children还能自动变成一个数组。
官方通过React.Children给出了专门处理children的辅助方法,例如用于遍历的forEach(),如下代码所示,其余方法可参考表1。
React.Children.forEach(props.children, child => {
console.log(child);
});
表1 辅助方法
方法 | 描述 |
map() | 当children不是null或undefined时,遍历children并返回一个数组,否则返回null或undefined |
forEach() | 功能与map()类似,但不返回数组 |
count() | 计算children的数量 |
only() | 当children是一个React元素时,返回该元素,否则抛出错误 |
toArray() | 将children转换成数组 |
当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属性限制为数字,可以像下面这样设置。
Btn.propTypes = {
age: PropTypes.number
}
在引入该库后,就会有一个全局对象PropTypes。除了数字类型之外,PropTypes还提供了其它类型的校验,具体对应关系可参考表2。
表2 对应关系
PropTypes中的属性 | 对应类型 |
PropTypes.array | 数组 |
PropTypes.bool | 布尔值 |
PropTypes.func | 函数 |
PropTypes.number | 数字 |
PropTypes.object | 对象 |
PropTypes.string | 字符串 |
PropTypes.symbol | 符号 |
PropTypes.node | 可被渲染的元素,例如数字、React元素等 |
PropTypes.element | React元素 |
PropTypes.elementType | React元素类型 |
当组件的属性是对象或数组时,PropTypes能校验其成员的类型,例如要求数组的成员都得是字符串、对象的某个属性必须是布尔值,可以像下面这样操作。
Btn.propTypes = {
names: PropTypes.arrayOf(PropTypes.string),
person: PropTypes.shape({ isMan: PropTypes.bool })
}
PropTypes还能在其任意属性后加isRequired标记,例如PropTypes.number.isRequired表示必须传数字类型的属性,并且不能缺省。下面示例中的school属性,不限制类型,只要有值就行。
Btn.propTypes = {
school: PropTypes.any.isRequired
}
此节只列出了prop-types库中的部分功能,其余功能可参考官方文档。
5)数据流
在React中,组件之间的数据是自顶向下单向流动,即父组件通过props将数据传递给子组件(如图3所示),以此实现它们之间的对话和联系。
图3 单向数据流
举个简单的例子,有两个组件Container和Btn,其中Container是父组件,Btn是子组件,Container组件会将它的text属性传递给Btn组件,以此完成数据的流动。为了便于观察,省略了两个组件的构造函数,具体如下所示。
class Btn extends React.Component {
render() {
return <button>{this.props.text}</button>;
}
}
class Container extends React.Component {
render() {
return <Btn text="提交" />;
}
}
四、列表和Keys
在组件中渲染列表数据是非常常见的需要,例如输出多个按钮,如下代码所示。
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属性,再为这个数组的每个元素加上<button>标签,最后得到元素列表list后,将其作为返回值输出。不过,此时会收到一个要求为列表中的子元素添加key属性的警告,如图4所示。
图4 key属性的警告
在React中,Keys会作为元素的身份标识,能够帮助React识别出发生变化的元素,从而只渲染这些元素。每个元素的key属性在当前列表中要保持唯一性,即在其兄弟元素之间要独一无二,如下代码所示(省略了组件的构造函数)。
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属性的<button>元素,但分别被嵌到了两个<section>元素中,从而将两者隔离,达成了key属性唯一的目标。由此可知,key属性不是全局唯一的。
注意,一般不建议用数组的索引作为key属性的值,因为一旦数组中元素的位置发生变化,其索引也会跟着改变,不利于渲染优化。
关于在何时设置key属性,有个简单的规则可以参考,那就是当元素位于map()方法内时,需要为该元素添加key属性。
React躬行记(3)——组件的更多相关文章
- React躬行记(7)——表单
表单元素是一类拥有内部状态的元素,这些状态由其自身维护,通过这类元素可让用户与Web应用进行交互.HTML中的表单元素(例如<input>.<select>和<radio ...
- React躬行记(9)——组件通信
根据组件之间的嵌套关系(即层级关系)可分为4种通信方式:父子.兄弟.跨级和无级. 一.父子通信 在React中,数据是自顶向下单向流动的,而父组件通过props向子组件传递需要的信息是组件之间最常见的 ...
- React躬行记(10)——高阶组件
高阶组件(High Order Component,简称HOC)不是一个真的组件,而是一个没有副作用的纯函数,以组件作为参数,返回一个功能增强的新组件,在很多第三方库(例如Redux.Relay等)中 ...
- React躬行记(5)——React和DOM
React实现了一套与浏览器无关的DOM系统,包括元素渲染.节点查询.事件处理等机制. 一.ReactDOM 自React v0.14开始,官方将与DOM相关的操作从React中剥离,组成单独的rea ...
- React躬行记(8)——样式
由于React推崇组件模式,因此会要求HTML.CSS和JavaScript混合在一起,虽然这与过去的关注点分离正好相反,但是更有利于组件之间的隔离.React已将HTML用JSX封装,而对CSS只进 ...
- React躬行记(2)——JSX
JSX既不是字符串,也不是HTML,而是一种类似XML,用于描述用户界面的JavaScript扩展语法,如下代码所示.在使用JSX时,为了避免自动插入分号时出现问题,推荐在其最外层用圆括号包裹,并且必 ...
- React躬行记(4)——生命周期
组件的生命周期(Life Cycle)包含三个阶段:挂载(Mounting).更新(Updating)和卸载(Unmounting),在每个阶段都会有相应的回调方法(也叫钩子)可供选择,从而能更好的控 ...
- React躬行记(6)——事件
React在原生事件的基础上,重新设计了一套跨浏览器的合成事件(SyntheticEvent),在事件传播.注册方式.事件对象等多个方面都做了特别的处理. 一.注册事件 合成事件采用声明式的注册方式, ...
- React躬行记(11)——Redux基础
Redux是一个可预测的状态容器,不但融合了函数式编程思想,还严格遵循了单向数据流的理念.Redux继承了Flux的架构思想,并在此基础上进行了精简.优化和扩展,力求用最少的API完成最主要的功能,它 ...
随机推荐
- Visual studio 创建通用项目失败vstemplate
Visual studio 创建项目失败 提示 the vstemplate file references the wizard class 'Microsoft.VisualStudio.WinR ...
- Win10的UWP之进度条
原文:Win10的UWP之进度条 关于UWP的进度条的处理的方案有两种方案 我们新建一个项目,然后处理的界面如下的代码 <Grid.RowDefinitions> <RowDefin ...
- 【备忘】WPF基础
XAML 为了避免生成用户界面(GUI)的代码和基于用户操作执行的代码混合在一起. 名称空间 值得注意的名称空间: xmlns="http://schemas.microsoft.com/w ...
- css的圣杯布局
圣杯布局和双飞翼布局实现的效果是一样的. 代码解析: 1.四个section,container,main,left,right.其中那个container为父容器. 2.main,left,righ ...
- Natively Compiled Code: A Comeback?
RAD Studio and Natively Compiled Code In today's development landscape, natively compiled code is ma ...
- Delphi 与 VC 共享接口和对象
我经常会用 Delphi 写一些工具和应用,为了扩展方便,大部分都会做成插件形式. 迫于某些原因,我的插件不得不用其他开发工具来完成,比如 VC. 于是有个大问题需要解决:如何让 D 和 VC 互相通 ...
- wpf中的datagrid绑定操作按钮是否显示或者隐藏
如图,需要在wpf中的datagrid的操作那列有个确认按钮,然后在某些条件下确认按钮可见,某些情况下不可见的,放在mvc里直接在cshtml页面中if..else就行了. 但是在wpf里不行..网上 ...
- KmdKit4D 0.01正式版发布了(0.02版已放出)(Delphi做驱动)
此版本较0.01预览版已经有了脱胎换骨的变化,主要表现在以下几个方面: 1.对程序的结构进行了调整,将原来的ntutils.dcu分成fcall.dcu.halfcall.dcu和macros. ...
- ASP.NET Web API Controller 是怎么建成的
先看ASP.NET Web API 讯息管线: 註:为了避免图片太大以至于超过版面,上图中的「HTTP 讯息处理程序」区块省略了 HttpRoutingDispatcher 处理路由分派的部分.「控制 ...
- 使用sikuli软件进行自动化编程
因为工作上的需要,某个信息系统不健全,因此仅仅需要一个一个的点击确认,客户端是网页版本的,抓包太复杂了,如何快速的能够自动化操作? 想到了之前学习python的时候,发现了一个基于java的图片编程软 ...