一、State状态
state状态只在类组件中才有,函数组件没有此功能
状态(state)即数据,是组件内部的私有数据,只能在当前组件内部使用
state为类组件的成员属性,其值是对象
通过this.state来获取状态,react中没有做数据代理和劫持
直接修改state属性中的数据,不具备响应式,而是要通过 this.setState方法来修改才具备修改数据的同步更新视图【diff比对】
注意:state它就和vue中的data配置选项功能是一样的,只不过它没有响应式
- 定义state数据
1.state它只能在类组件中使用
2.state它是类的一个成员属性,其值是一个对象
3.state它没有进行数据的劫持和代理,在方法中调用通过 this.state.xx来获取数据
4.state如果要修改里面的数据,必须通过this.setState方法来修改,否则视图不会更新
主动让视图更新的方法
1.setState
2.forceUpdate
3.新的props传入
4.主动调用render方法 – 很少用
import React, { Component } from 'react'
class App extends Component {
// 成员属性,此属性是一个特列属性,用来存储当前组件的私有数据
// 写法1 推荐
state = {
num: 100,
name: '张三'
}
// 写法2 不推荐
//constructor(props) {
// super(props)
// this.state = {
// num: 100,
// name: '张三'
// }
//}
render() {
// 解构写法,推荐
const { num, name } = this.state
return (
<div>
<h1>{name}</h1>
<h3>State状态 -- {num}</h3>
</div>
)
}
}
export default App
- state数据修改
同步修改和异步修改
1.同步修改 直接修改state属性中的值,它不会重新渲染视图 this.state.xx = xxx
2.异步修改 通过this.setState方法来修改,它会重新渲染视图
- this.setState它在react17及之前它是异步也是同步,react18之后它是异步的
- react17
- 如果你写在异步代码块中,它就是同步的
- 如果你写在原生事件中,它就是同步的 addEventListener(‘click’, () => {})
- react18中如果你就想要类似同步的效果,你可以用 flushSync方法来实现,flushSync方法已不被推荐使用,是一个即时过时的方法 类似于Vue中的$nextTick
setState语法
1.对象方式来修改
参数1:对象,要修改的state数据对象
参数2:回调函数中,获取最新的state数据 – 一般用的不多
this.setState({key名称就是state对象属性的key:value值就是你要修改的值}, callback)
2.函数方式来修改 – 官方推荐使用
参数1:函数,函数的返回值就是要修改的state数据对象,回调函数的形参就是当前的state数据
参数2:回调函数中,获取最新的state数据
this.setState(state=>({key:value}),callback)如果直接修改值,不去管原数据,可以用对象方式来修改
如果在原数据的基础上修改,可以用函数方式来修改
setState数据修改方式
- 同步修改,业务中对于数据更新,无须更新视图,建议用同步修改
this.state.num = Date.now()
console.log(this.state.num)- 不在原数据的基础上修改,直接修改值
this.setState({ num: Date.now() }, () => {
//获取最新的state修改后的数据
console.log(this.state.num)
})
this.setState({ num: this.state.num + 1 })- 马上就去执行更新操作 flusySync 不推荐使用
flushSync(() => {
this.setState({ num: this.state.num + 1 })
})
console.log(this.state.num) // 同步得到当前通过flushSync修改后的数据
flushSync(() => {
this.setState({ num: this.state.num + 2 })
})- 在原数据的基础上修改,用函数方式来修改
this.setState(state => ({
num: state.num + 1
}))
import React, { Component } from 'react'
import { flushSync } from 'react-dom'
class App extends Component {
state = {
num: 100
}
setNum = () => {
this.setState(state => ({
num: state.num + 1
}))
}
render() {
const { num } = this.state
return (
<div>
<h3>State状态 -- {num}</h3>
<button onClick={this.setNum}>修改Num数据</button>
</div>
)
}
}
export default App
上面是react18的state状态,下面介绍一下react17的状态
import React, { Component } from 'react'
class App extends Component {
state = {
num: 100
}
componentDidMount() {
// 非异步代码块或原生,this.setState为异步
// this.setState(state => ({
// num: state.num + 1
// }))
// console.log(this.state.num)
// this.setState写在原生事件中,它为同步
// document.querySelector('#btn').addEventListener('click', () => {
// this.setState(state => ({
// num: state.num + 1
// }))
// console.log(this.state.num)
// })
}
setNum = () => {
// 写在非异步代码块中this.setState为异步
// this.setState(state => ({
// num: state.num + 1
// }))
// 如果你写在异步任务中,则this.setState为同步
// setTimeout(() => {
// this.setState(state => ({
// num: state.num + 1
// }))
// console.log(this.state.num)
// }, 0)
let p = new Promise(resolve => {
resolve('')
})
p.then(() => {
debugger
this.setState(state => ({
num: state.num + 1
}))
console.log(this.state.num)
})
}
render() {
const { num } = this.state
return (
<div>
<h3>App组件 -- {num}</h3>
<button id='btn' onClick={this.setNum}>
+++++++
</button>
</div>
)
}
}
export default App
二、props进阶
2.1children属性
- 函数组件中使用children属性
children属性它的数据类型取决于父组件在双标签中传入的元素个数
如果调用子组件中,它的子元素只有一个,则返回对象[元素对象]
如果调用子组件中,它的子元组中多个,则返回数组,数组的元素为元素对象
import React from 'react'
const Child = ({ children = null }) => {
return (
<div>
<div>child组件</div>
<hr />
{children}
</div>
)
}
const App = () => {
return (
<div>
<h3>App组件</h3>
{/*
如果是双标签,里面还写了元素结构,则需要在子组件中通过 props中的children属性来获取对应数据
*/}
<Child>
<div>我是child标签里面的内容,有点类似于vue中的插槽</div>
</Child>
</div>
)
}
export default App
- 类组件张使用children属性
import React, { Component } from 'react'
class Child extends Component {
constructor(props) {
super(props);
const { children } = this.props;
}
render() {
return (
<div>
<div>child组件</div>
<hr />
{children}
</div>
)
}
}
class App extends Component {
render() {
return (
<div>
<h3>App组件</h3>
<Child>
<div>我是插槽</div>
</Child>
</div>
)
}
}
export default App
上面写的类似于默认插槽,下面实现具名插槽
import React, { Component } from 'react'
class Child extends Component {
render() {
const { children } = this.props;
const header = React.Children.toArray(children).find(item => item.props.name === 'header');
const content = React.Children.toArray(children).find(item => item.props.name === 'content');
const footer = React.Children.toArray(children).find(item => item.props.name === 'footer');
return (
<div>
{header}
<div>child组件</div>
{content}
{footer}
</div>
);
}
}
class App extends Component {
render() {
return (
<div>
<h3>App组件</h3>
<Child>
<div name='header'>导航顶部</div>
<div name='content'>我是插槽</div>
<div name='footer'>底部声明</div>
</Child>
</div>
)
}
}
export default App
上面实现的具名插槽,下面实现类似于作用域插槽
子组件的数据传递给了父组件
// cloneElement 把已有element元素给复制一个新的element元素,这样就可以进行操作了
import React, { Component, cloneElement } from 'react'
class Child extends Component {
state = {
num: 100
}
render() {
const element = cloneElement(this.props.children, {
onClick: () => {
// 如果this指向有问题,你要用apply/call
this.props.children.props.onClick(this.state.num)
}
})
return (
<div>
<div>child组件</div>
<br />
{element}
</div>
)
}
}
class App extends Component {
state = {
name: 'abc'
}
clickHandle = n => {
console.log('clickHandle', n, this.state.name)
}
render() {
return (
<div>
<h3>App组件</h3>
<Child>
<div onClick={this.clickHandle}>我是插槽</div>
</Child>
</div>
)
}
}
export default App
遍历插槽元素
如果只有一个插槽,就是对象,如果是多个插槽,就是数组,children.m
判断它是对象还是数组 Array.isArray ‘xx’ in xx instanceof
Children.map 此方法它会内部判断当前的this.props.children是否为数组
// cloneElement 把已有element元素给复制一个新的element元素,这样就可以进行操作了
import React, { Component, cloneElement, Children } from 'react'
class Child extends Component {
color = ['red', 'green', 'blue']
render() {
const ele = Children.map(this.props.children, (child, index) => {
return cloneElement(child, {
style: { color: this.color[index] }
})
})
return (
<div>
<div>child组件</div>
{ele}
</div>
)
}
}
class App extends Component {
render() {
return (
<div>
<h3>App组件</h3>
<Child>
<li>aaaa</li>
<li>bbb</li>
<li>ccc</li>
</Child>
</div>
)
}
}
export default App
2.2 类型限制
对于组件来说,props是外部传入的,无法保证组件使用者传入什么格式的数据,简单来说就是组件调用者可能不知道组件封装着需要什么样的数据,如果传入的数据不对,可能会导致程序异常,所以必须要对于props传入的数据类型进行校验。
安装包
npm i -S prop-types
对于父组件自定义的属性的值有要求,要符合指定的类型才能使用,在js环境中不符合只会有警告,还是能用
类型检查网址:https://zh-hans.legacy.reactjs.org/docs/typechecking-with-proptypes.html#proptypes
- 函数组件和类组件都可以使用的方式
import React, { Component } from 'react'
import types from 'prop-types'
//子组件
class Child extends Component {
}
//此方案函数组件和类组件都可以使用
Child.propTypes = {
}
//父组件
class App extends Component {
render() {
return (
<div></div>
)
}
}
- 类组件中使用的方式
types js数据类型 bool func node/element oneOf[枚举类型] shape指定对象类型 arrayOf指定数据元素类型 oneOfType一个属性可以是几种类型中的任意一个类型 any
import React, { Component } from 'react'
import types from 'prop-types'
class Child extends Component {
// 此方式只能在类组件中定义
static propTypes = {
// age字段必须要存在,且为数字类型
age: types.number.isRequired,
title: types.string,
// 此属性的值只能是1或2
sex: types.oneOf(['1', '2']),
// 属性可以是多个类型中的一个
msg: types.oneOfType([types.string, types.number]),
// 数组中元素类型为数字
// arr: types.arrayOf(types.number)
arr: types.arrayOf(types.oneOfType([types.number, types.string])),
// 对象类型
// obj: types.object
obj: types.shape({
id: types.number,
name: types.string
}),
users: types.arrayOf(
types.shape({
id: types.number,
name: types.string
})
),
// 自定义验证
phone: function (props, propName, componentName) {
// console.log(props, propName, componentName)
if (!/1[3-9]\d{9}$/.test(props[propName])) {
return new Error('phone属性值不合法')
}
},
header: types.node,
fn: types.func
}
render() {
const { age, header, fn } = this.props
return (
<div>
<div>子组件</div>
<div>{age}</div>
<hr />
{header}
<hr />
{fn()}
</div>
)
}
}
class App extends Component {
state = {
age: 10
}
fn = () => {
return this.state.age
}
render() {
return (
<div>
<h3>父组件</h3>
<hr />
{/* props可以接受任意类型的数据 */}
<Child
age={10}
title='aaa'
sex='1'
msg='aa'
arr={[1, 'a', 3]}
obj={{ id: 1, name: 'aaa' }}
users={[{ id: 1, name: 'aaa' }]}
phone='13352152152'
header={<h3>我是一个标题</h3>}
fn={this.fn}
/>
</div>
)
}
}
export default App
2.3props默认值
import React, { Component } from 'react'
import types from 'prop-types'
class Child extends Component {
static propTypes = {
age: types.number,
title: types.string
}
// 设置组件props的默认值 优先级高
static defaultProps = {
title: '默认值'
}
render() {
const { age, title } = this.props
return (
<div>
<div>子组件</div>
<div>{age}</div>
<div>{title}</div>
</div>
)
}
}
// 函数组件和类组件都支持的写法
// Child.defaultProps = {
// title: '默认值'
// }
class App extends Component {
state = {
age: 10
}
render() {
return (
<div>
<h3>父组件</h3>
<hr />
<Child age={10} />
<hr />
<Child age={20} title='abc' />
</div>
)
}
}
export default App
2.4 父子组件通信
第一种方式:
import React, { Component } from 'react'
import types from 'prop-types'
class Child extends Component {
static propTypes = {
num: types.number,
setNum: types.func
}
render() {
// props单向数据流,只能父修改,子不能修改
const { num, setNum } = this.props
return (
<div>
<div>我是child组件 -- {num}</div>
<button
onClick={() => {
// 子通过调用父传过来的修改方法来完成对父中的数据修改
setNum(Date.now())
}}
>
+++++子组件中去修改父组件中的num属性值++++
</button>
</div>
)
}
}
class App extends Component {
state = {
num: 100
}
setNum = num => {
this.setState({ num })
}
render() {
const { num } = this.state
return (
<div>
<h3>App组件 -- {this.state.num}</h3>
{/* 父通过prop向子组件传数据 */}
<Child num={num} setNum={this.setNum} />
</div>
)
}
}
export default App
第二种方式:
import React, { Component } from 'react'
import types from 'prop-types'
class Child extends Component {
render() {
// props单向数据流,只能父修改,子不能修改
const { value, setValue } = this.props
return (
<div>
<div>我是child组件 -- {value}</div>
<button
onClick={() => {
// 子通过调用父传过来的修改方法来完成对父中的数据修改
setValue(Date.now())
}}
>
+++++子组件中去修改父组件中的num属性值++++
</button>
</div>
)
}
}
const _stateDate = _this => {
return {
num: {
value: 500,
setValue: num => {
_this.setState(state => ({ num: { ...state.num, value: num } }))
}
}
}
}
class App extends Component {
state = {
..._stateDate(this),
title: 'aaa'
}
render() {
const { num } = this.state
return (
<div>
<h3>App组件 -- {this.state.num.value}</h3>
{/* 父通过prop向子组件传数据 */}
{/* <Child num={num} /> */}
{/* 扩展运算符 */}
<Child {...num} />
</div>
)
}
}
export default App
第一种写法很标准,但是第二种更聚合一些,那种都可以
三、补充
因为学习周期过长,我后面设置一个React学习的专栏,方便学习和管理笔记