React系统学习(組件三大核心属性之props)

从一个小需求出发

假设有这样一个需求,需要在页面中输出不同水果的一些信息,例如id编号、名称和价格。

很显然,上边有三种不同的水果,但是每一种的格式是一模一样的,看起来就是ulli

那么根据之前的学习,可以先定义出类似这样的组件以及相关代码,先实现其中一个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<body>
<div id="test"></div>

<!--核心库-->
<script type="text/javascript" src="../../js/react.development.js"></script>
<!--操作dom-->
<script type="text/javascript" src="../../js/react-dom.development.js"></script>
<!--用户将jsx转为js-->
<script type="text/javascript" src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>

<script type="text/babel">
class Order extends React.Component{
render() {
return (
<ul>
<li>id:1</li>
<li>name:苹果</li>
<li>price:6.8元</li>
</ul>
)
}
}

ReactDOM.render(<Order/>,document.getElementById('test'))
</script>
</body>

上边已经实现出来了一种水果的信息展示,如果要三个都出来,需要怎么办呢?实现方式不止一种,例如最简单粗暴的就可以直接在组件里写三个ul

但是很显然这样存在一些问题,其中一个问题就是这样的数据是写死的,而实际上这些数据不应该是固定的,例如价格。

另一个问题就是,真的写多个ul的话,明显是不符合组件的思想的,组件本身就是为了复用,如果写成这样子,那就完全体现不出来组件的意义。

那么下边就来解决这两个问题。

数据处理

由于之前学过了state,所以第一时间就能想到把数据从固定变为不固定,可以用state试试,于是组件的定义改成了如下这样:

1
2
3
4
5
6
7
8
9
10
11
12
class Order extends React.Component{
state = {id:1,name:'苹果',price:6.8}
render() {
return (
<ul>
<li>id:{this.state.id}</li>
<li>name:{this.state.name}</li>
<li>price:{this.state.price}元</li>
</ul>
)
}
}

看起来,数据好像不是固定的了,因为state里边数据可以通过构造方法等初始化方式获得,还可以通过事件回调进行修改。

但是这里依然只是一个水果,要继续实现三个水果的展示又要怎么办呢?

首先,可能会先把代码写成这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<body>
<div id="test"></div>
<div id="test1"></div>
<div id="test2"></div>

<!--核心库-->
<script type="text/javascript" src="../../js/react.development.js"></script>
<!--操作dom-->
<script type="text/javascript" src="../../js/react-dom.development.js"></script>
<!--用户将jsx转为js-->
<script type="text/javascript" src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>

<script type="text/babel">
class Order extends React.Component{
state = {id:1,name:'苹果',price:6.8}
render() {
return (
<ul>
<li>id:{this.state.id}</li>
<li>name:{this.state.name}</li>
<li>price:{this.state.price}元</li>
</ul>
)
}
}

ReactDOM.render(<Order/>,document.getElementById('test'))
ReactDOM.render(<Order/>,document.getElementById('test1'))
ReactDOM.render(<Order/>,document.getElementById('test2'))
</script>

</body>

上边代码看起来展示了三种水果,也实现了组件的复用,但是实际上展示效果是这样的:

这并不是展示了三种水果,而是一个水果展示了三次。

如何解决这个问题呢?这就需要今天的主角props上场了。

props基本用法

propsstate一样是react内置的属性之一,props可以理解为就是组件标签的属性集合,会帮我们收集组件中所有属性。

使用props可以很好的解决上边的问题,修改后的组件定义以及使用的基本代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Order extends React.Component{
render() {
return (
<ul>
<li>id:{this.props.id}</li>
<li>name:{this.props.name}</li>
<li>price:{this.props.price}元</li>
</ul>
)
}
}

ReactDOM.render(<Order id={1} name='苹果' price={6.8}/>,document.getElementById('test'))
ReactDOM.render(<Order id={2} name='香蕉' price={3.7}/>,document.getElementById('test1'))
ReactDOM.render(<Order id={3} name='梨子' price={4.5}/>,document.getElementById('test2'))

进行上边这样的修改后,刷新页面就可以看到结果是最开头想要的效果了,如下:

对象解构

虽然效果是实现了,但是上边代码却显得比较繁琐,示例中仅仅展示了三个属性,而真实开发中经常会十几甚至几十的属性,那么像上边这样的代码就会显得极其难看。

上边代码是可以优化的,其中一个就是对象解构,例如组件定义中使用属性时如果使用解构写法,就可以改成下边这样:

1
2
3
4
5
6
7
8
9
10
render() {
const {id,name,price} =this.props;
return (
<ul>
<li>id:{id}</li>
<li>name:{name}</li>
<li>price:{price}元</li>
</ul>
)
}

解构相当于就是把一个对象的属性一个个拿出来,然后赋值给新的变量,这样之后使用的时候直接使用对应的变量就可以了,而不需要每次都从原对象中拿,代码看起来就会简洁很多。

三点展开运算符传值

除了组件中使用数据可以用解构来优化代码外,组件使用时的代码也是可以优化的。

上边的代码中,三个属性是分别传递的,当属性特别多的时候就特别难写,也特别难看。

同时,实际开发的时候一般这种数据也是从后端返回,基本也都是对象的方式,而不是直接一个一个属性的返回。

因此在使用组件并传数据的时候,上边的代码可以进一步优化为下边这样:

1
2
3
4
5
6
7
8
//模拟数据
let fruit={id:1,name:'苹果',price:6.8}
let fruit1={id:2,name:'香蕉',price:3.7}
let fruit2={id:3,name:'梨子',price:4.5}

ReactDOM.render(<Order {...fruit}/>,document.getElementById('test'))
ReactDOM.render(<Order {...fruit1}/>,document.getElementById('test1'))
ReactDOM.render(<Order {...fruit2}/>,document.getElementById('test2'))

当然了,真正开发时要怎么写,还要看具体的需求,如果本身就只需要其中几个属性,那么自然也可以直接传值。

数据约束及默认值

上边代码实现了一开始的需求,最做了一定的优化,但是还是不够完善,因为实际开发的时候,经常需要对数据进行约束,例如数据的类型限定,例如是否必传,例如没有数据的时候是否需要默认值等等。

针对上边的代码,如果要加上这些限制,可以改成这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Order extends React.Component{
render() {
const {id,name,price} =this.props;
return (
<ul>
<li>id:{id}</li>
<li>name:{name}</li>
<li>price:{price}元</li>
</ul>
)
}
}

Order.propTypes={
id:PropTypes.number.isRequired,
name:PropTypes.string
}

Order.defaultProps={
price:0
}

let fruit={id:1,name:'苹果',price:6.8}
let fruit1={id:2,name:'香蕉',price:3.7}
let fruit2={id:3,name:'梨子'}

ReactDOM.render(<Order {...fruit}/>,document.getElementById('test'))
ReactDOM.render(<Order {...fruit1}/>,document.getElementById('test1'))
ReactDOM.render(<Order {...fruit2}/>,document.getElementById('test2'))

有了上边新增的Order.propTypesOrder.defaultProps后,就可以实现一些数据的限制以及默认值。

例如这里限制了id必须是数字类型,并且必传;限制了name必须是字符串类型,但是可以不传;没有对price进行数据类型以及是否必传的限制,但是通过```Order.defaultProps`给它了默认值。

有了这些限制后,如果对应的数据不符合要求,页面也会显示,只是在控制台会有警告输出。

另外需要注意的是,这里的数字类型使用number,字符串使用string,如果类型是函数则需要使用func,因为function是关键字,所以需要替换。

还需要注意的是,PropTypesReact内置的类,在React15.5之前可以直接使用React.PropTypes,而之后的版本则需要额外引入一个依赖,例如:

1
<script type="text/javascript" src="https://cdn.bootcss.com/prop-types/15.6.1/prop-types.js"></script>

约束和默认值的优化

上边的代码还可以进一步优化,因为从定义可以看出来propTypesdefaultProps都是属于类的,但是却将他们定义在了类外边,就显得不那么好。

所以这两个定义是可以放到类里边的,使他们能够更清楚的显示和类的关系,修改后如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Order extends React.Component{
static propTypes={
id:PropTypes.number.isRequired,
name:PropTypes.string
}

static defaultProps={
price:0
}


render() {
const {id,name,price} =this.props;
return (
<ul>
<li>id:{id}</li>
<li>name:{name}</li>
<li>price:{price}元</li>
</ul>
)
}
}

推荐文章