从一个小需求出发
假设有这样一个需求,需要在页面中输出不同水果的一些信息,例如id编号、名称和价格。
很显然,上边有三种不同的水果,但是每一种的格式是一模一样的,看起来就是ul 和li 。
那么根据之前的学习,可以先定义出类似这样的组件以及相关代码,先实现其中一个:
<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 试试,于是组件的定义改成了如下这样:
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里边数据可以通过构造方法等初始化方式获得,还可以通过事件回调进行修改。
但是这里依然只是一个水果,要继续实现三个水果的展示又要怎么办呢?
首先,可能会先把代码写成这样:
<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基本用法
props 和state 一样是react 内置的属性之一,props可以理解为就是组件标签的属性集合,会帮我们收集组件中所有属性。
使用props 可以很好的解决上边的问题,修改后的组件定义以及使用的基本代码如下:
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'))
进行上边这样的修改后,刷新页面就可以看到结果是最开头想要的效果了,如下:
对象解构
虽然效果是实现了,但是上边代码却显得比较繁琐,示例中仅仅展示了三个属性,而真实开发中经常会十几甚至几十的属性,那么像上边这样的代码就会显得极其难看。
上边代码是可以优化的,其中一个就是对象解构,例如组件定义中使用属性时如果使用解构写法,就可以改成下边这样:
render() {
const {id,name,price} =this.props;
return (
<ul>
<li>id:{id}</li>
<li>name:{name}</li>
<li>price:{price}元</li>
</ul>
)
}
解构相当于就是把一个对象的属性一个个拿出来,然后赋值给新的变量,这样之后使用的时候直接使用对应的变量就可以了,而不需要每次都从原对象中拿,代码看起来就会简洁很多。
三点展开运算符传值
除了组件中使用数据可以用解构来优化代码外,组件使用时的代码也是可以优化的。
上边的代码中,三个属性是分别传递的,当属性特别多的时候就特别难写,也特别难看。
同时,实际开发的时候一般这种数据也是从后端返回,基本也都是对象的方式,而不是直接一个一个属性的返回。
因此在使用组件并传数据的时候,上边的代码可以进一步优化为下边这样:
//模拟数据
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'))
当然了,真正开发时要怎么写,还要看具体的需求,如果本身就只需要其中几个属性,那么自然也可以直接传值。
数据约束及默认值
上边代码实现了一开始的需求,最做了一定的优化,但是还是不够完善,因为实际开发的时候,经常需要对数据进行约束,例如数据的类型限定,例如是否必传,例如没有数据的时候是否需要默认值等等。
针对上边的代码,如果要加上这些限制,可以改成这样:
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.propTypes 和Order.defaultProps 后,就可以实现一些数据的限制以及默认值。
例如这里限制了id 必须是数字类型,并且必传;限制了name 必须是字符串类型,但是可以不传;没有对price 进行数据类型以及是否必传的限制,但是通过```Order.defaultProps`给它了默认值。
有了这些限制后,如果对应的数据不符合要求,页面也会显示,只是在控制台会有警告输出。
另外需要注意的是,这里的数字类型使用number ,字符串使用string ,如果类型是函数则需要使用func ,因为function 是关键字,所以需要替换。
还需要注意的是,PropTypes 是React 内置的类,在React15.5 之前可以直接使用React.PropTypes ,而之后的版本则需要额外引入一个依赖,例如:
<script type="text/javascript" src="https://cdn.bootcss.com/prop-types/15.6.1/prop-types.js"></script>
约束和默认值的优化
上边的代码还可以进一步优化,因为从定义可以看出来propTypes 和defaultProps 都是属于类的,但是却将他们定义在了类外边,就显得不那么好。
所以这两个定义是可以放到类里边的,使他们能够更清楚的显示和类的关系,修改后如下:
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>
)
}
}
|