React笔记(一):jsx、state、props、refs、事件处理、状态提升、函数柯里化

React笔记(一)

1.什么是JSX
  • React作为一个使用虚拟DOM渲染页面的框架,并且本质上是一个JavaScript库,其包含有一套可以在JS代码内书写HTML的扩展语法。这套扩展语法就叫做JSX。
  • JSX的全称为JavaScript XML,是react定义的一种类似于XML的JS扩展语法,其主要功能为创建虚拟DOM。在组件多层嵌套时更为便捷。
2.JSX的语法规则
  • 定义虚拟DOM时不要使用引号。

    //正确用法
    var A  = <div><p>xxx</p></div>;
    //错误用法:a变成字符串了
    var A  = "<div><p>xxx</p></div>";
    
  • 在标签内部需要使用JS时,需要使用{}将这段JS语句包裹起来,并且这段JS的返回值,会变成页面上的元素显示出来。

    function a(){
        console.log('123');
        return '456';
    }
    var B = <div><p>{a()}</p></div>;
    //渲染B组件后,会在屏幕上显示456,并在F12控制台上输出123。
    
  • 标签内样式类名要使用className,为了与定义类时所用的class做区分;当使用内联样式时,要使用style属性,并使用属性键值对的形式书写,区别于CSS样式中的xxx-xxx形式,在JSX的标签内联样式中键的书写方式为xxxXxx的驼峰命名法。

    //正确用法
    var A = <div className=‘aaa’></div>;
    var B = <div style={{minHeight:'400px'}}></div>;
    //错误用法:类名属性不对
    var A = <div class=‘aaa’></div>;
    //错误用法:键值对中的键不对
    var B = <div style={{min-height:'400px'}}></div>;
    //错误用法:少一层大括号,第一层代表内部将要写JS语句,第二层代表对象,内部是属性键值对
    var B = <div style={minHeight:'400px'}></div>;
    //错误用法:内部是JS语句,值作为字符串需要使用引号包起来
    var B = <div style={{minHeight:400px}}></div>;
    
  • 返回的虚拟DOM只能有一个根标签,且根标签必须闭合

    //正确用法
    var A  = <div><p>xxx</p></div>;
    //错误用法:返回两个根标签
    var A  = <div></div><p>xxx</p>;
    
  • 当创建组件时,需要使用大写字母作为开头,如果小写子母开头,React将其判断为一个标签,并去渲染特定的标签,如果渲染不到即报错。

3.组件类型
  • 函数式组件创建时函数名要大写,返回值为一个虚拟DOM,在页面渲染时需要写作闭合标签。需要注意的是,函数式组件内部的this属性为undefined。这是由于使用babel进行编译后,内部开启了严格模式。
  • 类式组件在创建时要继承于React.Component类。并且需要书写render()方法,返回值为虚拟DOM。其中类的this是渲染在页面时,所创建的此组件的实例对象。
4.state
  • React组件内含有三大核心属性,state是最重要的属性,其驱动着页面的变化,在不使用React Hooks的情况下,只有类组件存在此属性,因为在使用其的过程中需要应用到this关键字。使用的整体流程为:初始化state=>在页面需要变化的部分使用state=>在页面需要变化时调用setState方法。在调用对应的组件内部的setState方法时,会自动触发render方法,重新渲染页面。

    import React, { Component } from 'react'
    
    export default class Demo extends Component {
      //初始化state
      state = {
        count: 1
      };
      //配置按钮事件响应函数
      //使用箭头函数的原因,this指向问题,由于因为事件函数是作为onClick的回调,不是通过实例调用的,是直接调用。
      //并且由于类中的方法默认开启了局部的严格模式,所以事件响应函数中的this为undefined。
      //在使用箭头函数时会将此函数的this指向组件类的当前实例对象,消除错误的发生。
      addNumber = () => {
        const count = this.state.count + 1;
        this.setState({ count });
      }
      render() {
        return (
          <div>
            {/* 在需要显示的地方,使用state展示 */}
            <p>{this.state.count}</p>
            {/* 在绑定时使用onClick属性,而不使用onclick属性 */}
            {/* 在需要触发页面变化的地方绑定事件 */}
            <button onClick={this.addNumber}>点我加一</button>
          </div>
        )
      }
    }
    
5.props
  • 在直属的父子关系内,父子之间传值使用props。在使用时需要使用,父组件在其标签内添加属性

    //父组件
    <Child name='abc'/>
    //子组件
    class Child extends Component {
      render() {
        return (
          <div>{this.props.name}</div>
        )
      }
    }
    
  • 当需要批量传递props时可以使用解构赋值与扩展运算符,这时则可以批量传递props的属性值,也叫批量传递标签属性

    //父组件
    //定义对象 
    var p = {
        name:'abc',
        age:16
      }
    //向子组件传递内容
    //最外侧{}代表的是内部为JS语句,里面是对对象使用扩展运算符,在react中是可以对对象使用扩展运算符的
    <Child {...p}/>
    //子组件
    class Child extends Component {
      render() {
        return (
          <div>{this.props.name}...{this.props.age}</div>
        )
      }
    }
    
  • 组件props的限制,在需要对父组件向子组件传递的内容进行限制时,可以使用prop-types进行限制,可以配置对于props的一些限制,例如传递类型,默认值,是否是必填项等。

    //父组件
    <Child name='abc'/>
    //子组件
    //引入PropTypes
    import PropTypes from 'prop-types';
    class Child extends Component {
      render() {
        return (
          <div>{this.props.name}...{this.props.age}</div>
        )
      }
    };
    
    //定义props的限制
    Child.propTypes={
      //限制必填与字符串类型
      name:PropTypes.string.isRequired,
      //限制为数字类型
      age:PropTypes.number
    };
    //定义props的默认值
    Child.defaultProps={
      age:17
    };
    

    此时即使父组件没有向子组件传递age属性,子组件也会显示age17,并且如果将父组件向子组件传递的name属性由abc转换成数字,或删除在控制台处也会出现error

  • 区别于state,在函数式组件内部也可以使用props,类式组件内部应用的是this,传递的属性是存在于其实例对象上的。函数式组件不存在this,因此使用函数的参数props传递进子组件。并且props限制等使用方法与类式组件相同

    //父组件
    <Child name='abc'/>
    //子组件
    function Child(props) {
        return (
          <div>{props.name}...{props.age}</div>
        )
    }
    
    Child.propTypes={
      name:PropTypes.string.isRequired,
      age:PropTypes.number
    }
    Child.defaultProps={
      age:17
    }
    
    
6.ref
  • React是使用虚拟DOM进行页面渲染的,因此要减少对真实DOM的操作,真实DOM也就是通过getElementById等方法获取到的DOM元素,此时可以使用第三个核心属性ref对特定元素进行操作。

  • 与HTML标签中的id属性类似,存在着一种字符串类型的ref属性来对组件内的标签进行标识,此时对此组件内标签的取值就可以通过此种简单的方式获取,在使用时要使用refs,因为所有ref属性会寄存于refs这个键值对的集合中。

    class Demo extends Component{
      //配置驱动显示的状态
      state = {
        show:''
      };
      render(){
        return (
          <div>
          <p>{this.state.show}</p>
           {/* input框,并为其配置一个ref属性 */}
          <input type="text" ref='abc'/>
           {/* 通过ref读取input的内容,可以在F12控制台上看见输出了一个DOM元素 */}
          <button onClick={()=>{console.log(this.refs.abc);this.setState({show:this.refs.abc.value})}}>同步</button>
          </div>
        )
      }   
    }
    
  • 字符串形式的ref在目前的版本上已经过时了,因此可以使用回调形式的ref,具体的使用方式,只需要将上面的ref=‘abc'这段替换成内联函数形式的ref={c=>this.abc=c}即可,在使用时,也无需去通过this.refs获取,而是直接从当前组件的实例对象this上获取。

  • 使用回调形式的ref时,如果在回调函数中添加console.log,会发现此回调函数被触发的两次。原因是在每次渲染时都会创建一个新的内联函数实例,并清空旧的,因此会相应的触发两次。但在大多数情况下此无关紧要。可以通过类似于事件绑定函数方式,在类内定义方法,并进行绑定的方式设置,这样会减少此种情况的发生。

    class Demo extends Component{
      //配置驱动显示的状态
      state = {
        show:''
      };
    setRef = (c)=>{
        this.abc =c;
    }
      render(){
        return (
          <div>
          <p>{this.state.show}</p>
           {/* input框,并为其配置一个ref属性 */}
          <input type="text" ref={this.setRef}/>
           {/* 通过ref读取input的内容,可以在F12控制台上看见输出了一个DOM元素 */}
          <button onClick={()=>{console.log(this.abc);this.setState({show:this.abc.value})}}>同步</button>
          </div>
        )
      }   
    }
    
  • 在React内部存在一个直接创建ref的方法,createRef。在使用时,区别于回调形式与字符串形式,其定义ref时不会在标签内定义,而是在方法内或构造器内定义,并将其赋值给需要使用的标签。

    class Demo extends Component{
      constructor(props){
        super(props);
        this.abc = React.createRef();
      }
      state = {
        show:''
      };
      
      render(){
        return (
          <div>
          <p>{this.state.show}</p>
          {/* input框,并为其配置一个ref属性 */}
          <input type="text" ref={this.abc}/>
          {/* 通过ref读取input的内容,可以在F12控制台上看见输出了一个DOM元素 */}
          <button onClick={()=>{console.log(this.abc);this.setState({show:this.abc.current.value})}}>同步</button>
          </div>
        )
      }   
    }
    
  • 需要注意的是,在使用createRef时,想要获取ref所对应的DOM元素不可以直接获取,需要使用ref.current获取。另外,对于函数组件,由于其没有实例化对象,所以是没有ref属性的,也就是不可以在函数式组件上使用ref属性;对于类式组件,可以使用ref属性,其对应的是此组件的当前实例实例化对象。

7.事件处理
  • 在React中使用驼峰命名法,而非传统HTML中的纯小写方式书写标签内的事件绑定部分。在上面的部分代码中已经体现了如何为一个按钮绑定事件。

  • 在React中绑定事件时,如果需要阻止默认事件,例如表单的提交。这时不能如原生JS一样,使用false的返回值实现,需要使用传递给事件的event参数内部的preventDefault方法实现。

    <button onClick={(e)=>{e.preventDefault()}}>阻止默认</button>
    
  • 在最开始的state部分,使用了箭头函数来解决getState()的this指向问题。在React的事件绑定中,都存在着这种问题,除了箭头函数以外,可以使用原生JS中的bind完成这项处理。上面的state部分代码可以相应的替换为:

    class Demo extends Component {
      constructor(props){
        super(props);
        this.addNumber = this.addNumber.bind(this);
      }
      //初始化state
      state = {
        count: 1
      };
      //配置按钮事件响应函数
      addNumber() {
        const count = this.state.count + 1;
        this.setState({ count });
      }
      render() {
        return (
          <div>
            {/* 在需要显示的地方,使用state展示 */}
            <p>{this.state.count}</p>
            {/* 在绑定时使用onClick属性,而不使用onclick属性 */}
            {/* 在需要触发页面变化的地方绑定事件 */}
            <button onClick={this.addNumber}>点我加一</button>
          </div>
        )
      }
    
  • 当需要向事件传递参数时,也可以通过这两种方式实现。比如说,在方法内存在一个计算后得出的属性,每一次点击增加这个属性值而不是固定的增加一,这个时候代码就需要如下这种写法。

    class Deno extends Component {
      constructor(props){
        super(props);
        this.addNumber = this.addNumber.bind(this);
      }
      state = {
        count: 1
      };
      addNumber(a) {
        const count = this.state.count + a;
        this.setState({ count });
      }
      render() {
        var a = 1;
        return (
          <div>
            <p>{this.state.count}</p>
            <button onClick={(e)=>{this.addNumber(a,e)}}>点我加{a}</button>
          </div>
        )
      }
    }
    

    又或者可以将按钮那一行替换为

    <button onClick={this.addNumber.bind(this,a)}>点我加{a}</button>
    
8.状态提升
  • 当兄弟组件需要使用互相的状态时,可以把此状态提升到共同的父组件上,这种做法就叫做状态提升。

  • 在状态提升前,需要了解如何子组件向父组件传值。在React中子组件向父组件传值,本质上也是父组件向子组件传递内容。但此内容是父组件内部的一个方法,子组件通过这个方法,操作父组件的状态,即子组件向父组件传值。

    class Root extends Component {
      constructor(props){
        super(props);
        this.addNumber = this.addNumber.bind(this);
      }
      state = {
        count: 1
      };
      addNumber(a) {
        const count = this.state.count + a;
        this.setState({ count });
      }
      render() {
        return (
          <div>
            <p>{this.state.count}</p>
           	{/* 向子组件传递操作本组件状态的方法,并将调用此方法的实例绑定在本组件上 */}
            <Child fun={this.addNumber.bind(this)}/>
          </div>
        )
      }
    }
    
    class Child extends Component {
      constructor(props){
        super(props);
      }
      render() {
        var a = 1;
        //使用箭头函数向方法内传值,实现子组件操作父组件的状态
        return (
            <button onClick={()=>{this.props.fun(a)}}>点我加一</button>
        )
      }
    }
    
  • 状态提升也就是在上述的基础上,将原本位于兄弟组件上的状态,提升到父组件上,这样子组件可以通过props获取父组件内部属性,另一个子组件也可以通过props操作父组件的属性,实现了兄弟组件之间的联系。

9.函数柯里化
  • 在上述的代码中可以发现,每一次事件处理传值时都需要使用箭头函数,或进行绑定传值。原因是,标签的onClick属性(或其他关于事件处理的属性)都是一个函数。也就是在onClick后的{}内应当存在一个函数,但向函数传递参数,最好的做法还是调用并传参,但即使在函数中指定形参,在标签内绑定事件时也无法确定其形参。这时可以使用函数柯里化。

  • 首先了解函数柯里化,其代表着将原本传递的多个参数,分层传递。例如

    //不使用柯里化
    function add(a,b){
        return a+b;
    };
    add(1,2);//3
    //使用柯里化
    function addC(a){
        return function(b){
            return a+b;
        };
    }
    addC(1)(2);//3
    
  • 这时会发现,使用函数柯里化的部分,在函数名后跟着两个()。这其实不难理解,第一个()代表着函数addC的调用运算结果:外侧的return,一个有一个形参的函数,第二个()代表着此调用此返回函数的运算结果:里侧的return,两个实参的和

  • 应用到实际事件处理传值中,可以使用如下代码替换上部分事件处理传参代码。

    class Demo extends Component {
      constructor(props){
        super(props);
        this.addNumber = this.addNumber.bind(this);
      }
      state = {
        count: 1
      };
      addNumber(a) {
        return (e)=>{
          const count = this.state.count + a;
          this.setState({ count });
        }
      }
      render() {
        var a = 1;
        return (
          <div>
            <p>{this.state.count}</p>
            <button onClick={this.addNumber(a)}>点我加{a}</button>
          </div>
        )
      }
    }
    
  • 相当于在addNumber的核心代码外包了一层外壳,通过这个外壳获取参数,并把参数传递给壳内的函数。返回时将壳内的函数传递给标签的事件属性上,完成事件绑定。

参考文章

详解JS函数柯里化 - 简书 (jianshu.com)
React 官方中文文档 – 用于构建用户界面的 JavaScript 库 (docschina.org)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值