react-4 react 组件的三大属性

在react组件实例对象当中存在三个属性, 是我们在创建组件过程中经常使用到的,分别是控制组件
更新的state,负责传值的props以及用于存储当前节点的refs

  • props (负责传值)
  • state (控制组件更新状态)
  • refs 存储当前节点(能不用就不用,表单中常用

state状态

简介

组件免不了要与用户互动,React的一大创新,就是将组件看成是一个状态机,开始有一个初始状
态,然后用户互动,导致状态变化,从而触发重新渲染UI

React 里,只需更新组件的 state,然后根据新的 state重新渲染用户界面 (不要操作DOM).

简单来说,react就是控制state里数据的更新来驱动ui的渲染

那么问题来了:

(1)状态在哪里?

(2)如何初始化状态?

(3)如何修改状态?

状态在哪里?

State是在组件实例对象中的,组件实例对象是由类生成的,所以只有在类式组件中才具有stale

如何初始化?

state是组件实例对象继承了react.component,所以要在当前组件的构造器函数中进行初始化,默认
值为null。

注:state可以在构造器中初始化,this.state={},也可以使用简写形式,在构造器外初始化,直接state={}

如何修改状态?

常用的通知react数据变化的方法是调用setSate(data cllbalk),这个方法会合并data到this.state,并
重新渲染组件,渲染完成后,调用可选的callback回调, 大部分情况下不需要提供allback,因为
react会负责把界面更新到最先状态。

注意: setState()方法是个异步方法

this.setState():

  • 组件状态改变时,可以通过this.setState修改状态

  • setState方法支持按需修改,如state有两个字段,仅当setState传入的对象包含字段key
    才会修改属性

  • 每次调用setState会导致重渲染调用render方法

  • 直接修改state不会重渲染组件

请求的数据都存储在state中。

严重注意:状态不可直接更改,需要使用setState,否则react根本不嘞你。

state 工作原理

mark

这是一个React组件实现组件可交互所需的流程,render()输出虚拟DOM, 虚拟DOM转为DOM,再在DOM上注册事件事件触发setState()修改数据在每次调用setState方法时,React会自动执行render方法来更新虚拟DOM,如果组件已经被渲染,那么还会更新到DOM中去。

props

简介

Props相对于组件来说是外来属性, 使用props可以相件外部向组件内部传值,类似与函数的传
参,他经常用于组件之间的传值,一种父级向子级传递数据的方式

props是只读的。

语法

直接在组件中添加属性即可,添加完毕后,传入的属性被保存到组件实例化对象的props属性中。

 <TestProps me={this.state.name} hobby="swim"></TestProps>  

上面的例子,想组件传入了两个属性:(1)从父组件传过来的state.name (2)自定义的属性hobby

这是运行项目,查看一下<TestProps/>组件的props值。

mark

接收到属性后,便可以使用this.props.属性名来进行相应处理。

props支持批量化的传递标签属性,例如:

const p={name:'wind',age:24}
ReactDom.render(<Person {...p} />,document.getElementById("root"))

在组件内,可以搭配解构赋值,提高开发效率。

扩展 类组件的构造器 constructor()

开门见山:类组件的constructor完全可以不写(开发中一般不写)

那么什么后需要写呢?

下面是官方文档的解释:

在 React 组件挂载之前,会调用它的构造函数。在为 React.Component 子类实现构造函数时,应在其他语句之前前调用 super(props)。否则,this.props 在构造函数中可能会出现未定义的 bug。

通常,在 React 中,构造函数仅用于以下两种情况:

上面文档说明,constructor可以不写,因为state的设置可以写在构造器外面(简写形式),事件绑定可以使用箭头函数。

但是如果写了constructor,则要传入props参数,否则在构造器中操作this.props时会报错。

所以说,一言以蔽之:constructor写不写,完全取决于你要不要在构造器中操作props属性。

组件间的传值

父组件向子组件传值

直接使用props传参就可以。

这里讨论两种情况:

父组件 子组件
类组件 类组件
类组件 函数组件

组件间的传值分为两步走:1、父组件传值 2、子组件接收值

传值时,方法都一样,都是利用props属性进行传值。

<Son myname={this.state.name} sex={"man"} fun={this.transformData} > </Son>  //类组件 (子组件)

<SonFun x={this.state.name}></SonFun>   //函数组件  (子组件)

不过若子组件分别为类组件和函数组件时,他们接收值的方式有些不同。

(1)类组件直接使用this.props.属性名即可读取

(2)函数组件则需将props作为形参接收,然后再使用props.属性名读取

function SonFun(props){
    return(
        <li>
            hello,我是摇摆洋{props.x}     {/* 读取穿过类的值*/}
        </li>
    )
}

子组件向父组件传值

通过回调函数传值。

先在父组件中定义一个函数,用于接收子组件传过来的数据。然后使用props将该函数传递到子组件中,最后在子组件中调用该函数,同时传参。

兄弟组件间的传值

兄弟间的传值,相当于上面两者的结合,一个儿子Son1先将数据传给Father,再由Father传递给Son2

综合示例

演示兄弟组件间的传值

组件结构:

Father---父组件

Son1----子组件

Son2----子组件

Son1和Son2互为兄弟组件

Father.js

import React from "react"

import Son1 from "../son1/son1"
import Son2 from "../son2/son2"

class FatherSpecial extends React.Component{

    constructor(){
        super()
        this.state={hobby:'swim'}  // 父组件自身的状态
    }
    render(){
        return(
            <ul>

                <li>我是wind-zhou</li>
                <Son1 fun={this.transformData}> </Son1>  {/* (2)将父类定义的函数,通过props转递给子组件*/}
                <Son2  hobby={this.state.hobby}> </Son2>     {/* (3)将值从父组件的state传递给子组件Son2的props*/}
            </ul>
        )
    }


    transformData=(data)=>{ // (1)父组件定义的函数
        this.setState({hobby:data})
    }
}

export default FatherSpecial

Son1.js

import React from "react"

class Son1 extends React.Component {
    constructor() {
        super()

        this.state={hobby:'吃面条'}
    }

    render(){
        return(
            <li>
                我是吕泽准
                <button onClick={this.giveData}>吕泽准献hobby</button>    {/* (2)子组件中调用封装后的方法*/}
            </li>
        )
    }

    giveData=()=>{     // (1)将父组件传过来的函数进行一封封装,封装成自己的方法
        this.props.fun(this.state.hobby)   //要注意这里传入的数据
    }
}

export default Son1

Son2.js

import React from "react"

class Son2 extends React.Component {
    constructor() {
        super()
    }

    render(){
        return(
            <li>
                我是 摇摆羊<br/> 
                {this.props.hobby}       {/* 接收传递过来的参数*/}
            </li>
        )
    }

}

export default Son2

最开始时,三个组件的状态。

mark

点击完“吕泽准献hobby”后,父类的hobby应该变为“吃面条”,son2的hobby也应该变为“吃面条”。

下面我们来看一下。

mark

上面的箭头就是此时传值的数据流。

几点总结:

  • 传值时会用到props属性
  • 子组件向父组件传值时的回调函数:这个函数其实是父组件的方法,父组件在定义时使用的是箭头函数定义,这样改函数内的this就指向了父组件的实力,这是最关键的地方,函数里写的是将数据写进父组件的state中,这样,即使将函数通过props转递给子组件,在子组件中调用时,操作的也是父组件的state,只不过数据使子组件的数据。
  • 在子组件接收到穿过来的回调函数后,一般会再次将其的封装为自己的方法,然后再在自己的内部元素上进行绑定

refs

简介

常规的React数据流中,props 是父组件与子组件交互的唯一方式。 要修改子元素,你需要用新的props去重新渲染子元素。然而,在少数情况下,你需要在常规数据流外强制修改子元素。被修改的子元素可以是React组件实例,或者是一个DOM元素。在这种情况下,React 提供了解决办法:使用Refs

一般在表单中使用。

refs使用有三种形式:

  • 字符串形式的ref
  • 回调函数形式的ref
  • createRef()创建的ref (常用)

何时使用refs:

  • 处理focus、 文本选择或者媒体播放
  • 触发强制动画
  • 集成第三方DOM库

注意不要过度使用refs,你可能首先会想到在你的应用程序中使用refs来更新组件。如果是这种情况,请花一点时间,更多的关注在组件层中使用state。在组件层中,通常较高级别的state更为清晰。

字符串形式的ref

React支持一个特殊的属性,你可以将这个属性加在任何通过render()返回的组件中。这也就是说对render()返回的组件进行一个标记,可以方便的定位的这个组件实例。这就是ref的作用。

语法:

<input ref="myinput">

访问这个实例,可以通过this.refs来访问。

this.ref.myinput

注意:字符串形式的ref已经不推荐使用了,原因是效率问题。

回调函数形式的ref (常用)

ref属性的值除了是一个字符串,还可以是一个函数, 这个函数是一个回调函数在组件初始化或更新时由react调用,这个回调函数会得到一个参数, 即当前的节点对象,我们可以将得到的节点对象挂在到组件的实例对象上,在需要的地方进行调用

语法:

    <input ref={(element)=>{this.mailRef=element}} type="text" />

访问这个实例,可以通过this.mailRef来访问。

就是将其挂载到实例化对象上的一个自定义属性上。

注意:关于回调 refs 的说明

如果 ref 回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数 null,然后第二次会传入参数 DOM 元素。这是因为在每次渲染时会创建一个新的函数实例(之前的被释放掉了),所以 React 清空旧的 ref 并且设置新的。通过将 ref 的回调函数定义成 class 的绑定函数的方式可以避免上述问题,但是大多数情况下它是无关紧要的。

什么是类的绑定函数方式?

constructor(props) {
super(props);

this.textInput = null;

this.setTextInputRef = element => {
this.textInput = element;
};
}

<input
    type="button"
    value="Focus the text input"
    onClick={this.focusTextInput}
 />

creatRef() (官方推荐常用)

创建16.3版本中Res通过React.crlae()的方法创建,通过ref属性关联

React.createRef()可以返回一个容器,容器里可以存储当前ref被标识的节点。,但该容器只能存一个。

语法:

(1)现在constructor函数中创建refs节点

this.userRef = React.createRef();
this.pwdRef = React.createRef();

一个属性只能存储一个节点,因此上面存储两个input,便创建了两个。

(2)在节点中通过ref关联

   <p>
          姓名:
          <input ref={this.userRef} type="text" />
   </p>
   <p>
          密码 <input ref={this.pwdRef} type="password" />
  </p>

(3)访问

必须通过current才能获取节点

综合实例

下面通过第三种不同的方法学设置ref,并进行访问输出

import React from "react";

class TestRef extends React.Component {
  constructor(props) {
    super(props);

    this.userRef = React.createRef();
    this.pwdRef = React.createRef();
  }

  render() {
    return (
      <div>
        <p>
            {/* 字符串形式的ref */}
          电话:
          <input ref="telephone" type="text" />
        </p>
        <p>
            {/* crrateRef()方法创建ref */}
          姓名:
          <input ref={this.userRef} type="text" />
        </p>
        <p>
             {/* crrateRef()方法创建ref */}
          密码 <input ref={this.pwdRef} type="password" />
        </p>
        <p>
             {/* 回调函数形式ref */}

          邮箱:
          <input ref={element => {this.mailRef = element}} type="text"/>
        </p>
        {/* 绑定事件,访问输出 */}
        <button onClick={this.showData}>登录</button>
      </div>
    );
  }

  showData = () => {
    console.log(this);
    let user = this.userRef.current;
    let pwd = this.pwdRef.current; //crateRef
    let tele = this.refs.telephone;  //字符串形式
    let mail = this.mailRef;  //回调函数形式

    console.log(user.value, pwd.value, tele.value, mail.value);
  };
}

export default TestRef;

mark

我们再看一下此时的组件实例化对象:

mark

可以看到,几个节点被存储到了几个不同的属性上。

总结:

  • 三种方法都可以创建ref,不过新版本中推荐使用createRef()来创建ref

  • createRef()来创建ref,语法上更严谨,结构上更完整。

  • 一个ref上只能存一个节点。

  • ref不仅可以加载节点身上,还能加载组件身上。例如将子组件的ref加载父组件身上。

    因此可以访问到子组件的数据

react中的事件处理

  1. 通过onXxx属性指定事件处理函数(注意大小写)

    (1) React使用的是自定义(合成)事件, 而不是使用的原生DOM事件 ----更兼容

    (2) React中的事件是通过事件委托方式处理的(委托给组件最外层的元素)----为了高效

  2. 通过event.target得到发生事件的DOM元素对象

posted @ 2021-05-24 21:35  wind-zhou  Views(427)  Comments(0Edit  收藏  举报