react使用 props
上一篇博文写了 react 使用 state 进行状态管理,状态是 react 一个常用的模块,今天说一下 props 这个模块,也是在开发 react 过程中必不可少的。
案例
我们使用一个案例来说一下这个功能。
我们写一个页面,用来展示一个男孩的信息,就类似于下面的样子。
下面开始。
props 基本使用
案例效果我们很容易实现,直接上代码吧!
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>17. props基本使用</title>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="app1"></div>
<!-- 引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 引入react-dom 用于支持 react 操作 dom -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入 label,用于将 jsx 转化为 js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<!-- 此处必须写 text/babel -->
<script type="text/babel">
class Boy extends React.Component {
render() {
return (
<ul>
<li>姓名:王肖杰</li>
<li>性别:男</li>
<li>年龄:12</li>
</ul>
)
}
}
ReactDOM.render(<Boy/>, document.getElementById('app1'))
</script>
</body>
</html>
好的,上面的代码就实现出了案例效果图的样子,一模一样。
但是仔细看代码发现不是很可行,因为代码里面直接把数据写死了,我想使用活的数据进行渲染,这个怎么做呢?很简单哈!修改一下。
<script type="text/babel">
class Boy extends React.Component {
state = { name:'王肖杰', sex: '男', age: 12}
render() {
return (
<ul>
<li>姓名:{this.state.name}</li>
<li>性别:{this.state.sex}</li>
<li>年龄:{this.state.age}</li>
</ul>
)
}
}
ReactDOM.render(<Boy/>, document.getElementById('app1'))
</script>
OK,上面的代码效果是一样的,数据是活的了,很棒!
其实还是有瑕疵,为啥呢?因为我们上边代码是通过上篇博文的 state 进行维护的,尽管可以但是有一个问题,啥问题呢,就是假设我现在有去实例化一个 Boy 组件。看代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>17. props基本使用</title>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="app1"></div>
<div id="app2"></div>
<!-- 引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 引入react-dom 用于支持 react 操作 dom -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入 label,用于将 jsx 转化为 js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<!-- 此处必须写 text/babel -->
<script type="text/babel">
class Boy extends React.Component {
state = { name:'王肖杰', sex: '男', age: 12}
render() {
return (
<ul>
<li>姓名:{this.state.name}</li>
<li>性别:{this.state.sex}</li>
<li>年龄:{this.state.age}</li>
</ul>
)
}
}
ReactDOM.render(<Boy/>, document.getElementById('app1'))
ReactDOM.render(<Boy/>, document.getElementById('app2'))
</script>
</body>
</html>
我们保存,刷新一下浏览器看效果。
看,展示出来了两个,而且数据是一模一样的。但是我的本意是,第一个是一个男孩信息,第二个又是另一个男孩信息,这样子直接使用 state 把数据维护死不太好。
这时候我们就想,我每渲染一个组件的时候就给这个组件从外边传参,然后 Boy 组件根据传进来得参数渲染不同的效果。
这时候,就需要今天要学的新模块 —— props
props 怎么使用呢?很简单,我们先看一下实例里面的数据。打印一下:
上边的截图可以看出来,实例上面已经存在了 props ,但是是空的,为啥是空的呢?因为我们没有往里面存储数据啊!其实有没有发现?我们学习 state 的时候打印的时候,如果我们不自己设置 state 的话,state 就是 undefined,但是 props 我们现在没传,他不是 undefined,而是一个空对象?所以说这样的话,我们不需要创建 props ,而是直接向组件实例传值就可以了。
问题来了,怎么传值呢?看下面代码:
<!-- 此处必须写 text/babel -->
<script type="text/babel">
class Boy extends React.Component {
render() {
console.log(this)
return (
<ul>
<li>姓名:{this.props.name}</li>
<li>性别:{this.props.sex}</li>
<li>年龄:{this.props.age}</li>
</ul>
)
}
}
ReactDOM.render(<Boy name="王肖杰" sex="男" age="12" />, document.getElementById('app1'))
ReactDOM.render(<Boy name="王肖洛" sex="男" age="10" />, document.getElementById('app2'))
</script>
上面的代码看的懂吧? 我们在 ReactDOM.render(<Boy name="王肖杰" sex="男" age="12" />, document.getElementById('app1')) 里面想组件传递了 name,age,sex 三个属性,其实这三个属性会被 props 存储起来,所以说,实例里面的 props 中就包含了传进去的三个值,直接可以使用 this.props.name, this.props.age, this.props.sex 获取到。所以说我们保存刷新,看效果。
我们可以看到,props 确实设置完成了,控制台可以看到我们设置的 props,而且,页面也可以正常的渲染。
好了,props 的基本使用就是这个样子,记住就好。
【本部分相关代码资料】:我是𝒆𝒅. 的 gitee
批量传入 props
上面一部分,我们介绍了 props 的简单用法,我们知道往标签像添加属性一样传入数据就可以给 props 把数据存进去使用:
ReactDOM.render(<Boy name="王肖杰" sex="男" age="12" />, document.getElementById('app1'))
看,上面代码传了三个数据,name,age,sex。确实成功了,但是再想:现在是传了3个,但是如果我有一个需求,需要传入10个,20个,30个也是这样子一个属性一个树形的传递吗?答案是对的,就是一个一个的写。
但是,有其他的办法可以不这样写,那就是这部分要分享的 批量传入 props。
一个一个写完全没问题,但是操作繁琐,代码量大,不方便,而且我们传入的一般是后端返回的数据,后端返回的数据一般是以对象的形式存在的。所以说接下来分享一下一次性全部传入的方式,看下面的代码:
<!-- 此处必须写 text/babel -->
<script type="text/babel">
class Boy extends React.Component {
render() {
console.log(this)
return (
<ul>
<li>姓名:{this.props.name}</li>
<li>性别:{this.props.sex}</li>
<li>年龄:{this.props.age}</li>
</ul>
)
}
}
// 批量传入
const boy = {name:'王肖杰', sex: '男', age: 10}
ReactDOM.render(<Boy {...boy} />, document.getElementById('app1'))
// 单个传入
ReactDOM.render(<Boy name="王肖洛" sex="男" age="10" />, document.getElementById('app2'))
</script>
上面代码我们看第一个组件,他在组件使用 { } 中包裹了一个展开的对象 实现传值。第二个组件是一个一个传值的,我们先看一下结果。
OK,依旧可以正常显示。然后这个批量传入 props 的地方使用了 ... 展开。
我们先回顾一下展开运算符。
展开运算符
... 三个点呢是展开运算符,他可以把一个数组给展开。
【展开一个数组】
let arr1 = [1, 3, 5, 7, 9]
// 展开一个数组
console.log(...arr1)
看结果
数组就被展开了。
【连接两个数组】
let arr1 = [1, 3, 5, 7, 9]
let arr2 = [2, 4, 6, 8, 10]
// 链接两个数组
let arr3 = [...arr1, ...arr2]
console.log(arr3)
看结果:
【传参计算数字之和】
function sum(...numbers) {
return numbers.reduce((preVal, currVal) => {
return preVal + currVal
})
}
console.log(sum(2, 3, 4, 5))
看结果
【展开运算符不能展开对象】
切记: 展开运算符是不可以展开对象的。
// 展开对象报错,展开运算符不能展开一个对象
let boy = { name: '王肖杰', sex: '男', age: 10 }
console.log(...boy)
看控制台,直接报错。
【深拷贝对象】
// 复制对象 深拷贝 构造字面变量对象时使用展开语法
let boy1 = { name: '王肖杰', sex: '男', age: 10 }
let boy2 = { ...boy1 }
boy1.name = "王俊天"
console.log(boy1, boy2)
保存看结果
【复制对象并修改属性值】
// 复制对象的同时,修改属性值,其实就是合并
let boy3 = { name: '王俊宇', sex: '男', age: 4 }
let boy4 = { ...boy3, name: '王俊天' }
console.log(boy3, boy4)
看结果:
好了,上边就是简单的 展开运算符 使用规则,还有其他的,都是些基础,稍微过一下就行了。
思路转回 “ 批量传入props案例 ”
在批量传入 props 的时候我们是在 组件 中使用 { } 加上 展开对象的方式传入的。
const boy = {name:'王肖杰', sex: '男', age: 10}
ReactDOM.render(<Boy {...boy} />, document.getElementById('app1'))
然后再回顾 展开运算符 的时候有一个拷贝的案例:
let boy1 = { name: '王肖杰', sex: '男', age: 10 }
let boy2 = { ...boy1 }
长的差不多哈!
【请问】:react 组件上面的 {...boy} 和展开运算符案例的 { ...boy1 } 是一样的吗?
【答】:不是!
为啥,因为 组件上的 {} 表示花括号里面的是 js运算表达式,是 ...boy 的执行结果。但是 let boy2 = { ...boy1 } 等号右边整个都是 js 代码。
所以说 组件 的 {...boy} 和下面的代码是一样的,直接展开对象:
let boy = { name: '王肖杰', sex: '男', age: 10 }
console.log(...boy)
诶?不对啊,不是 展开运算符不能展开对象吗?为啥这个组件这里可以呀?
原因是:我们引入的 babel 和 react 合作之后,就可以展开对象,但是只适用于标签属性的传递。啥叫只适用于标签属性的传递呀?就是你写在别的地方不行,只有在批量传递props的时候可以展开对象。好的,这个地方知道就行。
OK,这就是 批量传入 props 的方法,这部分结束。
【本部分相关代码资料】:我是𝒆𝒅. 的 gitee
props 限制
OK,上一部分说了,props 的批量传入,现在又有一个问题,就是我想给每个人的年龄自动加上 1 ,也就是说还是上面代码,我想给 王肖杰 和 王肖洛 传递进去的年龄基础上默认在加 1 岁,怎么实现呢?很简单,既然已经获取到 props 的 age 参数了,在渲染的时候直接 + 1 就可以了吧!看下面代码:
<!-- 此处必须写 text/babel -->
<script type="text/babel">
class Boy extends React.Component {
render() {
let {name, age, sex} = this.props // 这里解构一下方便渲染
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age + 1}</li>
</ul>
)
}
}
// 批量传入
const boy = { name: '王肖杰', sex: '男', age: 10 }
ReactDOM.render(<Boy {...boy} />, document.getElementById('app1'))
ReactDOM.render(<Boy name="王肖洛" sex="男" age="9" />, document.getElementById('app2'))
</script>
在渲染年龄的时候自动 + 1 :
看执行结果,感觉出问题了,王肖洛的年纪不对呀?加是加了,但是感觉成了字符串拼接,而不是数值相加,什么原因呢?很简单,因为王肖洛同学的年纪我们传入的事字符串类型:
ReactDOM.render(<Boy name="王肖洛" sex="男" age="9" />, document.getElementById('app2'))
怎么修改呢?简单:
ReactDOM.render(<Boy name="王肖洛" sex="男" age={9} />, document.getElementById('app2'))
现在两个人的年龄都正常了。
但是我们写的是组件啊,这是我们自己用,所以知道 age 必须传递数值类型,如果是别人用了我们的组件,他不知道很正常,他直接穿了之前那样子的字符串那就出问题了,一个男孩的年纪90多岁当然可以发现不对修改过来,但是如果是金额的话,多一位少一位会出大问题的。
so,我们需要给传递进去的 props 进行限制,比如说,age 要求必须是 数值类型 的!
怎么操作? 很简单。提供了方法,就是在 Boy 上添加 propTypes 属性实现对 props 的限制。
<!-- 此处必须写 text/babel -->
<script type="text/babel">
class Boy extends React.Component {
render() {
let {name, age, sex} = this.props
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age + 1}</li>
</ul>
)
}
}
// 对标签属性进行类型、必要性限制
Boy.propTypes = {
age: React.PropTypes.number
}
// 批量传入
const boy = { name: '王肖杰', sex: '男', age: 10 }
ReactDOM.render(<Boy {...boy} />, document.getElementById('app1'))
ReactDOM.render(<Boy name="王肖洛" sex="男" age="9" />, document.getElementById('app2'))
</script>
看上面的代码,给 Boy 添加了 propTypes 属性,注意 propTypes 的 首字母一定是小写,而对 age 限制的 React.PropTypes.number 中的 PropTypes 首字母一定是大写!!
Boy.propTypes = {
age: React.PropTypes.number // 设置 age 必须是数值类型
}
保存刷新查看效果。
嚯! 好家伙,直接报错了。
问题出在哪里,就是 在 react 15 之前的版本,都是可以这样写的,他把 PropTypes 直接挂在了 React 下面,这样其实是挺占用效率的,所以在 16 版本以后,不能这样写了,他把 PropTypes 直接拆出来 props-types.js 文件,我们需要引用一下才可以使用,文件在 gitee:
<!-- 引入 prop-types,用于对于组件标签属性进行限制 -->
<script type="text/javascript" src="../js/prop-types.js"></script>
引入之后,使用的方式也有些许的改变,因为没有挂在在 React 下面了,在我们引入 prop-types.js 文件的时候就直接导入了 PropTypes 对象,所以写法改成:
// 对标签属性进行类型、必要性限制
Boy.propTypes = {
age: PropTypes.number, // 限制年龄为数字
}
就可以了!
这时候我们刷新页面,当 王肖洛 的年纪是字符串的类型的话,控制台会爆出警告提醒。
他会报错进行提醒的哈!但是他并不会阻碍程序执行!
你看,假设我们把名字删掉,不传了,会有什么现象?
<!-- 此处必须写 text/babel -->
<script type="text/babel">
class Boy extends React.Component {
render() {
let {name, age, sex} = this.props
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age + 1}</li>
</ul>
)
}
}
// 对标签属性进行类型、必要性限制
Boy.propTypes = {
age: PropTypes.number, // 限制年龄为数字
}
// 批量传入
const boy = { name: '王肖杰', sex: '男', age: 10 }
ReactDOM.render(<Boy {...boy} />, document.getElementById('app1'))
ReactDOM.render(<Boy sex="男" age="9" />, document.getElementById('app2'))
</script>
看代码,我们把 王肖洛 的名字删掉不传了,看会发生啥。
OK。名字直接不显示了,当然其他也一样,不传递就不显示,但是实际业务可能不行啊!我名字需要必填,而且是传递字符串形式,所以说需要给名字添加一个限制,要求是字符串,而且不能不写。
// 对标签属性进行类型、必要性限制
Boy.propTypes = {
name: PropTypes.string.isRequired, // 限制名字必传且为字符串
age: PropTypes.number, // 限制年龄为数字
}
好的,必传就是继续 .isRequired。
好的,现在名字和年龄的校验都写完了,当然我们还可以继续加性别的限制,必须是字符串,就都是一样的东西了。
// 对标签属性进行类型、必要性限制
Boy.propTypes = {
name: PropTypes.string.isRequired, // 限制name必传且为字符串
sex: PropTypes.string, // 限制性别为字符串
age: PropTypes.number, // 限制年龄为数字
}
好,对于 props 的限制已经说的差不多了。
还有一个问题哈,就比如说我们上面没有对 性别 进行必传限制,所以说 sex 可以不传,那如果不传就是空,我能不能直接在没有数据传递进来的时候给一个默认值啊!当然可以!
看,下面代码我们把 王肖洛 的性别删掉了。
ReactDOM.render(<Boy name="王肖洛" age={9} />, document.getElementById('app2'))
然后嘞,我们给没有设置性别的男孩都给设置成 男。
Boy.defaultProps = {
sex: '男', // sex 不传入,则设置默认值为 未知
}
保存刷新
呦吼! 默认值设置成功!在性别没有传入的时候默认是男。同样可以设置年龄。
OK,那这部分关于 props 限制的部分就结束了哈,奥对了, 还有一点,限制的时候 string,number 啥的都要是小写哈,不能大写,注意!
还有一个,就是我能不能传递一个方法进组件啊?
当然可以!看下面代码,给王肖洛同学添加一个说话的方法。
function speak() {
console.log("我是说话的方法")
}
ReactDOM.render(<Boy name="王肖洛" age={9} speak={speak} />, document.getElementById('app2'))
我怎么对说话的方法进行限制呢?下面代码,直接校验 speak 是不是方法:
Boy.propTypes = {
name: PropTypes.string.isRequired, // 限制name必传且为字符串
sex: PropTypes.string, // 限制性别为字符串
age: PropTypes.number, // 限制年龄为数字
speak: PropTypes.function // 限制 speak 为方法
}
保存刷新:
控制台报错了,为啥呢?明明是方法,为啥还报错?因为关键字冲突了,校验 function 的时候,要写作 func 就可以了。
Boy.propTypes = {
name: PropTypes.string.isRequired, // 限制name必传且为字符串
sex: PropTypes.string, // 限制性别为字符串
age: PropTypes.number, // 限制年龄为数字
speak: PropTypes.func // 限制 speak 为方法
}
现在在保存刷新:
最后说一句:
props 是只读的,不能进行修改也修改不了!
就没有问题了,好,这部分就到这儿。
【本部分相关代码资料】:我是𝒆𝒅. 的 gitee
props 简写
到这里,我们的案例已经写了很多东西了哈,比较多,那能不能稍微化简一下子呢?
答案是可以的,我们看下面这一张图。
这几部分代码里面,红框里面的都是组件自身使用的东西,但是他却分成了三部分,校验和初始值都是给 Boy 原型也就是Boy类自己使用的,不是给实例使用,那能不能把对标签的校验和赋初始值也都放进 Boy 类里面去呢?可以,使用 static 。
类(class)通过 static 关键字定义静态方法。不能在类的实例上调用静态方法,而应该通过类本身调用。
所以说,我们的代码可以直接修改为:
<!-- 此处必须写 text/babel -->
<script type="text/babel">
class Boy extends React.Component {
// 对标签属性进行类型、必要性限制
static propTypes = {
// name: React.PropTypes.string
name: PropTypes.string.isRequired, // 限制name必传且为字符串
sex: PropTypes.string, // 限制性别为字符串
age: PropTypes.number, // 限制年龄为数字
speak: PropTypes.func // 限制 speak 为方法
}
// 对标签属性默认值设置
static defaultProps = {
sex: '男', // sex 不传入,则设置默认值为 未知
age: 0 // age 不传入,则设置默认值为 0
}
render() {
// 首先注意,props是只读的,不允许任何的修改操作!!
let { name, age, sex } = this.props
// this.props.name = '王俊宇' // 此行代码报错,因为只读,不能修改,改不了
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
)
}
}
// 批量传入
const boy = { name: '王肖杰', sex: '男', age: 10 }
ReactDOM.render(<Boy {...boy} />, document.getElementById('app1'))
</script>
效果和之前是一毛一样的!
【本部分相关代码资料】:我是𝒆𝒅. 的 gitee
类式组件中的构造器和 props
在学习 state 状态的时候,我们写过一个 构造体函数 不知道还记不记得,当时是这么说的,我去之前博客里面截个图:
上篇博客留下来的坑,现在填一下。
构造函数 和 props 之间有啥关系呢?
首先稍微说一下 构造函数在什么情况下使用:
- 通过给 this.state 赋值对象来初始化内部 state。
- 为事件处理函数绑定实例。
只有这两种情况使用 构造函数。第一种情况我们上一篇博客用到了,初始化 state 的时候对吧?尽管最后简写出去了,第二种情况也遇到了,就是点击事件 this 指向问题解决时候,尽管最后也用箭头函数解决了,最后 构造函数就没有在用了。
那如果我们就要写的话,怎么写?
// 编写构造器 构造器传进来的props就是你向组件传递的props
constructor(props) {
super(props) // 继承的时候也传进去
}
就上边这样写。接收 props,然后 super 继承传入 props。
那我就是不传会怎么样?试一下。
改一下,不传了。
// 编写构造器
constructor(props) {
super()
}
嘿嘿,没影响。
正常显示,也没有报错。
那为啥官网要我们传呢?官网其实给了解释:在 React 组件挂载之前,回调用他的构造函数,在为 React.Component 子类实现构造函数时,应在其他语句之前调用 super(props)。否则, this.props 在构造函数中可能出现没定义的 bug。
啥意思,就是如果不传 props 的话,你在构造体种调用 this.props 可能会是 undefined。
// 编写构造器
constructor(props) {
super() // 接收到外部传进组件的 props,但是不给继承的传
console.log(this.props)
}
看一下打印结果
记住是 this.props,如果不加 this,只打印 props 是肯定没问题的。
// 编写构造器
constructor(props) {
super() // 接收到外部传进组件的 props,但是不给继承的传
console.log(this.props, props)
}
看一下:
所以说这玩意儿知道就好,几乎不会用,既然要用
props ,直接使用传进来得 props 就行,没有必要脱裤子放屁的去加上个 this。当然官方得解决bug,我们可以不用,不是可以不用,用的话极其罕见!
好了,这一部分也结束了。
【本部分相关代码资料】:我是𝒆𝒅. 的 gitee
函数式组件使用 props
上边我们说的 props 都是在 类式组件 中使用的,之前博客说,除了类式组件还有函数组件,函数组件能不能使用 props 呢?
首先,函数组件他就是一个函数对吧?函数里面没有自己的 this 这个是肯定的,所以说,函数组件里面是肯定不能使用 state 了,因为使用 state 的时候都是 this.state 操作的吧?所以不能使用 state。后面会说 ref,在函数组件中,ref 也是不能使用的。但是 props 是可以的,主要是因为他可以传参。
怎么使用呢?直接一码带过。了解一下就可以了。
<!-- 此处必须写 text/babel -->
<script type="text/babel">
// 创建函数组件
function Boy(props) {
console.log(props)
let { name, age, sex } = props
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
)
}
// 对标签属性进行类型、必要性限制
Boy.propTypes = {
// name: React.PropTypes.string
name: PropTypes.string.isRequired, // 限制name必传且为字符串
sex: PropTypes.string, // 限制性别为字符串
age: PropTypes.number, // 限制年龄为数字
speak: PropTypes.func // 限制 speak 为方法
}
// 对标签属性默认值设置
Boy.defaultProps = {
sex: '未知', // sex 不传入,则设置默认值为 未知
age: 0 // age 不传入,则设置默认值为 0
}
ReactDOM.render(<Boy name="我是ed." />, document.getElementById('app1'))
</script>
OK,效果和类式组件一模一样。
【本部分相关代码资料】:我是𝒆𝒅. 的 gitee
好了,今天 props 就到这里,再见!