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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/280175.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mac word 设置语言_如何更改Mac的语言和区域设置

mac word 设置语言If you want to use your Mac in a different language, or you’re live in a different region, then you can change it in OS X. When you do, it’ll display everything in your preferred language, currency, date format, and more. 如果您想以其他语…

【Luogu3931】SAC E#1 - 一道难题 Tree

problem solution codes //树形DP //f[u]:割掉u和u子树中所有的叶子节点所需要的最小代价 #include<iostream> #include<vector>using namespace std; typedef long long LL; const int N (int)1e510, inf 1e9;int n, S;struct node{LL to, v;node(LL to, LL v):…

IT史上十大收购案

本文讲的是IT史上十大收购案【IT168 资讯】据英国资讯网站V3报道&#xff0c;本周&#xff0c;业界中的大事件无疑是硬件巨头Intel公司斥资76.8亿美元全盘收购著名安全软件公司McAfee。本次收购被看做是软硬件领域的一次亲密接触&#xff0c;下面为大家盘点近年来IT领域中影响较…

飞利浦dicom_如何按计划打开或关闭飞利浦色相灯

飞利浦dicomThe Philips Hue app can do a handful of cool stuff with your Hue lights, including the ability to schedule your lights to turn on and off at specific times throughout the day. Here’s how to set it up so that you never have to flip a switch ever…

Mono生命周期小实验

今天在写代码的时候&#xff0c;遇到一个初始化顺序问题&#xff0c;于是做了一个实验&#xff0c;下面记录结果&#xff1a; 情景&#xff1a; 1.在 脚本A中实例化 一个预制体&#xff0c;该预制体挂有脚本B 2.在 脚本A中&#xff0c;获取实例化物体 身上的 脚本B&#xff0c;…

[读书笔记]大型分布式网站架构设计与实践.分布式缓存

前言&#xff1a;本书是对分布式系统架构涉及到的相关技术的一本科普书籍。由于很难作为开发参考&#xff0c;只能但求了解。所以通篇浅读&#xff0c;对分布式系统进行大致的了解。因为写的非常好&#xff0c;感觉非常有意思&#xff0c;自己也做不出总结。所谓的读书笔记也就…

宁波保哥后院_如何抛出终极后院电影之夜

宁波保哥后院Most people have the basics of throwing a movie night down: you get a movie, you get snacks, you get comfortable, and boom, you’re done. When it comes to throwing a movie party in the backyard, however, things get a little trickier. Read on as…

大厂前端高频面试问题与答案精选

近日&#xff0c;GitHub上一位名为木易杨&#xff08;yygmind&#xff09;的开发者&#xff0c;在 GitHub 中建了一个名为Advanced-Frontend/Daily-Interview-Question项目&#xff0c;该项目每天会更新一道前端大厂面试题&#xff0c;并邀请开发者在issue区中作答&#xff0c;…

Maven打包小技巧--持续更新

NO.1 跳过测试&#xff0c;打包指定环境 mvn clean install -Dmaven.test.skiptrue -P dev 其中&#xff1a;clean将target目录中的文件移除&#xff1b; install根据配置文件&#xff0c;将本地工程打包成jar/war包&#xff1b; -Dmaven.test.skiptrue&#xff0c;打包时路过测…

OpenLayers学习笔记5——使用jQuery UI实现查询并标注(UI篇)

近期事情非常多&#xff0c;老板给的压力也非常大。经常出差&#xff0c;另外项目和个人研究还都要跟上&#xff0c;本月要交论文&#xff0c;还要写专利&#xff0c;仅仅能抽时间来学习其它的东西了。 关于OpenLayers的在博客中不会写太多详细的实现&#xff08;网上有非常多o…

C++ 排序函数 sort(),qsort()的用法

想起来自己天天排序排序&#xff0c;冒泡啊&#xff0c;二分查找啊&#xff0c;结果在STL中就自带了排序函数sort,qsort&#xff0c;总算把自己解脱了~ 所以自己总结了一下&#xff0c;首先看sort函数见下表&#xff1a; 函数名功能描述sort对给定区间所有元素进行排序stable_s…

.net core 实现默认图片

web 上 如果图片不存在 一般是打xx 这时候 一般都是会设置默认的图片 代替 现在用中间件的方式实现统一设置 一次设置 全部作用 .net core 实现默认图片 Startup 文件 app.UseDefaultImage(defaultImagePath: Configuration.GetSection("defaultImagePath").Va…

spring cloud config将配置存储在数据库中

转载请标明出处&#xff1a; https://blog.csdn.net/forezp/...本文出自方志朋的博客 Spring Cloud Config Server最常见是将配置文件放在本地或者远程Git仓库&#xff0c;放在本地是将将所有的配置文件统一写在Config Server工程目录下&#xff0c;如果需要修改配置&#xff0…

VMware虚拟机VMware Authorization Service不能启动问题

出现VMware Authorization Service不能启动问题&#xff0c;注意要在安装VMware Player时使用管理员权限转载于:https://www.cnblogs.com/mingzhang/p/9152873.html

PHP替换回车换行的三种方法

一个小小的换行&#xff0c;其实在不同的平台有着不同的实现&#xff0c;为什么要这样&#xff0c;世界是多样的&#xff01;本来在Unix世界换行用/n来代替换行&#xff0c;Windows为了体现不同&#xff0c;就用/r/n&#xff0c;更有意思的是&#xff0c;Mac中又用了/r。所以&a…

全球的weex资源都在这里

WeeX FAQ QQ: Weex大前端 516682889Weexbox&#xff1a; 943913583WeeX相关资源 weex官方资源 weex官网 Weex Market 已挂 : 一个提供 Weex 第三方组件的网站&#xff0c;您可以在这里找到你需要的 Weex 组件。 Playground : Playground在线&#xff0c;直接在线编写代码并预览…

初步解决博客园代码高亮的一个方案

今天我要推荐的是一个免费而且支持markdown语法的软件——Typora 它有很多优点&#xff0c;支持多种类型代码的高亮风格&#xff0c;方便的排版处理&#xff0c;支持Latex等&#xff0c;最重要的一点是真正做到了所见即所得ヽ(&#xff9f;∀&#xff9f;)&#xff92;(&#x…

git工作原理

工作区&#xff1a;就是你在电脑里能看到的目录。暂存区&#xff1a;英文叫stage, 或index。一般存放在 ".git目录下" 下的index文件&#xff08;.git/index&#xff09;中&#xff0c;所以我们把暂存区有时也叫作索引&#xff08;index&#xff09;。版本库&#xf…

【前端基础进阶】JS-Object 功能详解

Object.assign(target,source1,source2,...)该方法主要用于对象的合并&#xff0c;将源对象source的所有可枚举属性合并到目标对象target上,此方法只拷贝源对象的自身属性&#xff0c;不拷贝继承的属性。Object.assign方法实行的是浅拷贝&#xff0c;而不是深拷贝。也就是说&am…

解决“无法从套接字读取更多数据”

重启下Oralce服务即可。转载于:https://www.cnblogs.com/fkeyta/p/9153297.html