003 React类组件
[A] 类组件的执行顺序及次数
1. 类组件渲染过程中从上往下执行,先执行constructor构造器,再执行render函数返回dom
2. constructor执行一次,即类组件首次渲染的时候执行
3. render函数执行 1+n 次,即首次渲染的时候执行一次,当页面数据更新后再执行一次
4. 类组件中的方法执行n次,即方法调用几次就执行几次
[B] 类组件的三大属性:state, props, refs
class Weather extends React.Component {
constructor(props) {
super(props)
this.state = { isHot: true}
}
render() {
return <div>今天天气{this.state.isHot ? '炎热' : '凉爽'}</div>
}
}
const dom = document.getElementById('box')
ReactDOM.render(<Weather/>, dom)
注:
1. state存储在类的构造器中,state必须为对象
state初始化:
即在类的构造器其中添加state属性
constructor(props) { super(props) this.state = { isHot: true} }
类组件事件添加:
js中添加事件的三种方式:
1. 获取原生节点,添加事件
const box = document.getElementById('box') box.onclick = () => { console.log('盒子被点击了') }
2. 获取原生节点,添加事件监听器 addEventListener
const box = document.getElementById('box') box.addEventListener('click', () => { console.log('盒子被点击了') })
<button onclick="btn()">按钮</button> function btn() { console.log('盒子被点击了') }
React中均可以使用三种方式添加事件,但推荐使用第三种
<button onClick={btn}>按钮</button> function btn() { console.log('盒子被点击了') }
而实际上,事件的方法通常写在类的原型方法上,即:
class Weather extends React.Component { constructor(props) { super(props) this.state = { isHot: true} } btn() { console.log(this.state.isHot) } render() { return <button onClick={this.btn}>按钮</button> } } const dom = document.getElementById('box') ReactDOM.render(<Weather/>, dom)
注:
1. 这里方法写在{}中表示内部包含的是一个js片段,而不是用""来调用的
2. 这里是onClick而不是onclick
3. React对事件名,属性名(onclick => onClick, class => className,... )等做了改写
4. 为什么改写? 后续讲
关于类方法中的this问题:
1. 在上述添加事件方式中
class Weather extends React.Component { constructor(props) { super(props) this.state = { isHot: true} } btn() { console.log(this.state.isHot) } render() { return <button onClick={this.btn}>按钮</button> } } ReactDOM.render(<Weather/>, document.getElementById('box'))
2. 这里的方法调用中,会报错,因为btn中的this指向undefined
3. 这是因为:
onClick={this.btn}是将btn方法在堆中的地址保存到了onClick上,当点击事件发生时,会直接调用堆中的这个方法,严格模式下,直接调用方法,this就是指向undefined的
4. 解决:
方法一:
在类属性中添加一个同名的方法,将类方法绑定类的this的方法赋值给这个同名方法
constructor(props) { super(props) this.state = { isHot: true} this.btn = this.btn.bind(this) }
方法二:
在添加绑定事件时,通过bind方法绑定this
<button onClick={this.btn.bind(this)}>按钮</button>
state状态的修改:
1. state状态的直接修改,可以实现数据修改,但react无法检测到数据变动
2. react中提供了专门修改state状态的API,即setState
3. setState是挂载在React.Component原型上的方法
4. setState方法的使用:
this.setState({ isHot: !this.state.isHot })
5. setState方法会修改传入的属性为对应的值,其他的属性不会丢失
类组件的精简写法:
原始写法:
class Weather extends React.Component { constructor(props) { super(props) this.state = { isHot: true} } btn() { console.log(this.state.isHot) } render() { return <button onClick={this.btn}>按钮</button> } } ReactDOM.render(<Weather/>, document.getElementById('box'))
精简写法:
class Weather extends React.Component { state = { isHot: true} btn = () => { console.log(this.state.isHot) } render() { return <button onClick={this.btn}>按钮</button> } } ReactDOM.render(<Weather/>, document.getElementById('box'))
注意:
1. 类中可以直接添加属性并赋值,这些直接添加的属性会添加到类的原型链上供其实例化的对象使用
(这和方法直接写在类中是一个原理)
2. 类中的方法也是直接写在类中的,为保证方法中的this使用正常,通常将方法携程箭头函数的形式
3. 当类组件不需要继承来的属性时,构造器也可以省略掉
[D] props
props的基本使用:props主要用于父子类组件传值时,子组件接受父组件传过来的数据
class Person extends React.Component { render() { const { name, age, sex } = this.props return ( <div> <div>姓名:{name}</div> <div>年龄:{age}</div> <div>性别:{sex}</div> </div> ) } } ReactDOM.render(<Person name="Carrey" age="20" sex="male"/>, document.getElementById('box'))
注意:
1. props属性的值获取有两种方式:
1. constructor构造器中获取
2. this对象自身挂在的props属性上获取
2. 当需要传给子组件的属性较多时,可进行批量传值
const info = {name: "Carrey" age: "20" sex: "male"}
<Person {...info}/>
3. 子组件中的props中获取到的属性值,是只读的,不可修改,强行修改会报错
props添加限制:
1. props用于父组件在调用子组件的时候给子组件传值,但有时我们需要限定有些属性必传,还要限制属性的数据类型
对属性的数据类型限制和必传限制,我们通过 propTypes 来实现:
先引入 propTypes 类:
<script src="../js/prop-types.js"></script>
在类组件中使用 propTypes 类:
class Person extends React.Component {} Person.propTypes = { name: PropTypes.string.isRequired, age: PropTypes.number.isRequired, sex: PropTypes.string, }
数据类型限制:
字符串:PropTypes.string
数值:PropTypes.number
函数:PropTypes.func
必传限制:
PropTypes.xxx.isRequired
注意:
上述调用 PropTypes 的方式在react15.5版本之后才能使用,即PropTypes被封装成单独的一个类使用
而在15.5版本以前,PropTypes是挂载在React对象上的,无需单独引入,即 PropTypes -> Reatc.PropTypes
2. 此外,我们有时候还需要给类组件的属性添加默认值,当父组件未传时,显示该默认值
默认值的添加,我们只需要将默认值添加在类组件对象的 defaultProps 即可:
class Person extends React.Component {} Person.defaultProps = { name: '佚名', age: 100, sex: '未知' }
props的简写:
1. 上述例子中,我们将一个类组件的定义分成了三部分:
类定义:
class Person extends React.Component {}
属性限制:
Person.propTypes = {}
指定默认值:
Person.defaultProps = {}
2. 而习惯上,我们希望将类组价的定义写在一个对象中,而不是零零散散的好几块,也就是说,上述 属性限制 和 指定默认值 两部分我们希望写在类定义的内部
即改写为:
class Person extends React.Component { static propTypes = { name: PropTypes.string.isRequired, age: PropTypes.number.isRequired, sex: PropTypes.string, } static defaultProps = { name: '佚名', age: 100, sex: '未知' } state = { msg: 'hello' } render() { const { name, age, sex } = this.props return ( <div> <div>姓名:{name}</div> <div>年龄:{age}</div> <div>性别:{sex}</div> </div> ) } }
var p = new Person() p.state // { msg: 'hello' } Person.state // undefined
var p = new Person() p.propTypes // undefined Person.propTypes // {...}
类中的构造器的省略与否的区别:
constructor(props) { super(props) this.state = { name: 'Carrey' } this.changeWeather = this.changeWeather.bind(this) }
constructor(props) { super(props) console.log(this.props) // 正常打印 }
constructor() { super() console.log(this.props) // undefined }
字符串形式的ref:
即给元素添加一个ref属性,其值为一个自定义的字符串
使用方法:
class Person extends React.Component { print = () => { const { ipt } = this.refs console.log(ipt.value) } render() { return ( <div> <input ref="ipt" type="text" /> <button onClick={this.print}>打印输入值</button> </div> ) } }
即模板中绑定ref属性的元素,在this.refs上可以直接获取
即给元素添加一个ref属性,其值为一个回调函数
使用方法:
class Person extends React.Component { print = () => { const { ipt } = this console.log(ipt.value) } render() { return ( <div> <input ref={ c => this.ipt = c} type="text" /> <button onClick={this.print}>打印输入值</button> </div> ) } }
即给ref绑定的回调在render渲染的时候遇到ref属性,会将其回调执行一遍,并将当前的标签元素作为参数传入
关于回调函数执行次数的问题:
1. ref绑定的回到函数会执行 1 + 2n 次
2. 首次渲染的时候会执行一次,然后在每次数据更新的时候执行两遍,第一遍传入null,第二遍传入标签元素
解决数据更新时,ref回调执行两遍的问题:
1. 上述执行两遍是因为,回到函数直接写成行内属性的形式
2. ref回调函数也可以绑定类组件的一个实例方法,这样数据更新时,不会再调用该回调了(零调用)
createRef形式:
使用方法:
class Person extends React.Component { myRef = React.createRef() print = () => { console.log(this.myRef.current.value) } render() { return ( <div> <input ref={ this.myRef } type="text" /> <button onClick={this.print}>打印输入值</button> </div> ) } }
执行过程:
1. 首先需要通过 React.createRef 方法去创建一个专门用于保存元素标签的ref容器
2. 将该容器绑定在需要标识的标签的ref属性上
3. 当render渲染的时候,遇到ref属性,并且发现绑定的是一个ref容器,就会将当前标签存储到这个容器中去