React---三大属性state&props&refs、事件处理、非受控组件&受控组件、高阶函数&函数柯里化

206 阅读8分钟

state

setState更新状态的两种写法

(1). setState(stateChange, [callback])------对象式的setState
           1.stateChange为状态改变对象(该对象可以体现出状态的更改)
           2.callback是可选的回调函数, 它在状态更新完毕、界面也更新后(render调用后)才被调用
				
(2). setState(updater, [callback])------函数式的setState
           1.updater为返回stateChange对象的函数。 
           2.updater可以接收到state和props。 //(state,props) => stateChange
           4.callback是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用。
总结:
	1.对象式的setState是函数式的setState的简写方式(语法糖)
	2.使用原则:
            (1).如果新状态不依赖于原状态 ===> 使用对象方式
            (2).如果新状态依赖于原状态 ===> 使用函数方式
            (3).如果需要在setState()执行后获取最新的状态数据, 
                    要在第二个callback函数中读取

对象式的setState

import React from 'react';
import ReactDOM from 'react-dom';

class App extends React.Component {
 state = { message: '努力加油' }
 
 //后面的回调函数可以省略
handleChange = () => {

    this.setState({ message: '向前奋斗' }, () => {
          console.log(this.state.message);
    })
    
}

 render() {
   return (
     <div>
       <h1>{this.state.message}</h1>
       <button onClick={this.handleChange}>切换</button>
     </div>
   );
 }
}
ReactDOM.render(
 <App />,
 document.getElementById('root')
);

函数式的setState

import React from 'react';
import ReactDOM from 'react-dom';

class App extends React.Component {
  state = { message: '努力加油' }

//能接收两个参数,state和props
  handleChange = () => {
  
    this.setState((state, props) => ({
      message: `${state}向前奋斗`
    }), () => {
      console.log(this.state.message);
    })
    
  }

  render() {
    return (
      <div>
        <h1>{this.state.message}</h1>
        <button onClick={this.handleChange}>切换</button>
      </div>
    );
  }
}
ReactDOM.render(
  <App />,
  document.getElementById('root')
);

立即获取最新的值

setSate是异步操作,当我们通过某个点击事件修改当前state中的值并打印(使用)它时,会发现当前的值并没有改变。 例如:

import React from 'react';
import ReactDOM from 'react-dom';

class App extends React.Component {
  state = { message: '努力加油' }

  handleChange = () => {
    this.setState({ message: '向前奋斗' })
    console.log(this.state.message);
  }

  render() {
    return (
      <div>
        <h1>{this.state.message}</h1>
        <button onClick={this.handleChange}>切换</button>
      </div>
    );
  }
}
ReactDOM.render(
  <App />,
  document.getElementById('root')
);

在这里插入图片描述 在这里插入图片描述

这样页面虽然改变,但是值却没有改变。但是我们由需要获取最新的值要怎么办呢?

第一种方法:

使用setState中的第二个参数(回调函数),这个回调函数会在修改了state值之后才会执行,这样就可以使用修改了之后的state值。

import React from 'react';
import ReactDOM from 'react-dom';


class App extends React.Component {
  state = { message: '努力加油' }

  handleChange = () => {
  
    this.setState({ message: '向前奋斗' },() => {
        console.log(this.state.message);
      })
      
  }

  render() {
    return (
      <div>
        <h1>{this.state.message}</h1>
        <button onClick={this.handleChange}>切换</button>
      </div>
    );
  }
}
ReactDOM.render(
  <App />,
  document.getElementById('root')
);

第二种方法:

使用async/await

import React from 'react';
import ReactDOM from 'react-dom';


class App extends React.Component {
  state = { message: '努力加油' }


  async handleChange() {
    await this.setState({ message: '向前奋斗' })
    console.log(this.state.message);
  }
  

  render() {
    return (
      <div>
        <h1>{this.state.message}</h1>
        <button onClick={() => this.handleChange()}>切换</button>
      </div>
    );
  }
}


ReactDOM.render(
  <App />,
  document.getElementById('root')
);

第三种方法:

使用函数式写法,它会接收一个回调函数,这个回调函数里面有两个参数,一个是更新前的值,一个是更新后的值。

import React from 'react';
import ReactDOM from 'react-dom';


class App extends React.Component {
  state = { message: '努力加油' }

  handleChange() {
  
    this.setState((state, props) => {
      return {
        message: '向前奋斗'
      }
    }, () => {
      console.log(this.state.message);
    })
    
  }

  render() {
    return (
      <div>
        <h1>{this.state.message}</h1>
        <button onClick={() => this.handleChange()}>切换</button>
      </div>
    );
  }
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
);

在这里插入图片描述 在这里插入图片描述

同时也可以拿到之前的值,进行操作

 handleChange() {
    this.setState((state, props) => {
      return {
        message: `${state.message}  向前奋斗`
      }
    }, () => {
      console.log(this.state.message);
    })
  }

在这里插入图片描述 在这里插入图片描述

props

  1. 每个组件对象都会有props(properties)属性

  2. 组件标签中的所有属性都会保存在props中

  3. 可以通过props属性来进行组件之间的数据传递

  4. 无论是函数组件还是类组件,都不能修改自身的props,如需要更改则换为state

传值的写法

<组件名 属性名=值 属性名2=值2 .. />

propName="字符"  propName={js语句}

例:

import React, { Component } from "react";
import Person from "./page/Person";

class App extends Component {

  state = { age: 18 };

  handleChange = () => {
    this.setState({ age: 22 });
  };
  
  render() {
    return (
      <div>
      
        {/* <组件名  属性名=属性值/> */}
        <Person
          //传递字符串
          name="张三"
          //传递表达式
          age={this.state.age}
          //传递方法
          handleChange={this.handleChange}
        />
        
      </div>
    );
  }
}

export default App;

使用

  • 在类组件中使用this.props就能获得传递的数据;
  • 在函数组件中使用props来获取

例:

App.jsx代码:

import React, { Component } from "react";
import Person from "./page/Person";
import Student from "./page/Student";

class App extends Component {
  state = { age: 18 };

  handleChange = () => {
    this.setState({ age: 22 });
  };
  render() {
    return (
      <div>
      
        {/* <组件名  属性名=属性值/> */}
        <Person
          name="张三"
          age={this.state.age}
          handleChange={this.handleChange}
        />

        <hr />

        <Student
          name="李四"
          age={this.state.age}
          handleChange={this.handleChange}
        />
        
      </div>
    );
  }
}

export default App;



Person.jsx代码:

import React, { Component } from "react";

class Person extends Component {
  render() {
  
    console.log(this);
    console.log(this.props);
    
    return <div>Person</div>;
  }
}

export default Person;




Studeny.jsx代码:

import React from "react";

export default function Student(props) {

  console.log(props);
  
  return <div>Student</div>;
}

类组件Person:

image.png

函数组件Student:

image.png

对props进行限制

  • 如果别人在调用我们的组件的时候乱传不同的数据会导致我们数据可能无法产生预期的效果

  • 此时就需要借助第三方类型检查库,来对数据类型进行检查和限制

  • yarn add prop-types -save或者npm install prop-types -save

  • 该库中有两个静态属性defaultPropspropTypes

1、对标签属性进行类型、必要性的限制

组件.propTypes={ 
    //必传参数 
    propName: propsTypes库.类型名.isRequired
    
    propsName:propsTypes库.类型名, 
} 

//propsTypes.array/bool/func/number/object/string



2、指定默认标签属性

//默认值: 
组件.defaultProps={ 
    propName:值,  
}

例:

import React, { Component } from "react";
import propTypes from "prop-types";
import Person from "./page/Person";
import Student from "./page/Student";

Person.propTypes = {
  name: propTypes.string.isRequired, //限制name必传,且为字符串
  age: propTypes.number, //限制age为数值
  handleChange: propTypes.func, //限制speak为函数
};

Person.defaultProps = {
  name: "王五", //name默认值为王五
  age: 28, //age默认值为28
  sex: '男' //sex默认值为男
};

class App extends Component {
  state = { age: 18 };

  handleChange = () => {
    this.setState({ age: 22 });
  };

  render() {
    return (
      <div>
      
        <Person
          // name="张三"
          age={this.state.age}
          handleChange={this.handleChange}
        />

      </div>
    );
  }
}

export default App;

当设置了name属性为必传时,但没有传递组件name属性,则控制台会报错:

image.png

当传递的参数没有该属性时,可以设置默认值:

  • 在组件中age传递了18,没有传递sex,则设置默认值后,age依旧为传递的数值18,sex则为设置的默认值男

image.png

props.children

  • props.children 类似于Vue中的插槽,在组件中间所写的标签,都可以从props来传递,然后在组件中使用props.children;来接收这些标签并且渲染到页面上

例:

<Person name="张三">
  {/* 内部的标签 */}
  <h3>children1-1</h3>
  <h3>children1-2</h3>
  <h3>children1-3</h3>
  <h3>children1-4</h3>
</Person>



Person.jsx代码:

import React, { Component } from "react";

class Person extends Component {
  render() {
    // console.log(this);
    console.log(this.props);
    
    //此处使用this.props.children来渲染
    return <div>{this.props.children}</div>;
  }
}

export default Person;

image.png

React.Children

React还提供了一个工具方法React.Children来处理this.props.children。

1. React.Children.map(children, fn)

遍历props.children,在每一个子节点上调用fn 函数

  • children:组件内部的标签,即props.children

  • fn:执行的函数,接收两个参数(组件内部标签的每一项,下标值)

例:

class Person extends Component {
  render() {
    console.log(this.props);
    return (
      <div>
        {React.Children.map(this.props.children, (child, index) => {
          return <div key={index}>{child}</div>;
        })}
      </div>
    );
  }
}

image.png

2. React.Children.forEach(children, fn)

类似于 React.Children.map(),但是不返回对象。

  • children:组件内部的标签,即props.children

  • fn:执行的函数,接收两个参数(组件内部标签的每一项,下标值)

例:

class Person extends Component {
  render() {
    console.log(this.props);
    return (
      <div>
        {React.Children.forEach(this.props.children, (child, index) => {
          console.log(child, index);
        })}
      </div>
    );
  }
}

image.png

3. React.Children.count(children)

返回当前children当中的标签总数

  • children:组件内部的标签,即props.children

例:

class Person extends Component {
  render() {
    console.log(this.props);
    return <div>{React.Children.count(this.props.children)}</div>;
  }
}

image.png

4. React.Children.only(children)

返回children中仅有的子节点。如果在props.children传入多个子节点,将会抛出异常

  • children:组件内部的标签,即props.children

例:

class Person extends Component {
  render() {
    console.log(this.props);
    return <div>{React.Children.only(this.props.children)}</div>;
  }
}

当子节点只有一个时:

image.png

当子节点有多个时,控制台报错:

image.png

refs

1、组件内的标签可以使用ref属性来标识自己,即访问DOM节点

何时使用refs

  • 管理焦点,文本选择或者媒体播放
  • 触发强制性的动画
  • 集成第三方DOM库

字符串形式的ref(已过时)

<input ref='firstInput'/>

class App extends React.Component {

  showFirstData = () => {
    const { firstInput } = this.refs;
    alert(firstInput.value);
  }

  showSecondData = () => {
    const { secondInput } = this.refs;
    alert(secondInput.value);
  }

  render() {
    return (
      <div>
        <input ref='firstInput' type="text" placeholder='点击按钮提示数据' />
        <button onClick={this.showFirstData}>点击提示左侧数据</button>
        <input ref='secondInput' type="text" placeholder='失去焦点提示数据' onBlur={this.showSecondData} />
      </div>
    )
  }
}

在这里插入图片描述

回调函数形式的ref

<input ref={(c)=>this.firstInput = c}/>

在回调函数形式的ref中,会传递一个函数。这个函数中会接收React 组件实例或者HTML DOM元素作为参数,以使它们能在其他地方被存储和访问

class App extends React.Component {

  showFirstData = () => {
    console.log(this);
    const { firstInput } = this
    alert(firstInput.value)
  }

  showSecondData = () => {
    const { secondInput } = this
    alert(secondInput.value)
  }

  render() {
    return (
      <div>
        <input ref={c => this.firstInput = c} type="text" placeholder='点击按钮提示数据' />
        <button onClick={this.showFirstData}>点击提示左侧数据</button>
        <input ref={c => this.secondInput = c} type="text" placeholder='失去焦点提示数据' onBlur={this.showSecondData} />
      </div>
    )
  }
}

![在这里插入图片描述](https://img-blog.csdnimg.cn/cc9bac071762421aaa4938f80262f764.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBATWFpa2Uu,size_20,color_FFFFFF,t_70,g_se,x_16

createRef的使用

firstInput = React.createRef()

<input ref={this.firstInput} />

React会在组件挂载时将current属性传入DOM元素,并在组件卸载的时候传入null值。ref会在componentDidMountcomponentDidUpdate生命周期钩子触发前更新。

class App extends React.Component {

  // 创建新的ref实例
  firstInput = React.createRef()
  secondInput = React.createRef()

  showFirstData = () => {
  //可以获得当前节点
    console.log(this.firstInput.current);
    alert(this.firstInput.current.value)
  }

  showSecondData = () => {
    alert(this.secondInput.current.value)
  }

  render() {
    return (
      <div>
        <input ref={this.firstInput} type="text" placeholder='点击按钮提示数据' />
        <button onClick={this.showFirstData}>点击提示左侧数据</button>
        <input ref={this.secondInput} type="text" placeholder='失去焦点提示数据' onBlur={this.showSecondData} />
      </div>
    )
  }
}

在这里插入图片描述

事件处理

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

  • React中使用的是自定义事件(合成)事件,而不是使用原生的DOM事件-----为了更好的兼容性
  • React中的事件是通过事件委托的方式处理的(委托给组件最外层的元素)-----为了更高效

2、通过event.target可以得到发生事件的DOM元素对象-----不要过度使用ref

class App extends React.Component {

    showFirstData = (e) => {
        console.log(e);
        console.log(e.target);
    }

    showSecondData = (e) => {
        console.log(e);
        console.log(e.target);
    }

    render() {
        return (
            <div>
                <input type="text" placeholder='点击按钮提示数据' />
                <button onClick={this.showFirstData}>点击提示左侧数据</button>
                <input type="text" placeholder='失去焦点提示数据' onBlur={this.showSecondData} />
            </div>
        )
    }
}

在这里插入图片描述

事件处理的三种写法

一、在构造器中使用bind绑定this

import React, { useEffect, useState } from 'react';
import ReactDOM from 'react-dom';

class App extends React.Component {
  constructor(props) {
    super(props)
    this.state = { count: 0 }
    
    //此处绑定this
    this.handleAdd = this.handleAdd.bind(this)
    
  }
  
  //基础写法
  handleAdd() {
    this.setState({ count: this.state.count + 1 })
  }
  
  // 传值写法
  // handleAdd(a) {
  //   this.setState({ count: this.state.count + a })
  // }
  
  render() {
    return (
      <>
        <div>{this.state.count}</div>
        
        <button onClick={this.handleAdd}>+1</button>
        
        {/* 可使用下面写法来传值
        <button onClick={this.handleAdd.bind(this, 5)}>+1</button> */}
        
      </>
    )
  }
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
);

二、在回调函数中使用箭头函数

import React, { useEffect, useState } from 'react';
import ReactDOM from 'react-dom';

class App extends React.Component {
    constructor(props) {
        super(props)
        this.state = { count: 0 }
    }
    
    //基础写法
    handleAdd() {
        this.setState({ count: this.state.count + 1 })
    }
    
    // 传值写法
    // handleAdd(a) {
    //     this.setState({ count: this.state.count + a })
    // }
    
    // 需要e的传值写法
    // handleAdd(a, e) {
    //     console.log(a, e);
    //     this.setState({ count: this.state.count + a })
    // }
    
    render() {
        return (
            <>
                <div>{this.state.count}</div>
                
                {/* <button onClick={() => this.handleAdd()}>+1</button> */}
                
                {/* 可使用下面写法来传值
                <button onClick={() => this.handleAdd(5)}>+1</button> */}
                
                {/* 当需要e时可以使用下面写法 */}
                {/* <button onClick={(e) => this.handleAdd(5, e)}>+1</button> */}
                
            </>
        )
    }
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
);

三、使用类字段语法

import React, { useEffect, useState } from 'react';
import ReactDOM from 'react-dom';

class App extends React.Component {
  constructor(props) {
    super(props)
    this.state = { count: 0 }
  }
 
  handleAdd = () => {
    this.setState({ count: this.state.count + 1 })
  }

  render() {
    return (
      <>
        <div>{this.state.count}</div>
        
        <button onClick={this.handleAdd}>+1</button>
        
      </>
    )
  }
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
);

表单

非受控组件

不需要React来管理,现取现用

class App extends React.Component {

    handleSubmit = (e) => {
        e.preventDefault();
        alert(this.name.value)
    }

    render() {
        return (
            <form onSubmit={this.handleSubmit}>
                <label>
                    名字:
                    <input type="text" ref={c => this.name = c} />
                </label>
                <input type="submit" value="提交" />
            </form>
        )
    }
}

受控组件

渲染表单的React组件还控制着用户输入过程中表单发生的操作。被React以这样的方式控制取值的表单输入元素就叫做“受控组件”

class App extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            value: ''
        }
    }

    handleChange = (e) => {
        this.setState({
            value: e.target.value
        })
    }

    handleSubmit = (e) => {
        e.preventDefault();
        alert('提交成功!提交的名字为:' + this.state.value)
    }

    render() {
        return (
            <form onSubmit={this.handleSubmit}>
                <label>
                    名字:
                    <input type="text" value={this.state.value} onChange={this.handleChange} />
                </label>
                <input type="submit" value="提交" />
            </form>
        )
    }
}

高阶函数&函数柯里化

高阶函数:如果一个函数符合下面两个规范中的任何一个,那么该函数就是高阶函数

  1. 若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数;

  2. 若A函数,调用的返回值仍然是一个函数,那么A就可以成为高阶函数

常见的高阶函数有:PromisesetTimeoutarr.map()

函数柯里化:通过函数调用函数继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式

function sum1(a, b, c){
  return a + b + c;
}
sum1(1, 2, 3)

// 柯里化后
function sum(a){
  return(b)=>{
	return (c)=>{
	  return a+b+c
	}
  }
}
sum(1)(2)(3)

示例代码

//创建组件
class Login extends React.Component{
  //初始化状态
  state = {
    username:'', //用户名
    password:'' //密码
  }

  //保存表单数据到状态中 (高阶函数+函数柯里化)
  saveFormData = (dataType)=>{
	return (event)=>{
	  this.setState({[dataType]:event.target.value})
	}
  }

  //表单提交的回调
  handleSubmit = (event)=>{
	event.preventDefault() //阻止表单提交
	const {username,password} = this.state
	alert(`你输入的用户名是:${username},你输入的密码是:${password}`)
  }
  render(){
	return(
	  <form onSubmit={this.handleSubmit}>
		用户名:<input onChange={this.saveFormData('username')} type="text" name="username"/>
		密码:<input onChange={this.saveFormData('password')} type="password" name="password"/>
		<button>登录</button>
	  </form>
	)
  }
}
//渲染组件
ReactDOM.render(<Login/>,document.getElementById('test'))

不使用柯里化实现:

//保存表单数据到状态中
saveFormData = (dataType,event)=>{
  this.setState({[dataType]:event.target.value})
}

render(){
  return(
	<form onSubmit={this.handleSubmit}>
	  用户名:<input onChange={ event => this.saveFormData('username',event) } type="text" name="username"/>
	  密码:<input onChange={ event => this.saveFormData('password',event) } type="password" name="password"/>
	  <button>登录</button>
	</form>
  )
}