State状态-props进阶

一、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学习的专栏,方便学习和管理笔记

猜你喜欢

转载自blog.csdn.net/wsq_yyds/article/details/134756363