开发一个React应用,更多的是在编写组件,而React组件最小的单位就是React元素,编写组件的最大的好处,就是实现代码的复用javascript
将一个大的应用按照功能结构等划分红若干个部分(组件),对每一个部分(组件)进行分开管理,与组件相关的东西放在一块儿,达到高内聚的目的,而不一样组件又各自独立管理达到低耦合的效果。html
构建组件,本质上就是在编写javascript函数,而组件中最重要的是数据,在React中数据分两种:props和state,当定义一个组件时,它接收任意的形参(即props),并用于返回描述页面展现内容的React元素java
不管props仍是state,当他们任何一个发生改变时,都会引起render函数的从新渲染react
一个UI组件所渲染的结果,就是经过props和state这两个属性在render方法里面映射生成对应的HTML结构npm
那么在写一个React组件的时候,究竟何时使用state,何时使用props呢?如何的划分组件的状态数据?数组
那么本节就是你想要知道的bash
React中组件的数据-props(想阅读体验更好,可戳该连接,内附有视频)babel
React中的props
当经过函数声明或者class自定义一个组件时,它会将JSX所接受的属性(attributes)转换为一对象传递给该定义时的组件app
这个接收的对象就是props(property的简写),props就是组件定义属性的集合,它是组件对外的接口,由外部经过JSX属性传入设置(也就是从外部传递给内部组件的数据)dom
一个React组件经过定义本身可以接收的prop,就定义了本身对外提供的公共接口
每一个定义的React组件应该都是独立存在的模块,组件以外的一切都是外部世界(组件),外部世界(组件)就是经过prop来和组件进行对话数据传递的
在React中,你能够将prop相似于HTML标签元素的属性,不过原生HTML标签的属性值都是字符串,即便是内嵌js表达式,也依然是字符串,而在React中,prop的属性值类型能够任何数据类型(基本数据类型(number,String,null等函数)或者对象)
固然若是是非字符串数据类型,在JSX中,必需要用花括号{}把prop值给包裹起来
这也是为何style有两层花括号的缘由:最外层表明的是JSX语法,意味着它是一个变量对象,而内层的花括号{}表明的是一个对象
在函数声明自定义的组件中,能够经过props获取组件的属性
以下所示:自定义一个Button组件,给组件添加各个属性值,渲染的结果以下所示
import React, { Fragment, Component } from 'react';
import ReactDOM from 'react-dom';
// 函数式组件,定义一个Button组件,首字母大写
function Button(props) {
console.log(props); // 将会把调用处组件的style属性给打印出来
const btnStyles = {
width: props.style.width,
height: props.style.height,
background: props.style.background,
color: props.style.color,
border: props.style.border,
outline: props.style.outline,
cursor: props.style.cursor
};
return (
<div>
<button style = { btnStyles }>按钮</button>
</div>
);
}
const btnStyle = {
width: "100px",
height: "40px",
background: "orange",
color: "#fff",
border: "none",
outline: "none",
cursor: "pointer"
}
const container = document.getElementById('root');
ReactDOM.render(<Button style = { btnStyle } />, container);
复制代码
类class声明的组件: 经过Es6中的class声明,继承React.Component进行实现
import React, { Fragment, Component } from 'react';
import ReactDOM from 'react-dom';
// 类组件,经过class关键字声明使用
class Button extends Component {
constructor(props){
super(props);
}
render() {
console.log(this.props);
// 这里利用Es6中的解构赋值
const { width, height, background, color, border, outline,cursor} = this.props.style;
const btnStyles = {
width, // 等于width:width
height,
background,
color,
border,
outline,
cursor
}
return (
<div>
<button style = { btnStyles }>按钮</button>
</div>
);
}
}
// 该Button组件按钮自身拥有的属性
const btnStyle = {
width: "100px",
height: "40px",
background: "orange",
color: "#fff",
border: "none",
outline: "none",
cursor: "pointer"
}
const container = document.getElementById('root');
ReactDOM.render(<Button style = { btnStyle } />, container);
复制代码
上述代码中分别使用了函数式组件与类声明的组件,在调用组件时,对组件设置了props值,而在组件内部经过this.props获取属性值
从而得出,父组件(外部组件)向子(内)组件传值是经过设置JSX属性的方式实现的,而在子组件内部获取父(外部)组件数据是经过this.props来获取的,也能够这么认为,props就是对外提供的数据接口
对于用类class声明的组件,读取prop的值,是经过this.props来获取的
首先用construcor定义了一个构造函数,而且给它接收了一个props形参,而后在constructor构造器函数内调用super(props)
这个是固定的写法,组件继承父类的一些方法,若是一个组件须要定义本身的构造函数,那么就必定要调用super(props),也就是继承了React.Component构造函数
至于为何要调用super(props)方法,由于Es6采用的是先建立父类实例的this,而后在用子类的构造函数修改this
若是没有constructor构造器函数,调用super(),以及参数props,它是会报错的
在组件实例被构造以后,该组件的全部成员函数都没法经过this.props访问到父组件传递过来的props值,错误以下所示
ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
复制代码
关于constructor()构造器函数
这个constructor(props)构造器函数是自动就生成的,若是没有声明,React会默认添加一个空的construcor,而且会自动执行,有且只执行一次,能够将它视为钩子函数(生命周期函数)
这个constructor函数接收props形参数,接收外部组件传值的集合,只要组件内部要使用prop值,那么这个props参数是要必传的,不然的话在当前组件内就没法使用this.props接收外部组件传来的值
可是不管有没有constructor函数,render函数,子组件内均可以使用this.props获取组件外部的数据,它是默认自带的
constructor(props){
super(props);
}
复制代码
至于写不写构造器函数,若是该自定义的组件不须要初始化state,不用进行方法的绑定(this坏境的设置),只是单纯的用于接收外部组件传来的props数据用做展现,并无UI交互渲染动做
那么就不须要为该React组件实现构造函数
若是是这样,则更应该把它转换为函数式(无状态UI)组件,由于它的效能是最高的
不然的话,那么就要编写constructor构造器函数,何况Es6编写类的方式提供了更多实用的功能,特定的条件下,该用仍是要用的
通常而言,在React中,构造函数仅用于下面两种状况:
经过给this.state赋值对象来初始化当前组件内部的state(状态)
在JSX中监听绑定事件处理函数(this坏境的绑定)
在constructor()函数中不要调用setState()方法,若是组件须要使用内部状态state,直接在构造函数中为this.state赋初始state值
constructor(props){
super(props);
// 不要在这里调用this.setState(),更改state状态数据
this.state = {
// 属性:属性值
count: 0
}
//this.方法 = this.方法.bind(this);
this.handleClick = this.handleClick.bind(this)
}
复制代码
只能在构造函数中直接为this.state赋值,若是在其余地方法须要改变该state的值,应该使用this.setState()方法替代
注意:
若是把函数组件替换成类组件的写法,在子组件内部接收外部的props值时,须要将props更改为this.props的写法,反过来也是,类声明的组件替换成函数式(无状态)组件时,须要将this.props替换成props
而在用class类定义的组件时,一旦对组件初始化设置完成,该组件的属性就能够经过this.props获取获得,而这个this.props是不可更改的
不要轻易更改设置this.props里面的值,换句话说,组件的props属性只具有可读性,不能修改自身的props,这不区分是用函数声明的组件仍是用class声明的组件,没法直接的更改props值
以下所示:点击按钮,想要改变外部传进去的props值,在代码中直接更改props值,是会报错的以下图错误所示:
import React, { Fragment, Component } from 'react';
import ReactDOM from 'react-dom';
// 类组件
class Button extends Component {
constructor(props){
super(props);
}
render() {
const { width, height, background, color, border, outline,cursor} = this.props.style;
const btnStyles = {
width,
height,
background,
color,
border,
outline,
cursor
}
return (
<div>
<button onClick = { this.handleBtnClick.bind(this) } style = { btnStyles }>{ this.props.btnContent }</button>
</div>
);
}
handleBtnClick(){
// 直接更改props的值,是会报错的,在React中不容许这么作
this.props.btnContent = "按钮B";
}
}
const btnStyle = {
width: "100px",
height: "40px",
background: "orange",
color: "#fff",
border: "none",
outline: "none",
cursor: "pointer"
}
const container = document.getElementById('root');
ReactDOM.render(<Button btnContent ="按钮A" style = { btnStyle } />, container);
复制代码
之因此这么规定,由于组件的复用性,一个组件可能在各个页面上进行复用,若是容许被修改的话,这个组件的显示形态会变得不可预测,当组件出现某些bug的时候,会给开发者带来困扰,调试将会是噩梦,没法定位,违背组件的设计原则了
可是这并不表明着props的值并不能被修改,有时候,因为业务的需求,咱们是须要对props值进行修改的
若是想要修改,那么能够经过借助React内置的一个方法setState方法从新渲染的方式,把props传入组件当中,这样的话,由props属性决定这个组件的显示形态也会获得相应的改变
更改以下所示:
import React, { Fragment, Component } from 'react';
import ReactDOM from 'react-dom';
// 类组件
class Button extends Component {
constructor(props){
super(props);
// state是组件内部的状态
this.state = {
btnContent: this.props.btnContent
}
}
render() {
const { width, height, background, color, border, outline,cursor} = this.props.style;
const btnStyles = {
width,
height,
background,
color,
border,
outline,
cursor
}
return (
<div>
<button onClick = { this.handleBtnClick.bind(this) } style = { btnStyles }>{ this.state.btnContent }</button>
</div>
);
}
handleBtnClick(){
// this.props.btnContent = "按钮B";
this.setState({
btnContent: "按钮B"
});
}
}
const btnStyle = {
width: "100px",
height: "40px",
background: "orange",
color: "#fff",
border: "none",
outline: "none",
cursor: "pointer"
}
const container = document.getElementById('root');
ReactDOM.render(<Button btnContent ="按钮A" style = { btnStyle } />, container);
复制代码
关于React中事件监听this的绑定
this的指向一般与它的执行上下文有关系,通常有如下几种方式
函数的调用方式影响this的取值,若是做为函数调用,在非严格模式下,this指向全局window对象,在严格模式(use "strict")下,this指向undefined
若是做为方法的调用,this指向调用的对象,谁调用它,this就指向谁
做为构造器函数调用,this指向该建立的实例化对象(类实例方法里面的this都指向这个实例自己)
经过call,apply调用,this指向call和apply的第一个参数
在React中,给JSX元素,监听绑定一个事件时,你须要手动的绑定this,若是你不进行手动bind的绑定,this会是undefined,在Es6中,用class类建立的React组件并不会自动的给组件绑定this到当前的实例对象上
将该组件实例的方法进行this坏境绑定是React经常使用手段
代码以下所示:
import React, { Fragment, Component } from 'react';
import ReactDOM from 'react-dom';
// 类组件
class Button extends Component {
constructor(props){
super(props);
// this坏境的绑定,这是React里面的一个优化,constructor函数只执行一次
this.handleBtnClick = this.handleBtnClick.bind(this);
this.state = {
btnContent: this.props.btnContent
}
}
render() {
const { width, height, background, color, border, outline,cursor} = this.props.style;
const btnStyles = {
width,
height,
background,
color,
border,
outline,
cursor
}
return (
<div>
<button onClick = { this.handleBtnClick } style = { btnStyles }>{ this.state.btnContent }</button>
</div>
);
}
handleBtnClick(){
// this.props.btnContent = "按钮B";
this.setState({
btnContent: "按钮B"
});
}
}
const btnStyle = {
width: "100px",
height: "40px",
background: "orange",
color: "#fff",
border: "none",
outline: "none",
cursor: "pointer"
}
const container = document.getElementById('root');
ReactDOM.render(<Button btnContent ="按钮A" style = { btnStyle } />, container);
复制代码
固然若是不用这种手动绑定this的方式,用箭头函数也是能够的,箭头函数没有this的绑定,以下代码所示
import React, { Fragment, Component } from 'react';
import ReactDOM from 'react-dom';
// 类组件
class Button extends Component {
constructor(props){
super(props);
// this坏境的绑定,这是React里面的一个优化,constructor函数只执行一次
// this.handleBtnClick = this.handleBtnClick.bind(this);
this.state = {
btnContent: this.props.btnContent
}
}
render() {
const { width, height, background, color, border, outline,cursor} = this.props.style;
const btnStyles = {
width,
height,
background,
color,
border,
outline,
cursor
}
return (
<div>
<button onClick = { () => { this.handleBtnClick() } } style = { btnStyles }>{ this.state.btnContent }</button>
<!--或者如下写法-->
<!--<button onClick = { this.handleBtnClick } style = { btnStyles }>{ this.state.btnContent }</button>-->
</div>
);
}
handleBtnClick(){
// this.props.btnContent = "按钮B";
this.setState({
btnContent: "按钮B"
});
}
// handleBtnClick = () => {
// this.setState({
// btnContent: "按钮B"
// });
// }
}
const btnStyle = {
width: "100px",
height: "40px",
background: "orange",
color: "#fff",
border: "none",
outline: "none",
cursor: "pointer"
}
const container = document.getElementById('root');
ReactDOM.render(<Button btnContent ="按钮A" style = { btnStyle } />, container);
复制代码
对比两种实现方式,都是能够的,可是官方推荐使用bind绑定,使用bind不只能够帮咱们把事件监听方法中的this绑定到当前的组件实例上
bind后面还还能够设置第二个参数,把与组件相关的东西传给组件的,并在construcor构造器函数中进行初始化绑定,虽然bind的使用会建立一个新的函数,可是它在constructor中只会调用一次
而利用箭头函数,箭头函数中没有this的绑定,从性能上讲,它是会重复调用,进行额外的渲染,不如在构造器函数中进行this坏境的初始化手动绑定
在上面说到了prop值既然能够是任意数据类型,正好利用这一特性,子组件接收父组件用this.props能够获取属性,那么这个属性值能够是个方法,子组件也能够调用父组件的方法,来达到子组件向父组件传递数据
以下代码所示,最终的效果以下所示
import React, { Fragment, Component } from 'react';
import ReactDOM from 'react-dom';
// 定义一个父组件
class ParentComponent extends Component {
constructor(props){
super(props);
console.log("父组件props",props);
}
childContent(parm) {
alert(parm);
}
render(){
return (
<Fragment>
<div>{ this.props.parentContent }</div>
<ChildComponent getChildContent = { this.childContent } childcon = "我是子组件的内容" ></ChildComponent>
</Fragment>
);
}
}
// 定义子组件
class ChildComponent extends Component {
constructor(props){
super(props);
console.log("子组件props",props);
}
handleChild = ()=> {
const {getChildContent, childcon} = this.props;
getChildContent(childcon);
}
render(){
return (
<Fragment>
<div onClick = { this.handleChild }>{ this.props.childcon}</div>
</Fragment>
);
}
}
const container = document.getElementById('root');
ReactDOM.render(<ParentComponent parentContent = "我是父组件的内容" />, container);
复制代码
从上面的代码中,能够看得出,父组件中JSX的prop值能够是一个方法,在子组件想要把数据传递给父组件时,须要在子组件中调用父组件的方法,从而达到了子组件向父组件传递数据的形式
这种间接操做的方式在React中很是重要.固然你看到上面把子组件与父组件放在一个文件当中,或许看得不是很舒服,你能够把子组件单独的抽离出去,经过Es6中的export,import导出导入的方式是能够的(后面每每用的是这种方式)
在index.js同级目录下建立一个ChildComponent.js的文件
import React, { Component, Fragment} from 'react';
class ChildComponent extends Component {
constructor(props){
super(props);
console.log("子组件props",props);
}
handleChild = ()=> {
const {getChildContent, childcon} = this.props;
getChildContent(childcon);
}
render(){
return (
<Fragment>
<div onClick = { this.handleChild }>{ this.props.childcon}</div>
</Fragment>
);
}
}
export default ChildComponent;
复制代码
在index.js中,经过import将ChildComponent组件进行引入,以下代码所示
import React, { Fragment, Component } from 'react';
import ReactDOM from 'react-dom';
import ChildComponent from './ChildComponent'; // 引入ChildComponent组件
// 定义一个父组件
class ParentComponent extends Component {
constructor(props){
super(props);
console.log("父组件props",props);
}
childContent(parm) {
alert(parm);
}
render(){
return (
<Fragment>
<div>{ this.props.parentContent }</div>
<ChildComponent getChildContent = { this.childContent } childcon = "我是子组件的内容" ></ChildComponent>
</Fragment>
);
}
}
const container = document.getElementById('root');
ReactDOM.render(<ParentComponent parentContent = "我是父组件的内容" />, container);
复制代码
使用PropTypes进行类型检查
既然prop是组件对外的接口,那么这个接口就必然要符合必定的数据规范,换句话说:也就是输入与输出的类型要保持一致,不然的话就会出问题
经过类型检查捕获一些错误,规避一些程序上的bug,React内置了一些类型检查的功能,要在组件的props上进行类型的检查,只须要作一些特定的propTypes属性配置便可
定义一个组件,为了该程序的严谨性,应该规范组件数据的以下方面
这个组件支持哪些prop
每一个prop应该是什么样的格式
在React中,借助了第三方库prop-types来解决这一问题,经过PropTypes来支持这一功能
命令行终端下,安装prop-types这个库
cnpm install --save prop-types
复制代码
在你所要验证的组件内,引入prop-types库
import PropTypes from 'prop-types'
class PropTest extends Component {
render(){
return (
<Fragment>
<div>{ this.props.propContent }</div>
</Fragment>
);
}
}
// 类组件.propTypes对象下进行配置
PropTest.propTypes = {
propContent: PropTypes.number
}
const container = document.getElementById('root');
ReactDOM.render(<PropTest propContent = "我是prop属性内容" />, container);
复制代码
控制台错误显示以下:
具体的解决办法就是:要么更改传入属性值的prop类型,要么把校验类型进行更改与之对应的
PropType提供了一系列的验证方法,用于确保组件接收到的数据类型是有效准确的,一旦传入的prop值类型不正确时,控制台将会显示的警告,虽然程序不会报错,可是会出现警告.
有时候,对于外部传入组件内部的prop值,不管有没有传入,为了程序的健壮性,,须要判断prop值是否存在,咱们每每须要设置一个初始默认值,若是不存在,就给一个默认初始值,固然你利用传入的prop进行“||”或字符进行处理也是能够的
在React中,能够配置defaultProps进行默认prop值的设置,代码以下所示
具体写法:
组件.defaultProps = {
prop属性名称: 默认值
}
复制代码
import React, { Fragment, Component } from "react";
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
class PropTest extends Component {
render(){
return (
<Fragment>
<div>{ this.props.propContent }</div>
</Fragment>
);
}
}
PropTest.defaultProps = {
propContent: "我是propTest组件的内容"
}
const container = document.getElementById('root');
ReactDOM.render(<PropTest />, container);
复制代码
效果以下所示
它会显示默认设置的初始值,若是外部组件传了prop值,它会优先使用传入的prop值,覆盖默认设置的初始值
具体PropTypes下更多的方法,可参考官网手册PropTypes库的使用,也能够查看npm中的prop-types这个库的使用
出于性能的考虑,在开发的时候能够发现代码中的问题,可是放在生产坏境中就不适合了
由于它不只增长了代码行数,占用空间,并且还消耗CPU资源
折中的办法就是:在开发的时候代码定义propTypes,避免开发犯错,但在发布产品代码时,用一种自动的方式将propTypes去掉,这样在线上坏境代码时最优的
借助babel-plugin-transform-react-remove-prop-types这个第三方模块进行配置处理一下的,具体详细配置:可见npm官网对这个库的介绍的:www.npmjs.com/package/bab…
本文主要讲述了React组件中的数据属性-props,它相似HTML标签的属性,但属性值能够是任意数据类型,数字number,字符串String,甚至函数,对象
而且要注意函数式声明(无状态)组件与Es6中类声明组件时,在子组件内部接收props的写法上的差别,当使用类class声明一个组件时,定义本身的构造器函数,必定要使用constructor构造器函数,而且设置接收props参数,以及调用super(props),若是不进行该设置,该组件下定义的成员私有方法(函数)将没法经过this.props访问到父组件传递过来的prop值
固然,在React中,规定了不能直接更改外部世界传过来的prop值,这个prop属性只具有读的能力,具体缘由可见上文
若是非要更改,那么能够借助React提供的setState这一方法进行改变
值得一提的就是关于this坏境绑定的问题,在组件内的constructor构造器函数内使用bind的方式进行this手动绑定设置,具体详细内容可见上文
以及当知道如何定义组件中的prop数据,还有必要对外部组件传给内部组件的prop数据类型的校验,经过prop-types库来解决,PropTypes这个实例属性来对prop进行规格的设置,这样能够在运行代码时,能够根据propTypes判断外部组件是否整整的使用组件的属性,输入输出的类型是否一一对应,保持一致
限于篇幅所示:React中数据的另外一个state将在下一篇幅中进行学习了