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

在第二篇文章 《新型前端开发方式》 中有说到 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 (
    <User
        name="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 可以只写属性名,如下所示:

<input
    disabled     // 禁用该输入框
    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 中组件的定义方式,以及几个关键的注意事项。另外介绍了组件属性的作用、属性默认值、属性类型校验以及如何为组件传递属性。

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

原文地址:https://www.cnblogs.com/10manongit/p/12207254.html

时间: 2024-10-13 15:55:41

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

玩转 React(五)- 组件的内部状态和生命周期

文章标题总算是可以正常一点了-- 通过之前的文章我们已经知道:在 React 体系中所谓的 "在 JavaScript 中编写 HTML 代码" 指的是 React 扩展了 JavaScript 的语法,也就是 JSX.JSX 语法中可以以类似 HTML 语法的方式使用 React 组件,从而编写 React 组件就有一种创造一个新的 HTML 标签的体验. 上一篇文章<玩转 React(四)- 创造一个新的 HTML 标签>介绍了如何来创建一个 React 组件,以及组件

玩转 React(三)- JavaScript代码里写HTML一样可以很优雅

这是<玩转 React>系列的第三篇,看到本篇的标题,了解过 React 的同学可能已经大致猜到我要讲什么了,本篇中要讲的内容对于刚接触 React 的同学来说,可能有些难以接受,但希望你能坚持学下去,这是 Facebook 的前端大神们为前端开发做出的革命性创新. React 第一印象 废话不多说,先看一段代码: class HelloMessage extends React.Component { render() { return <div>Hello {this.prop

开发一个新的android界面、界面跳转 看图学Android---Android 开发实例教程三、四

Android实例图解教程目录 http://blog.csdn.net/wyx100/article/details/45061407 一.课程功能 本课程讲述建立一个新界面和界面切换(从界面一切换到界面二). 二.课程界面 界面一(启动界面) 界面二(主界面) 三.工作流程 完成页面切换需要2个过程: 1.建立一个工程,见第二节. http://blog.csdn.net/wyx100/article/details/45248209 可以在该项目基础继续开发. 2.建立开机界面 先引入资源

【玩转React】关于React你需要知道的事儿

前言 随着前端技术的迅猛发展,各种前端框架也随势崛起,但归根结底,支撑每一款web框架流行的强大因素都是它能更好地服务于业务. React 自然也不例外,它的开发者当初正在开发Facebook的一个广告系统,由于不满足于当下任何的 MVC 框架,所以就自己写了一套 UI 框架,于是乎大名鼎鼎的 React 就由此诞生了. React 的出现无疑为 web 开发带来了颠覆性的改变,多少开发者夜以继日只为体验一把 React 带来的快感.本文就将带领大家一起领略一番 React 的理念.特色与情怀.

Unity3D游戏开发从零单排(四) - 制作一个iOS游戏

提要 此篇是一个国外教程的翻译,虽然有点老,但是适合新手入门.自己去写代码,debug,布置场景,可以收获到很多.游戏邦上已经有前面两部分的译文,这里翻译的是游戏的最后一个部分. 欢迎回来 在第一篇中,我们学会了怎么在Unity中搭建游戏的场景,并且设置模型的物理属性. 在第二篇中,我们学会了怎么在unity中使用脚本,并且创建了大部分的游戏逻辑,包括投球和得分! 在这最后一节中,我们将会为用户创建一个菜单系统,并且和GameController进行交互,我们开始吧. 在设备上测试 到目前为止,

使用React并做一个简单的to-do-list

1. 前言 说到React,我从一年之前就开始试着了解并且看了相关的入门教程,而且还买过一本<React:引领未来的用户界面开发框架 >拜读.React的轻量组件化的思想及其visual-dom的这种技术创新,也算是早就有了初步了解.一来没有学的太深入,二来后来在工作中和业余项目中都没有用到,因此慢慢的就更加生疏了. 近期,因为我想把自己的开源项目wangEditor能放在React.angular和vuejs中使用.先从react开始,顺手自己也重试一下React的基础知识,顺便再做一个小d

从零开始玩转JMX(四)——Apache Commons Modeler &amp; Dynamic MBean

Apache Commons Modeler 前面的Model MBean的创建方式看上去特别复杂,一个简单功能的类ModelMBeanUtils 写了很多代码,那有木有简单点的方式呢,答案是肯定的,这里就引出了Apache Commons Modeler(使用这个需要在classpath中导入commons-modeler-2.0.1.jar以及modeler的依赖项目commons-logging-1.1.3.jar,下载地址:http://commons.apache.org/proper

跨过2019 - 如何立一个新的Flag?且看行业解读

跨过2019 - 如何立一个新的Flag?且看行业解读 本文关键字:2019.新年Flag.行业解读.技术重心的转移.学习路线 一.IT行业发展历程 1. 应用开发分支 首先引用乔布斯访谈中的几句话(访谈时间为上个世纪末): 软件正在向各行各业***,成为重要的商业竞争武器.软件正在释放不可思议的力量,新的软件产品和软件服务将改变我们的社会.软件行业正在发生两件激动人心的事情,一个是面向对象编程,另一个就是Web.Web将实现我们盼望已久的梦想,计算机不再仅仅充当计算工具,开始承担通信功能.We

[ app运营 ] 一个新的App该如何推

由于经常写关于App推广的事情,经常有朋友打电话过来咨询我关于新的App的推广事宜,有的是旅游的app,有的是女性的app,有的是社交类的,问的问题也是各种各样,有的说没有预算,怎么推广,有的说有点预算但是不多怎么推广,经常电话给朋友们解答,索性今天我就把我的思路写出来,以飨初进行业做推广的朋友,如果是行业高人,可以略过. 先说下初步的推广思路,一个新的App刚出来,无论有没有预算都可以按照这样来.第一步:为你的App建立一条百科. 刚出来的app在网络上肯定是一篇空白,网络上还没有任何关于新的