react 中通过 props 和 state 实现组件间的通信,对数据进行传递、操作。
1. 属性 props
在组件中可以通过 props 传递数据。
正常情况下,props是外部传入的,组件内部也可以通过一些方式来设置属性的默认值,属性不能被组件自己更改,但是你可以通过父组件主动重新渲染的方式来传入新的 props。
通俗来讲,就是在使用一个组件的时候,可以把参数放在标签的属性当中,所有的属性都会作为组件 props
对象的键值。通过箭头函数创建的组件,需要通过函数的参数来接收props
。
来看一个使用 props 传参的例子:
import React from 'react';
import ReactDOM from 'react-dom';
//函数组件
function Box(props) {
return <h1>Hello {props.name}!</h1>;
}
const element = <Box name="React"/>;
ReactDOM.render(
element,
document.getElementById('root')
);
1.1 设置默认值 defaultProps
在类组件中,可以通过组件类的 defaultProps 属性为 props 设置默认值。在父组件没有指定其值时,会使用这个默认值。propTypes 类型检查发生在 defaultProps 赋值后,所以类型检查也适用于 defaultProps。
import React from 'react';
import ReactDOM from 'react-dom';
class Box extends React.Component {
//方式一:在类的内部设置静态属性
static defaultProps={
name:'react'
}
render() {
return (
<h1>Hello, {this.props.name},age:{this.props.age}</h1>
);
}
}
//方式二:在类的外面 设置defaultProps属性
Box.defaultProps = {
age: 18
};
ReactDOM.render(
<Box/>,
document.getElementById('root')
);
1.2 props.children
每个组件都可以获取到 props.children。它包含组件的开始标签和结束标签之间的内容。类似vue中的插槽。
看下面这个例子:
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class Content extends Component {
render() {
return (
<div>{this.props.children}</div>
);
}
}
class Title extends Component {
render() {
return (
<div>欢迎进入{this.props.children} </div>
);
}
}
ReactDOM.render(
<Content>
<Title>React</Title>
<p>这里是内容</p>
</Content>,
document.getElementById('root')
);
上面的代码中,在页面中渲染Content 的 props.children,最终会渲染出<Content></Content>标签中的所有内容,渲染页面的结果如下:
1.3 proptypes 类型检查
Props 验证使用 propTypes,它可以保证我们的应用组件被正确使用,React.PropTypes 提供很多验证器 (validator) 来验证传入数据是否有效。当向 props 传入无效数据时,JavaScript 控制台会抛出警告。
React.PropTypes 在 React v15.5 版本后已经移到了prop-types 库。
在终端中执行下面的命令,安装 prop-types 插件
yarn add prop-types -S
使用:
- 使用 import 导入 prop-types。
- prop-types 只能在类组件中做验证,不能在函数组件中做验证。
- 通过
类名.propTypes={}
,来定义属性规则,固定写法,注意大小写。 - 类名.propTypes={} 中,键名为属性名,值为对属性的验证规则,例如Proptypes.string等。
- 可以指定一个自定义验证器。它在验证失败时应返回一个 Error 对象。自定义验证时,对象值为一个函数,函数的参数可以通过 arguments 打印。
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import Proptypes from 'prop-types';
class Box extends Component {
render() {
return (
<div>
姓名: {this.props.name},年龄:{this.props.age}
</div>
);
}
}
Box.propTypes={
name:Proptypes.string.isRequired, //表示name为字符串类型,isRequired 必须要传
age:function(props,propName){ //自定义验证
if(props[propName]<20){
return new Error(
'Invalid prop `' + propName + '` Because it Less than 20.'
)
}
}
}
ReactDOM.render(
<Box name="xiaoming" age={18} />,
document.getElementById('root')
);
prop-types 的验证规则:
规则 | 说明 |
---|---|
.array | 输入的类型为数组 |
.bool | 输入的类型为布尔 |
.func | 输入的类型为函数 |
.number | 输入的类型为数值 |
.object | 输入的类型为对象 |
.string | 输入的类型为字符串 |
.symbol | 输入的类型为symbol类型 |
以上为 js 的原生类型
规则 | 说明 |
---|---|
.node | 表示任何可被渲染的元素(包括数字、字符串、元素或数组) (或 Fragment) 也包含这些类型。 |
.element | 表示一个 React 元素,确保传递给组件的 children 中只包含一个元素。 |
.elementType | 表示一个 React 元素类型,即上面案例中的 Box |
.instanceOf() | 声明 prop 为是否为类的实例,这里使用 JS 的 instanceof 操作符 |
.oneOf() | 指定 prop 只能是特定的值,指定它为枚举类型 |
.oneOfType() | 一个对象可以是几种类型中的任意一个类型 |
.arrayOf() | 指定一个数组由某一类型的元素组成,例如只能由数字组成的数组 .arrayOf(PropTypes.number) |
.objectOf() | 指定一个对象由某一类型的值组成,使用方法同 .arrayOf() |
.shape() | 指定一个对象由特定的类型值组成 |
.isRequired | 在任何 PropTypes 属性后面加上 isRequired ,确保这个 prop 没有被提供时,会打印警告信息 |
部分语法使用如下:
import PropTypes from 'prop-types';
Box.propTypes = {
// 你可以让你的 prop 只能是特定的值,指定它为
// 枚举类型。
optionalEnum: PropTypes.oneOf(['News', 'Photos']),
// 一个对象可以是几种类型中的任意一个类型
optionalUnion: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.instanceOf(Message)
]),
// 可以指定一个数组由某一类型的元素组成
optionalArrayOf: PropTypes.arrayOf(PropTypes.number),
// 可以指定一个对象由某一类型的值组成
optionalObjectOf: PropTypes.objectOf(PropTypes.number),
// 可以指定一个对象由特定的类型值组成
optionalObjectWithShape: PropTypes.shape({
color: PropTypes.string,
fontSize: PropTypes.number
}),
// An object with warnings on extra properties
optionalObjectWithStrictShape: PropTypes.exact({
name: PropTypes.string,
quantity: PropTypes.number
}),
2. 状态 state
state 与 props 类似,但是 state 是私有的,并且完全受控于当前组件。
可以在类的构造函数中初始化状态 state,也可以在类中直接定义属性(下面2.1中的写法)来初始化状态 state 。通过 this.state 获取 state 中的数据内容。
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class Box extends Component {
constructor() {
super();
this.state = {
name: 'xiaoming',
age: 18
}
}
render() {
return (
<div>
姓名: {this.state.name},年龄:{this.state.age}
</div>
);
}
}
ReactDOM.render(
<Box />,
document.getElementById('root')
);
2.1 setState()
修改 state 中的数据内容,有以下三种方式:
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class Box extends Component {
state={
name:"xiaoming"
}
componentDidMount(){
//第一种修改方式
this.setState({name:'zhangsan'});
//第二种修改方式
this.state.name="sssss";
this.setState({});
//第三种修改方式
this.setState((preState,props)=>{
return{
name:preState.name+'ccc'
}
},()=>{
//数据修改完成后的回调函数
})
}
render() {
return (
<div>姓名: {this.state.name}</div>
);
}
}
ReactDOM.render(
<Box />,
document.getElementById('root')
);
当使用第二种方式的时候,虽然修改后的数据也在浏览器端渲染出来了,但是在控制台中会输出下面的警告信息,所以不建议直接使用 this.state 修改数据。
react 和 vue 中对 state 中数据的修改都是异步的,在vue中可以使用$nextTick() 方法,获取修改后的数据内容,在 react 中通过使用上面的第三种方法,获取修改后的数据内容。
setState 对数据的更新,会做 merge 合并的操作,即不会覆盖原来的数据内容,会把你提供的对象合并到当前的 state中。
2.2 状态提升
如果多个组件要实现数据共享,可以将数据提升到父组件中,对数据的操作统一在父组件中进行。
3. 属性和状态的区别
props 和 state 的相似点:
- 都是 js 对象,更新数据后会都会触发 render() 更新;
- 都可以设置默认值;props 通过 defaultProps 属性设置默认值;state 直接在定义的时候设置初始值;
props 和 state 的不同点:
- 属性是从父组件获取的,状态是在当前组件中定义的;
- 属性值只能由父组件修改,状态值只能由当前组件修改;
- 属性主要用于父组件和子组件间的通信,在组件内部是无法修改参数的;
- 状态是给自己用的,在内部初始化,被自己修改,在外部是不能修改的,内部通过 setState 修改,会触发render函数;
总结:state 和 props 主要的区别在于 props 是不可变的,而 state 可以根据与用户交互来改变。这就是为什么有些容器组件需要定义 state 来更新和修改数据。 而子组件只能通过 props 来传递数据。