React组件实例的三大属性及事件处理
0. state
0. props
0. refs与事件处理
既然是实例属性,在不用hooks的情况下,只能通过类式组件来使用,所以本章例子都会用类式组件举例
⭐第一个属性:state
state属性中存放着组件的状态,或者说是数据更好理解一些,它里面的值是对象形式(包含多个key-value的组合),我们通过更新组件的state来更新对应的页面显示(重新渲染组件)
在我们创建好的组件实例中,已经有了state属性存在,但其内容为null,那么我们创建组件时,如何在state中定义数据呢?
答:通过构造器添加state状态,或直接对state进行赋值(更推荐)
//创建组件
class MyCpn extends React.Component{
constructor(props){
super(props)
//方式一:通过构造器操作
this.state = {myState:'hello!'}//在这里添加更改state内容
}
//方式二:直接操作
state = {myState:'hello!'}
render(){
return <h1>{this.state.myState}</h1>//在这里进行调用
}
}
这样,我们就完成了state的初始化以及数据读取调用
注意: state中的属性一旦在构造器中定义,不可直接更改,否则响应式会失效,我们应通过其内置API:setState()进行状态的更改
⭐第二个属性:props
类式组件中使用props
上面的state是创建组件时就定义好了内容,如果我们想要在调用组件时再传入状态,就要用到props啦!
props同样也是组件实例上的属性,其状态的传入方式:
ReactDOM.render(<MyCpn name="何小幸" age="18"/>,document.getElementById('app'));
这样,何小幸和18都传入了组件中,成为prop属性中的状态,我们在组件中通过this.props.name/age就可以取到对应的属性。
如果属性有很多呢?
简化写法:
const info = {name:'何小幸',age:18,sex:"unkonwn",tel:'137...',addr:'山西太原',birth:'010214'}
ReactDOM.render(<MyCpn {...info}/>,document.getElementById('app'))
这样info对象中的数据都会分别传入组件中
限制props中属性的类型与设置默认值
在React版本15.5.0以前,我们使用组件.propTypes来限制属性类型:
//第一种写法:在类外侧定义
MyCpn.propTypes = {
name:React.PropTypes.string
}
//第二种写法:在类内侧定义
class MyCpn extends React.Component{
static propTypes = {
name:React.PropTypes.string
}
//其余省略
}
在新版本中,我们需要引入对应的prop-types库:
npm install prop-types --save
import PropTypes from 'prop-types';
然后直接通过PropTypes对象来使用:
MyCpn.propTypes = {
name:PropTypes.string
}
如果传入的value不符合对应类型,控制台就会爆红
这里要注意:string和number都是小写,函数类型写为func
设置属性为必填项:name:PropTypes.string.isRequired
设置默认值:
//第一种写法:在类外侧定义
MyCpn.defaultProps = {
sex:'不明'
}
//第二种写法:在类内侧定义
class MyCpn extends React.Component{
static defaultProps = {
name:React.PropTypes.string
}
//其余省略
}
props属性是只读的
当你写了构造器时
在你写了构造器时,需要将props作为参数传入,并在构造器中调用super(),同时也要为super传入props参数,否则就会产生props属性丢失问题(通过this.props)无法获取属性
写法:
constructor(props){
super(props);
//...
}
如果你并不需要在构造器中通过this.props获取其属性,那么完全可以不用写
函数式组件中使用props
函数式组件中无法绑定属性,我们就要通过参数的形式进行传递:
function MyCpn(props){
//...
}
在调用组件时,传入的属性就会自动作为参数进行传递,在组件内通过props就可获取。
由于函数内不能使用static关键字,所以如果想要对props中的内容进行限制,只能使用在外侧的方式:
MyCpn.propTypes = {
name:PropTypes.string
}
⭐第三个属性:refs
想知道refs属性,先来看看ref属性,ref类似于id,可以写在标签中作为其唯一标识符:
<input ref='myInput' type='text'/>
这样ref就标识了该input标签
refs属性则是绑定在组件上的属性,为对象类型,它会为我们收集所有标注ref属性的标签,统一以refValue:DomEle的形式存在refs中:
注:refs为我们收集到的不是虚拟DOM,而是真实DOM
🌰回调refs
由于字符串类型的ref存在效率问题,如今已移除字符串作为其值的用法,取而代之的是用回调函数或createRef API的方式代替
回调,顾名思义,传入回调函数作为其属性值,最简单的例子:
<input ref={()=>{console.log('@');}} type='text'/>
这样在打开网页的一瞬间,控制台就会输出@符号
如果我们定义一个参数:
<input ref={a=>console.log(a);} type='text'/>
控制台输出:<input type='text'/>
可以看出,输出的便是回调所在本身的DOM
那么我们就可以这样操作:
<input ref={a=>this.input1 = a;} type='text'/>
这里的this指向和render中的指向相同,为react组件本身,这样就可以在组件上创建一个input1属性,并为其赋值为a,也就是该回调函数所在的DOM
这样我们在自定义函数中要取出input,就可以直接从this中取出。
回调refs的执行次数
如果你的ref回调函数是以内联函数地形式定义的,在render第一次执行时,回调函数会相对应地执行一次,而在更新过程中,会被执行两次,第一次传入参数null,然后第二次传入参数DOM元素,这是因为在每次渲染时会创建一个新的函数实例,所以React清空旧的ref并且设置新的,我们可以通过将ref的回调函数定义为class的绑定函数以避免这个问题。
🌰createRef的使用
React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点
也就是说,我们在类中定义:myRef = React.createRef()方法后,再去需要定义ref的标签中:<input ref={this.myRef} type='text'/>,这样该标签就可以存入我们创建的myRef容器中
让我们看看现在容器中的状态:
{current: input}
于是我们通过this.myRef.current就可以取到该input标签实例
但注意:该容器中只能存放一个标签,若想存放多个标签,则要创建多个容器,如果一个容器存放多个标签,那么后存放的就会覆盖掉之前存在的
此方式是目前react最推荐的refs使用形式
⭐事件处理
如果你有心观察,会发现React中的事件与原生js的事件是有些区别的:例如onclick与onClick
原生事件都是小写,而React中的事件on后的单词首字母需要大写
这是由于React为了更好的兼容性,使用的是自定义(合成)事件,而不是使用的原生DOM事件,React中的事件是通过事件委托的方式,委托给组件最外层元素进行处理的,最终再通过event.target得到发生事件的DOM元素对象
例如:
<div>
<button onClick={this.showData}>点我,你点我呀~</button>
</div>
👆这里的onClick就绑定到了外层的容器,div身上
👉当发生事件的元素正好是你要操作的元素,那么就可以直接使用事件绑定,而不是全部用refs
例如:
<input onBlur={this.showData} type="text" placeholder="失去焦点我会提示数据哦~"/>
对应方法:
showData = (event) => {
alert(event.target.value);//这里显示的是input本身的属性,即发生事件的元素正好是你要操作的元素
}
👆这样便可以防止我们过度使用refs