文章目录
前言
这次来讲讲属于类式复杂组件的三大属性(函数式的后面hooks里会讲)
state
就是vue中的data()。
import React, { Component } from 'react'
export default class App extends Component {
constructor() {
super()
this.state = { // 初始化状态属性
str: '我的',
num: 1
}
}
change = () => {
this.setState({ str: '你的' }) // 状态必须通过setState进行更新,且更新是一种合并,不是替换。
// this.state.str = '你的' 是错误的写法,虽然值能被修改但是并不会渲染
}
render() {
let { str, num } = this.state // 读取状态
return (
<div onClick={this.change}>点击切换 <span>{str}-{num}</span></div>
)
}
}
我们可以看到核心的修改state方法是react对象上的setState()
方法,每次且修改都会自动渲染组件,就会重新执行render。
网上说,状态不是固定叫state,而是约定俗成,可以改成别的。但是我自己试了下改成其他的名称就没用了,还是需要写成state。
setState注意事项
这里列举一些注意事项
存储形式为对象
是个key-value的存储形式
this.state = { name: 'xiaoming' }
key的问题
如果setState的属性是一个变量,需要加上[]符号:
this.state = { name: 'xiaoming' }
fn = (key) => {
return (event)=>{
this.setState([key]: event.target.value) // 需要以[xxx]的形式去写key值
}
}
修改是合并不是重新赋值
前面注释里也提到了,例如上面的num变量在setState的时候没有写,但是state里还存在。
可以不写在constructor里面
直接写外面也可以:
export default class App extends Component {
state = {
str: '我的',
num: 1
}
change = () => {
this.setState({ str: '你的' })
}
render() {
let { str, num } = this.state // 读取状态
return (
<div onClick={this.change}>点击切换 <span>{str}-{num}</span></div>
)
}
}
是个异步操作(重要)
changeState = () => {
this.setState({ a: '2' }) // 异步操作,进入微任务,先执行下面的
console.log('state', this.state); // 打印出旧值
}
所以setState提供了第二个参数用于回调
changeState = () => {
this.setState({ a: '2' }, () => { console.log('state', this.state) })
}
是个语法糖
上面举例的写法叫做对象式写法,其实是函数式写法的语法糖
changeState = () => {
this.setState((state, props)=>{
// 这里可以拿到state, props
return { a: '2' } // 最后必须返回出去
}, () => { console.log('state', this.state) })
}
两种写法可以看具体的需求,没有固定死的规定。
不要修改原数据
具体可看我这个博客。
props
就是vue中的props,直接看如何使用
子组件中:
import React, { Component } from 'react'
export default class Small extends Component {
render() {
const { name, age } = this.props; // 这样直接拿到父组件的值,且是只读的,不可修改
return (
<div>名字{name}-年龄{age}</div>
)
}
}
父组件中:
import React, { Component } from 'react'
import Small from "./Small"
export default class App extends Component {
render() {
return (
<div>
<Small name="xiaoming" age="18"></Small>
</div>
)
}
}
父组件还可以直接展开的形式传入:
export default class App extends Component {
render() {
let info = { name: 'xiaoming', age: 18 }
return (
<div>
<Small {...info}></Small>
<Small {...info} name="222"></Small> // 后者会覆盖前者
</div>
)
}
}
注意:按照jsx规则,为什么不是
<Small {{...info}}></Small>
,是因为react给我们提供的写法,可以直接...obj
去展开一个对象,所以是<Small {...info}></Small>
。
如何限制
可以给props里的属性进行类型限制和是否必输入,首先你要有对应的库:
npm install prop-types
然后每个组件:
import React, { Component } from 'react'
import PropTypes from 'prop-types' // 记得要引入这个库
export default class Small extends Component {
render() {
const { name, age } = this.props;
return (
<div>名字{name}-年龄{age}</div>
)
}
}
// 对标签属性进行类型、必要性的限制
Small.propTypes = { // 这个属性react会捕捉到,然后下面的PropTypes是个库对象,帮我们处理限制的
name: PropTypes.string.isRequired, // 限制必传,且为字符串
age: PropTypes.number,// 限制为数值
speak: PropTypes.func,// 限制为函数
}
//指定默认标签属性值
Small.defaultProps = {
age: 18
}
可以看到其实就是给类本身加上两个属性propTypes
和defaultProps
对其进行限制,那么其实可以用es6语法的static
进行简写:
import React, { Component } from 'react'
import PropTypes from 'prop-types'
export default class Small extends Component {
// 对标签属性进行类型、必要性的限制
static propTypes = { // 这个属性react会捕捉到,然后下面的PropTypes是个库对象,帮我们处理限制的
name: PropTypes.string.isRequired, // 限制必传,且为字符串
age: PropTypes.number,// 限制为数值
speak: PropTypes.func,// 限制为函数
}
//指定默认标签属性值
static defaultProps = {
age: 18
}
render() {
const { name, age } = this.props;
return (
<div>名字{name}-年龄{age}</div>
)
}
}
补充
- 其实子要是标签中的属性都可以被带到props中,比如className。
- 如果在闭合标签中里写内容,那么props会自动生成一个children属性把内容放进去。
refs
这个属性vue的老熟人了,获取dom实例。
首先看最新的写法是React.createRef()
,官方也推荐这种写法:
export default class App extends Component {
smallRef = React.createRef() // 通过createRef创建容器
showDom = () => {
console.log("dom", this.smallRef.current);
}
render() {
return (
<div>
<div onClick={this.showDom}>获取</div>
<Small ref={this.smallRef}></Small>
</div>
)
}
}
接下来了一下,不太推荐的两种形式,用ref自动触发的回调:
export default class App extends Component {
showDom = () => {
let { smallRef } = this // 然后在函数中直接解构获取
console.log("dom", smallRef);
}
render() {
return (
<div>
<div onClick={this.showDom}>获取</div>
<Small ref={dom => this.smallRef = dom}></Small> // 在渲染函数render中,ref自动将dom对象作为箭头函数传入,我们直接用this接收
</div>
)
}
}
这种写法会出现会有这样一种情况,因为dom会随着数据的更新而重新渲染,每一次的新渲染react都会先以null为参数,再传入dom对象,以保证每次渲染后dom对象都是最新的。
如果不想每次渲染都触发,可以以绑定函数的形式写回调:
showDom = (dom)=>{
this.smallRef = dom;
}
<input ref={ this.smallRef } type="text"/><br/><br/>
后两种形式其实对性能没啥影响,但是还是推荐使用第一种。
如果有能实现代替refs的方式作出同样的效果,那么就尽量不要使用refs