组件的数据挂载方式有2种props属性、state状态。props用于描述特征和性质,组件自身不能随意更改。state用于描述状态的改变,在不同的状态下使组件的显示不同。
不轻易改变的数据使用props定义,如:height/color
变化的数据使用state定义,如:计数 、开关

属性(props)

- 由外部传入的 - 父组件传递给子组件(子组件通过this.props访问)

在父组件中,通过属性绑定的形式绑定数据给Child组件

  1. // src/Child.js
  2. import {Component} from 'react'
  3. export default class Child extends Component{
  4. render(){
  5. console.log(this.props) // {content: "内容", name: "syukinmei", age: 18}
  6. return(
  7. <>
  8. <p>{this.props.content}</p>
  9. <p>{this.props.name}</p>
  10. </>
  11. )
  12. }
  13. }
  14. // src/App.js
  15. import {Component} from 'react'
  16. import Child from './Child'
  17. export default class App extends Component{
  18. render(){
  19. return(
  20. <div>
  21. React
  22. <Child content='内容' name="syukinmei" age={18}/>
  23. </div>
  24. )
  25. }
  26. }

当子组件是函数组件时

  1. // src/Child.js
  2. const Child = (props) => {
  3. console.log(props) // {content: "内容", name: "syukinmei", age: 18}
  4. return (
  5. <>
  6. <p>{props.content}</p>
  7. <p>{props.name}</p>
  8. </>
  9. )
  10. }
  11. export default Child

总结:
在使用一个组件的时候,可以把参数放在标签的属性当中,所有的属性都会作为组件 props 对象的键值。通过箭头函数创建的组件,需要通过函数的参数来接收props

  1. 类组件自身定义数据 - 组件的默认props

类组件通过static defaultProps={}关键字 static关键字只有在class类中有所有只能运用于类组件

  1. import {Component} from 'react'
  2. export default class App extends Component{
  3. render(){
  4. return(
  5. <div>
  6. React
  7. <Child content='内容' name="syukinmei" age={18} title='我有标题'/>
  8. </div>
  9. )
  10. }
  11. }
  12. class Child extends Component{
  13. static defaultProps={
  14. title:'默认标题'
  15. }
  16. render(){
  17. console.log(this.props) // {content: "内容", name: "syukinmei", age: 18, title: "我有标题"}
  18. return(
  19. <>
  20. <h1>{this.props.title}</h1>
  21. </>
  22. )
  23. }
  24. }
  25. // 写法2 将Line14~16移出来写
  26. // 因为静态属性可以直接通过类访问或者设置
  27. Child.defaultProps={
  28. title:'默认标题'
  29. }

由于设置了defaultProps,如果Line7中没有绑定title属性则Line18打印

{content: “内容”, name: “syukinmei”, age: 18, title: “默认标题”}

渲染到页面的将会是 默认标题

属性验证

react是为了构建大型应用程序而生的,在一个大型应用中,根本不知道别人使用你写的组件的时候会传入什么样的参数,有可能造成应用程序运行不了,但是不报错。为了解决这个问题,React提供了一种机制,让写组件的人可以给组件的 props 设定参数检测,需要安装和使用 prop-types 第三方插件

$ yarn add prop-types -S $ cnpm i prop-types -S

  1. import Types from 'prop-types'
  2. console.log('types',Types) // Types是一个对象
  3. export default class Child extends Component{
  4. render(){
  5. // ... do things with the props
  6. }
  7. }
  8. Child.propTypes={
  9. content:Types.string, // 验证类型
  10. name:Types.string,
  11. age:Types.number,
  12. title:Types.string,
  13. customProp(props,propName,componentName){
  14. console.log(props) // 所有属性 {content: "内容", name: "syukinmei", age: 18, title: "默认标题"}
  15. console.log(propName) // customProp
  16. console.log(componentName) // Child
  17. if(props.age<18){
  18. alert('未成年')
  19. }
  20. }
  21. }

状态(state)

state定义方法有两种:构造函数中定义和class中直接定义

  1. import { Component } from 'react'
  2. export default class App extends Component {
  3. // 构造函数中定义 (推荐)
  4. constructor() {
  5. super()
  6. this.state = {
  7. name: 'syukinmei'
  8. }
  9. }
  10. // class中直接定义
  11. state = {
  12. name: 'syukinmei'
  13. }
  14. render() {
  15. console.log(this)
  16. const {name}=this.state // 使用ES6中的解构
  17. return (
  18. <div>
  19. {this.state.name}<br/>
  20. {name} // 使用ES6中的解构
  21. </div>
  22. )
  23. }
  24. }

setState

this.propsthis.state是纯js对象,在vue中,data属性是利用Object.defineProperty处理过的,更改data的数据的时候会触发数据的gettersetter,但是React中没有做这样的处理,如果直接更改的话,react是无法得知的,所以,需要使用特殊的更改状态的方法setState

  1. import { Component } from 'react'
  2. export default class App extends Component {
  3. constructor() {
  4. super()
  5. this.state = {
  6. Flag: true
  7. }
  8. }
  9. showName = () => {
  10. this.setState({
  11. Flag: !this.state.Flag
  12. })
  13. }
  14. render() {
  15. const { Flag } = this.state
  16. console.log(Flag)
  17. return (
  18. <div>
  19. <button onClick={this.showName}>changeName</button>
  20. {Flag ? <p>syukinmei</p> : <p>ebiebi</p>}
  21. </div>
  22. )
  23. }
  24. }

setState有两个参数

第一个参数可以是对象,也可以是方法return一个对象,我们把这个参数叫做updater

  • 参数是对象

    1. this.setState({
    2. Flag: !this.state.Flag
    3. })
  • 参数是方法

需要使用上一个状态(原值)时使用
注意:这个方法接受两个参数,第一个参数用于记录上一个状态(state),第二个参数是props

  1. this.setState((prevState)=>{
  2. console.log(prevState) // {Flag: true, num: 1}
  3. return {
  4. Flag: !prevState.Flag
  5. }
  6. })

第二个参数是一个可选的回调函数
setState是异步的,要想获取到最新的state就需要使用第二个参数

  1. showName = () => {
  2. this.setState((prevState,props)=>{
  3. console.log(1,prevState)
  4. return {
  5. Flag: !prevState.Flag
  6. }
  7. },()=>{
  8. console.log('2',this.state.Flag)
  9. })
  10. console.log('3',this.state.Flag)
  11. }

事件触发时的打印结果
image.png
tips:setState会导致组件重新渲染在2之前会执行render()
合成事件中是异步,原生事件中是同步

  1. componentDidMount(){
  2. const btn =document.querySelector('.btn')
  3. const _this=this
  4. btn.onclick=function(){
  5. console.log('原生事件',this) // this指向事件源btn而非组件实例 所以要加Line3操作
  6. _this.setState({
  7. title:'syukinmei'
  8. })
  9. document.title=_this.state.title
  10. }
  11. }

setState内置优化行为

我们使用一个for循环模拟1000万次简单的setState操作
setState —> data change —>虚拟DOM重新生成 —>diff算法比对新旧虚拟DOM得到patch补丁对象,然后在重新将补丁对象渲染为真实DOM
如果短时间进行大量的setState操作React不会立即执行虚拟DOM生成,而是设置一个计划时间,在这个计划时间内所有的setState操作全部会放入一个队列中统一进行处理 合并setState ,使得计划时间内只进行了一次虚拟DOM生成 链接
总结:React对setState已经进行了合并优化处理但是还不够性能损耗也不小,要减少setState操作如下例Line14。

  1. add = () => {
  2. for (let i = 0; i <= 10000000; i++) {
  3. this.setState({
  4. count: i
  5. })
  6. }
  7. }
  8. render(){
  9. console.log('渲染') //开始执行一次 setState操作导致执行一次
  10. return(...)
  11. }
  12. // 优化
  13. add = () => {
  14. let n=0
  15. for (let i = 0; i <= 10000000; i++) {
  16. n=i
  17. }
  18. this.setState({
  19. count: n
  20. })
  21. }

render函数中不能直接调用setState,会导致栈溢出死循环报错,这是因为 render函数本身是用于解析this.state和this.props的,解析时在修改就会矛盾

属性VS状态