在第二篇文章 《新型前端开发方式》 中有说到 React 有很爽的一点就是给我们一种创造 HTML 标签的能力,那么今天这篇文章就详细讲解下 React 是如何提供这种能力的,作为前端开发者如何来运用这种能力。

在第三篇文章 《JavaScript代码里写HTML一样可以很优雅》 中介绍了 JavaScript 的扩展语法 JSX,相信大家已经知道了,所谓的创造新的 HTML 的能力,其实就是以极其类似 HTML 的 JSX 语法来使用基于 React 编写的视图层组件。所以说,要完成今天的任务,我们只需要搞清楚一个问题即可:如何基于 React 编写视图层组件。

内容摘要

  • 定义组件两种方式:类继承组件、函数式组件。
  • 类继承组件有更丰富的特性,函数式组件书写更简洁,执行效率更高。
  • 组件名称首字母要大写。
  • 属性是一个组件的外部输入。
  • 属性值可以通过 {} 设置任意的 JS 表达式。
  • 属性是只读的。
  • 属性可以设置默认值。
  • 属性可以设置类型,开发阶段 React 会对属性进行类型检查。
  • 为组件所有属性设置类型检查是个好习惯,有助于协作开发。

通过内容摘要可以让你快速了解本文内容是否对你有用,从而决定是否继续阅读,节省你的时间也是一件很有意义的事情。

定义组件的几种姿势

下面介绍一下在 React 中定义组件的几种方式。

1. 类继承

有过 Java 等面向对象开发经验的同学一定很容易接受这种方式。ES6 为 JavaScript 增加了类和类继承的特性。子类会继承父类的“基因”(成员方法、属性),如果父类是一个组件,那子类自然而然也是一个组件。

React 提供了 ComponentPureComponent 两个父类,他们之间有一点点区别,我们在之后的文章中会详细介绍,现在你可以将他们同等看待,暂且无须理会。

通过继承自 React 提供的组件基类,我们可以这样来创建一个组件:

import React, {Component} from 'react';class HelloMessage extends Component {render() {return <div>Hello world.</div>;}
}

通过类继承的方式创建一个组件,就是这么简单,只要继承 Component 基类并实现 render 方法即可。然后就可以把 HelloMessage 当成一个新的“HTML 标签”来用了,如下你可以把它渲染到页面上:

ReactDOM.render(<HelloMessage />, document.querySelector('#root'));

你也可以用它来装配其它组件,如:

import React, {Component} from 'react';class HelloMessageList extends React.Component {render() {return (<div><HelloMessage /><HelloMessage /><HelloMessage /></div>)}
}

当然,例子没有任何实际意义,只是为了演示组件的定义及其用法。

演示代码:https://codepen.io/Sarike/pen...

2. 函数式组件

顾名思义,函数式组件,就是以函数的形式来定义一个组件,如下所示:

import React from 'react';function HelloMessage() {return <div>Hello world.</div>;
}// 或者:const HelloMessage = () => <div>Hello world.</div>;

实际上就是只实现了类继承方式中的 render 方法。

示例代码:https://codepen.io/Sarike/pen...

类继承 vs 函数式组件

这两种定义组件的方式,在实际的开发中都经常会被用到,对大部分人来说类继承的方式用得频率会更高一些。

类继承的方式,相较于函数式组件,虽然写起来略繁琐,但是它拥有更多的特性:

  • 内部状态:state
  • 生命周期函数

函数式组件虽然没有 state 和生命周期函数等特性,但是它有更简洁的书写方式,另外还有更好的性能,不用处理一些复杂的特性,执行效率当然高了。

现在你可以无需关心 state 和生命周期函数的具体作用,下一篇文章我会详细讲解,等你看完下一篇文章之后,至于选择哪种方式的问题就很好解决了。在开发一个组件的时候,我是这样来做的:当我一开始就知道这个组件会用到 state 或者生命周期函数时,毫无疑问直接使用类继承的方式;如果一开始用不到这些特性也不确定未来会不会用到,那我就先用函数式组件,如果随着业务的演进,组件需要应用这些特性的时候,我会再把它重构成类继承的方式。这个重构非常简单,只需要将原来的函数变成组件类的 render 方法即可。

另外,还有一点需要注意,不管那种方式,组件的名称首字母必须为大写。严格来说,是 JSX 要求用户自定义的组件名首字母必须为大写,如果是小写字母开头,那么 React 会将其当成内置的组件直接将其渲染成一个 html 标签,从而不会正确渲染用户自定义的组件。

如果你非要将组件名称以小写字母开头,那你在以 JSX 语法使用之前也必须将其赋值为一个大写字母开头的变量,如下所示:

function helloMessage() {return <div>Hello world.</div>
}const HelloMessage = helloMessage;ReactDOM.render(<HelloMessage />, mountNode);

但这有事何必呢,纯粹是没事儿找事儿,大家在实际项目开发时,直接将组件名以大写字母开头即可。

属性

上面说完了在 React 中两种定义组件的方式。在上面的例子中,我们定义的组件都是静态的,然而在实际的开发中,视图层组件往往会进行频繁更新,或者需要从后端 API 获取动态数据在组件中展示。这就需要组件拥有接收外部输入的能力。

属性是组件的输入

在第二篇文章 《新型前端开发方式》 中有说到 “视图是数据的映射”,那么其中说的数据指的就是属性。

如果把组件理解为一个函数,那么属性就是这个函数的参数,函数的返回值就是呈现到页面上的视图。而且通过上面部分的学习,在 React 中组件确实可以以函数的形式来定义,而且函数的参数就是一个包含当前组件接收到的所有属性的对象。

如下所示带有属性 name 的组件定义:

import React, {Component} from 'react';class HelloMessage extends Component {render() {return <div>Hello {this.props.name}.</div>;}
}

函数式:

import React from 'react';function HelloMessage(props) {return <div>Hello {props.name}.</div>;
}// 或者:const HelloMessage = props => <div>Hello {props.name}.</div>;

属性的传递也跟 HTML 一样(在本文的最后一部分会有各种类型属性的详细介绍),如下所示:

import React, {Component} from 'react';
import ReactDOM from 'react-dom';class HelloMessageList extends Component {render() {return (<div><HelloMessage name="Lucy" /><HelloMessage name="Tom" /><HelloMessage name="Jack" /></div>)}
}ReactDOM.render(<HelloMessageList />, document.querySelector('#root'));

这样页面上会展示出:

Hello Lucy.
Hello Tom.
Hello Jack.

示例代码:https://codepen.io/Sarike/pen...

属性必须为只读的

属性必须为只读的,这一点非常重要,请严格遵守。对应到上面说到的,如果把组件理解为一个函数,那么这个函数必须为一个纯函数(Pure function),在纯函数中不能修改其参数,确定的输入必须有确定的输出。

虽然有些时候,你修改了组件的属性,貌似也能正常工作。没错,因为 JavaScript 语言特性的原因,没人能阻止你这么做。但是请先相信我,严格遵守这条规则不仅能让你少踩很多坑,而且能让你的应用稳定性更强、维护性更强。如果你直接修改组件的属性,React 并不会感知到此修改,从而不会重新渲染组件,就导致了当前组件的视图展示与数据不一致,但这个被修改的属性会随着下一次组件的渲染被生效到视图上,而且这次渲染的时机是不确定的,不难想象,如果一个规模较大的项目里充满了这种不确定性是多么痛苦的一件事情。总之,如果你随意修改组件的属性,会很容易让你的应用充满许多难以排查的 BUG。

默认属性

通常情况下,我们需要为组件的属性设为默认值。就像 HTML 标签的属性也有默认值一样,例如 form 标签的 method 属性默认值是 GET,input 标签的 type 属性默认值是 text 一样。

还是上面 HelloMessage 组件,如果需求是当不传入 name 属性时,默认展示 Hello World.,也就是说 name 属性的默认值是 World。

一种很容易想到的做法:

<div>Hello {this.props.name || 'World'}.</div>

这样确实可以解决当前这个需求,但是属性可能还会是个 Object,也可能是个函数,你当然可以先判断下这个属性是否为 undefined 然后决定是否使用默认值,但是这样会让代码显得很不优雅,而且也会增加很多繁琐的判断逻辑。

因此,React 提供了相应的机制可以设置组件属性的默认值,如下所示,你需要通过组件的静态字段 defaultProps 来设置组件属性的默认值。如下所示:

import React, {Component} from 'react';class HelloMessage extends Component {render() {return <div>Hello {this.props.name}.</div>;}
}
HelloMessage.defaultProps = {name: 'World'
}

这样就可以了,<HelloMessage /> 这样不为组件设置任何属性,那么它就会在页面上展示Hello World.

示例代码:https://codepen.io/Sarike/pen...

属性的类型及校验

在开发较复杂的前端应用时,我们经常会遇到许多因为类型检查导致的问题,例如上面的 HelloMessage 组件,我期望其 name 属性只能是字符串类型的,你要是给我一个 Object,我是没法正确展示的。为了在开发过程中尽快的发现这类问题,React 为组件添加了类型检查的机制,你需要给组件设置静态字段 propTypes 来设置组件各个属性的类型检查器。

import React, {Component} from 'react';
import PropTypes from 'prop-types';class HelloMessage extends Component {render() {return <div>Hello {this.props.name}.</div>;}
}
HelloMessage.defaultProps = {name: 'World'
}
HelloMessage.propTypes = {name: PropTypes.string
}

这样在开发过程中 React 就能校验组件接收到的属性值是否符合指定的类型,如果校验不通过,将会抛出警告。React 只会在开发模式下进行属性类型检查,当代码进行生产发布后,为了减少额外的性能开销,类型检查将会被略过。

其实,为每一个组件编写完善的属性类型是一个非常好的习惯,这不仅能及时发现问题,更重要的是配合几句简单额注释,这将成为该组件一份非常好的文档,一个完善的组件应该具有良好的封装性和易复用性,在一个协作开发的项目中,其他开发者需要引用你开发的组件时,只需要看一下组件的属性列表,大致就可以了解如何来使用这个组件,省去了很多不必要的沟通。

下面是 React 提供的可用的数据类型检查器。

  • PropTypes.array
  • PropTypes.bool
  • PropTypes.func
  • PropTypes.number
  • PropTypes.object
  • PropTypes.string
  • PropTypes.symbol
  • PropTypes.element 元素,其实就是 JSX 表达式,上一篇文章有说过 JSX 是 React.createElement 的语法糖,一个 JSX 表达式实际上会生成一个 JS 对象,在 React 中称之为元素(Element)。
  • PropTypes.node 所有可以被渲染的数据类型,包括:数值, 字符串, 元素或者这些类型的数组。
  • PropTypes.instanceOf(Message) 某个类的实例
  • PropTypes.oneOf(['News', 'Photos']) 枚举,属性值必须为其中的某一个值。
  • PropTypes.oneOfType([PropTypes.string, PropTypes.number]) 类型枚举,属性必须为其中某一个类型。
  • PropTypes.arrayOf(PropTypes.number) 属性为一个数组,且数组中的元素必须符合指定类型。
  • PropTypes.objectOf(PropTypes.number) 属性为一个对象,且对象中的各个字段的值必须符合指定类型。
  • PropTypes.any 任何类型

如果你想指定某些属性为必需属性,你可以链式调动其 isRequired 来标识某个属性对于当前组件来说是必需的。如果在使用组件时未指定则会抛出警告提醒。

另外你还可以通过一个函数自定义属性验证器,如果验证不通过你需要返回一个 Error 实例,如下所示:

function(props, propName, componentName) {if (!/matchme/.test(props[propName])) {return new Error('Invalid prop `' + propName + '` supplied to' +' `' + componentName + '`. Validation failed.');}
}

设置组件的属性值

上面咱们了解到组件的属性有很多种类型,下面说一下各种类型的属性是如何传递给组件的。其实很简单,属性的值可以用一对大括号 { } 来包围,其中可以指定任意的 JavaScript 表达式。如下所示:

return (<Username="Tom"                            // 字符串age={18}                              // 数值isActivated={true}                    // 布尔值interests={['basketball', 'music']}   // 数组address={{ city: 'Beijing', road: 'BeiWuHuan' }} // 对象/>
)

展开操作符

你也可以用展开操作符 ... 将一个对象的所有字段展开,依次作为属性传递给组件,上面的代码等价于:

const userInfo = {name: 'Tom',age: 18,isActivated: true,interests: ['basketball', 'music'],address: { city: 'Beijing', road: 'BeiWuHuan' }
}
return <User {...userInfo} />

值为 true 的属性的简写

如果是属性类型为布尔值,且当前属性值为 true 可以只写属性名,如下所示:

<inputdisabled     // 禁用该输入框type="text"
/>

children 属性

用户自定义的组件内可以通过 this.props.children 来获取一个特殊的属性。该属性与其它属性的区别就是传递方式不同。

children 属性的值是指一对闭合的 JSX 标签中间的内容,如下所示:

<UserList><User name="Tom" /><User name="Lucy" />
</UserList>

那么在 UserList 内部可以通过 this.props.children 来获取下面这个 JSX 片段:

<User name="Tom" />
<User name="Lucy" />

该示例中,获取到的实际上是一个包含两个 User 元素对象的数组。

总结

本文主要介绍了在 React 中组件的定义方式,以及几个关键的注意事项。另外介绍了组件属性的作用、属性默认值、属性类型校验以及如何为组件传递属性。

希望内容对大家有用,如有任何问题和建议可以给我留言,谢谢。

玩转 React(四)- 创造一个新的 HTML 标签相关推荐

  1. 媒体报道|香港科大(广州)创校校长倪明选:“接下来的30年,我们希望再创造一个新的历史”...

    30年前,在香港科技大学创校的一批老师创造了奇迹,把香港科大打造成了世界一流的学校.接下来的30年,我们希望再创造一个新的历史. 5月17日下午,香港科技大学(广州)(下简称香港科大(广州))创校校长 ...

  2. vue项目点击左侧子菜单,打开一个新的浏览器标签页

    在项目开发中,产品给了这样一个需求:点击左侧子菜单,在浏览器中打开一个新的标签页,展示数据大屏.在此写个随笔记录下实现过程. 思路:使用编程式导航 实现页面跳转,我们常用的是 $router.push ...

  3. 你能用大数据创造一个新的商业模型吗?

    美国著名的技术创业训练营 Y Combinator, 一个众所周知的连接有潜力的新贵科技公司和风险投资的纽带.它另一个鲜为人知但是同等重要的功能,就是训练创业公司的所有者向所有可能最好的商业模型发展. ...

  4. HTML-CSS(三十四)HTML5新语义化标签

    header:页眉 footer:页脚 main:主体 hgroup:标题组合(如果出现两标题相邻,则可以使用这个) nav:导航 artical :独立的内容 section:区域 与div作用差不 ...

  5. 玩转 React(二)- 新型前端开发方式

    这是<玩转 React>系列的第二篇.在该篇中,我们来了解下,React 的出现到底给我们的开发方式带来了什么样的变化. 我的感触可以用一个字来形容,爽!主要爽在以下两个方面. 视图是数据 ...

  6. RN:React Native原理以及新架构JSI、Fabric等概念

    说明 RN需要一个JS的运行环境, 在IOS上直接使用内置的javascriptcore, 在Android 则使用webkit.org官方开源的jsc.so. 此外还集成了其他开源组件,如fresc ...

  7. 10.创造一个ROSmsg和srv

    创造一个ROSmsg和srv(rawmeat:http://wiki.ros.org/ROS/Tutorials/CreatingMsgAndSrv) 注意:本教程翻译于2018.9.19,英文原版教 ...

  8. 第四夜 八尾猫 异闻录——每晚一个离奇故事 敬请期待,一天,一个离奇故事!如果您喜欢,请在评论里发表您的高见,支持,每天一个新故事!

    第四夜 八尾猫  异闻录--每晚一个离奇故事 敬请期待,一天,一个离奇故事!如果您喜欢,请在评论里发表您的高见,支持,每天一个新故事! 第四夜 八尾猫 "在古埃及的神话中,猫扮演着很重要的角 ...

  9. 利用脚手架工具搭建一个新的react项目

    利用脚手架工具搭建一个新的react项目 一,工程架构 1.使用的是create-react-app脚手架工具搭建的工程架构 npm install create-react-app -g全局安装 c ...

最新文章

  1. CRM 客户端程序开发:获取表单界面上各种字段的值及其他属性
  2. CSS:给 input 中 type=text 设置CSS样式
  3. 深度探索C++ 对象模型(3)-默认构造函数Default Constructor续
  4. 服务器性能查看常用命令
  5. 发球机器轰灭了中国姑娘的奇迹
  6. Docker 制作自定义化的Tomcat镜像
  7. win7下使用Taste实现协同过滤算法
  8. DaVinci Resolve Studio 17.4.1 Mac(达芬奇调色软件)
  9. 致敬Github那些卓越贡献的大佬和他们的公众号
  10. oracle10g连接自动断开,报ORA-03135错误
  11. ORA-00932: 数据类型不一致:应为-,但却获得NCLOB
  12. Pandas 之DataFrame二维表基础操作及演示
  13. 【实验室设备管理系统SSM】
  14. 在list中筛选出符合条件的数据(返回list)
  15. 【微信公众号开发解决URL接口配置问题 】
  16. 测试工程师的明天在哪里
  17. 普通话测试app怎么样可以不交钱_如何说普通话才算标准?
  18. Use of Deep Learning in Modern Recommendation System: A Summary of Recent Works(笔记)
  19. LeaRun低代码OA系统构建平台
  20. CSDN文章点赞、收藏、评论后到底发生了什么?简要分析HTTP交互机制

热门文章

  1. mysql数据库操作手册
  2. ReactiveCocoa入门教程——第一部分
  3. CSS3常用属性及用法
  4. Finereport集群配置
  5. Ios17个常用代码整理
  6. MOSS站点的FORM认证修改小结
  7. 解决torch.cuda.is_available()为False的问题
  8. mysql 查找课程最高分_mysql 查询 学生id最高分的科目和日期
  9. mysql 取左_MySQL select语句从字符串左侧获取5个字符
  10. wait和notify使用例子