345 lines
6.9 KiB
Markdown
345 lines
6.9 KiB
Markdown
# 组件状态与数据传递
|
||
|
||
|
||
|
||
本章主要包含以下知识点:
|
||
|
||
- 组件状态
|
||
- *props*
|
||
- *props* 验证
|
||
- 状态提升
|
||
|
||
|
||
|
||
## 组件状态
|
||
|
||
早期类组件被称之为有状态组件,就是因为在类组件中能够维护组件数据。
|
||
|
||
```js
|
||
class 类名 extends React.Component{
|
||
constructor(){
|
||
super();
|
||
// 设置组件自身的数据状态
|
||
this.state = {
|
||
xxx : xxx
|
||
}
|
||
}
|
||
render(){
|
||
return (
|
||
// 通过 {this.state.xxx} 来获取状态数据
|
||
)
|
||
}
|
||
}
|
||
|
||
// 或者
|
||
class 类名 extends React.Component{
|
||
state = {
|
||
xxx : xxx
|
||
}
|
||
render(){
|
||
return (
|
||
// 通过 {this.state.xxx} 来获取状态数据
|
||
)
|
||
}
|
||
}
|
||
```
|
||
|
||
|
||
|
||
不要直接去修改状态值,而是应该通过 *setState* 方法修改组件的 *state* 状态数据。
|
||
|
||
```js
|
||
this.setState({
|
||
xxx: 新值
|
||
})
|
||
```
|
||
|
||
*setState*,它对状态的改变,**可能**是异步的。
|
||
|
||
> 如果改变状态的代码处于某个 *HTML* 元素的事件中,则其是异步的,否则是同步
|
||
|
||
|
||
|
||
如果在事件处理函数里面想拿到 *setState* 执行后的数据,可以提前使用一个变量来存储计算结果,或者使用 *setState* 的第二个参数,它是一个函数,这个函数会在 *state* 更新后被调用。
|
||
|
||
|
||
|
||
最佳实践:
|
||
|
||
1. 把所有的 *setState* 当作是异步的
|
||
2. 永远不要信任 *setState* 调用之后的状态
|
||
3. 如果要使用改变之后的状态,需要使用回调函数(*setState* 的第二个参数)
|
||
4. 如果新的状态要根据之前的状态进行运算,使用函数的方式改变状态(*setState* 的第一个函数)
|
||
|
||
|
||
|
||
*React* 会对异步的 *setState* 进行优化,将多次 *setState* 进行合并(将多次状态改变完成后,再统一对 *state* 进行改变,然后触发 *render*)
|
||
|
||
|
||
|
||
## *props*
|
||
|
||
和 *Vue* 一样,在 *React* 中组件会存在层级关系,那么自然会涉及到组件之间进行数据的传递。
|
||
|
||
如果是父组件向子组件传递数据,则使用 *props*。
|
||
|
||
如果是函数组件,*props* 作为函数的一个参数传入:
|
||
|
||
```react
|
||
function 组件名(props) {
|
||
return (
|
||
// 一段 JSX
|
||
// 通过 props.xxx 获取传入的值
|
||
<div>
|
||
<p>姓名:{props.name}</p>
|
||
<p>年龄:{props.age}</p>
|
||
<p>性别:{props.gender}</p>
|
||
</div>
|
||
);
|
||
}
|
||
```
|
||
|
||
如果是类组件,则需要在 *constructor* 中将 *props* 通过 *super* 传递给父类,然后通过 *this.props* 的方式来获取传入的值:
|
||
|
||
```react
|
||
class 组件名 extends React.Component {
|
||
constructor(props) {
|
||
super(props);
|
||
}
|
||
render() {
|
||
return (
|
||
// 一段 JSX
|
||
// 通过 this.props.xxx 获取传入的值
|
||
<div>
|
||
<p>姓名:{this.props.name}</p>
|
||
<p>年龄:{this.props.age}</p>
|
||
<p>性别:{this.props.gender}</p>
|
||
</div>
|
||
);
|
||
}
|
||
}
|
||
```
|
||
|
||
|
||
|
||
通过 *props.children*,可以实现类似于 *Vue* 中插槽的功能,例如:
|
||
|
||
```react
|
||
class 组件B extends React.Component{
|
||
constructor(props){
|
||
super(props);
|
||
}
|
||
render(){
|
||
return (
|
||
<div>
|
||
{this.props.children}
|
||
</div>
|
||
);
|
||
}
|
||
}
|
||
class 组件A extends React.Component{
|
||
constructor(props){
|
||
super(props);
|
||
}
|
||
render(){
|
||
return (
|
||
<组件B>
|
||
<p>Hello, React</p>
|
||
<p>Hello, Redux</p>
|
||
<p>Hello, Facebook</p>
|
||
<p>Hello, Google</p>
|
||
</组件B>
|
||
);
|
||
}
|
||
}
|
||
```
|
||
|
||
|
||
|
||
## *props* 验证
|
||
|
||
在 *Vue* 中,可以对传入的 *props* 设置默认值,以及验证 *props* 的有效性,在 *React* 中,针对 *props* 也可以做这些事。
|
||
|
||
通过 *defaultprops* 就可以对 *props* 设置默认值。
|
||
|
||
```react
|
||
function Greeting(props) {
|
||
const { name, age, gender } = props;
|
||
return (
|
||
<div>
|
||
<p>姓名:{name}</p>
|
||
<p>年龄:{age}</p>
|
||
<p>性别:{gender}</p>
|
||
</div>
|
||
);
|
||
}
|
||
// 设置默认的 props 值,当组件没有传值时会使用默认值
|
||
Greeting.defaultProps = {
|
||
name : 'xiejie',
|
||
age : 18,
|
||
gender : 'male'
|
||
};
|
||
```
|
||
|
||
```react
|
||
class Greeting extends React.Component {
|
||
constructor(props) {
|
||
super(props);
|
||
}
|
||
// 设置默认的 defaultProps 属性值
|
||
static defaultProps = {
|
||
name : "xiejie",
|
||
age : 18,
|
||
gender : 'male'
|
||
}
|
||
render() {
|
||
return (
|
||
<div>
|
||
<p>姓名:{this.props.name}</p>
|
||
<p>姓名:{this.props.age}</p>
|
||
<p>姓名:{this.props.gender}</p>
|
||
</div>
|
||
);
|
||
}
|
||
}
|
||
// 或者
|
||
Greeting.defaultProps = {
|
||
name : "xiejie",
|
||
age : 18,
|
||
gender : 'male'
|
||
}
|
||
```
|
||
|
||
|
||
|
||
关于 *props* 的类型检查,从 *React v15.5* 版本开始,移入到了 [`prop-types` 库](https://www.npmjs.com/package/prop-types) 。
|
||
|
||
```react
|
||
import PropTypes from 'prop-types';
|
||
|
||
class Greeting extends React.Component {
|
||
render() {
|
||
return (
|
||
<h1>Hello, {this.props.name}</h1>
|
||
);
|
||
}
|
||
}
|
||
|
||
Greeting.propTypes = {
|
||
name: PropTypes.string
|
||
};
|
||
```
|
||
|
||
```react
|
||
import PropTypes from 'prop-types'
|
||
|
||
function HelloWorldComponent({ name }) {
|
||
return (
|
||
<div>Hello, {name}</div>
|
||
)
|
||
}
|
||
|
||
HelloWorldComponent.propTypes = {
|
||
name: PropTypes.string
|
||
}
|
||
|
||
export default HelloWorldComponent
|
||
```
|
||
|
||
|
||
|
||
## 状态提升
|
||
|
||
在 *Vue* 中,父传子通过 *props*,子传父通过触发自定义事件。
|
||
|
||
在 *React* 中,如果子组件需要向父组件传递数据,同样是通过触发父组件传递给子组件的事件来进行传递。
|
||
|
||
这在官网中被称之为“状态提升”:*https://zh-hans.reactjs.org/docs/lifting-state-up.html*
|
||
|
||
汇率转换案例:
|
||
|
||
父组件
|
||
|
||
```react
|
||
import React from "react";
|
||
import Money from "./component/Money";
|
||
|
||
// 类组件
|
||
class App extends React.Component {
|
||
state = {
|
||
dollar: "",
|
||
rmb: ""
|
||
}
|
||
|
||
transformToRMB = (value) => {
|
||
if (parseFloat(value) || value === "" || parseFloat(value) === 0) {
|
||
this.setState({
|
||
dollar: value,
|
||
rmb: value === "" ? "" : (value * 7.3255).toFixed(2)
|
||
})
|
||
} else {
|
||
alert("请输入数字");
|
||
}
|
||
}
|
||
|
||
transformToDollar = (value) => {
|
||
if (parseFloat(value) || value === "" || parseFloat(value) === 0) {
|
||
this.setState({
|
||
dollar: value === "" ? "" : (value * 0.1365).toFixed(2),
|
||
rmb: value
|
||
})
|
||
} else {
|
||
alert("请输入数字");
|
||
}
|
||
}
|
||
|
||
render() {
|
||
return (
|
||
<div>
|
||
<Money text="美元" money={this.state.dollar} transform={this.transformToRMB} />
|
||
<Money text="人民币" money={this.state.rmb} transform={this.transformToDollar} />
|
||
</div>
|
||
)
|
||
}
|
||
|
||
}
|
||
|
||
export default App;
|
||
```
|
||
|
||
子组件
|
||
|
||
```react
|
||
import React from 'react';
|
||
|
||
function Money(props) {
|
||
|
||
|
||
function handleChange(e){
|
||
// 在子组件中,要做的事情很简单,将用户数据的值,传递给父组件
|
||
// 让父组件来进行修改
|
||
props.transform(e.target.value);
|
||
}
|
||
|
||
|
||
return (
|
||
<fieldset>
|
||
<legend>{props.text}</legend>
|
||
<input type="text" value={props.money} onChange={handleChange}/>
|
||
</fieldset>
|
||
);
|
||
}
|
||
|
||
export default Money;
|
||
```
|
||
|
||
|
||
|
||
---
|
||
|
||
-*EOF*-
|
||
|
||
|
||
|
||
|