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

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

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

内容摘要

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

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

定义组件的几种姿式

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

1. 类继承

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

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

经过继承自 React 提供的组件基类,咱们能够这样来建立一个组件:segmentfault

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>
        )
    }
}

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

演示代码: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 中组件的定义方式,以及几个关键的注意事项。另外介绍了组件属性的做用、属性默认值、属性类型校验以及如何为组件传递属性。

但愿内容对你们有用,若有任何问题和建议能够给我留言,谢谢。

相关文章
相关标签/搜索