知识总结 React【组件】

132 阅读10分钟

组件

创建组件的两种方式

函数组件

函数组件:使用JS的函数或者箭头函数创建的组件

  • 为了区分普通标签,函数组件的名称必须 大写字母开头
  • 函数组件必须有返回值,返回一段JSX,表示该组件的结构
  • 如果组件不渲染任何内容,可以返回 null 。不能返回undefined 。在17版本返回 undefined 会报错,18版本修改了不报错了。
  • React 组件对大小写敏感,使用组件同样要使用大写单词开头。
function Hello() {
  return <div>刀刀</div>
}

const title = (
  <>
    <Hello></Hello>
  </>
)

执行了 ReactDOM.render 做了什么:

  1. 解析标签,找到相应组件,找不到则报错
  2. 发现组件是函数定义的,调用该函数,将返回的虚拟dom转为真实dom,随后呈现页面

类组件

创建语法如下:

class Xxxx extends React.Component{
    render() {
        return
    }
}

注意:

  1. 类组件的名称必须是大写字母开头,使用上大小写敏感
  2. 类组件应该继承React.Component父类,从而可以使用父类中提供的方法或者属性
  3. 类组件必须提供render方法,并返回组件的结构,如无需要返回的结构,返回null
class Hello extends React.Component{
  render() {
    return <div>刀刀好棒</div>
  }
}

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

执行了 ReactDOM.render 做了什么:

  1. 解析标签,找到相应组件,找不到则报错
  2. 发现组件是类定义的,随后new出该类的实例,并通过该类的实例,调用到原型上的render方法,将返回的虚拟dom转为真实dom,随后呈现页面

将组件提取到单独的js文件中

实现方式

  1. 创建Hello.js
  2. 创建组件(函数 或 类)
  3. 在 Hello.js 中导出该组件
  4. 在 index.js 中导入 Hello 组件
  5. 渲染组件

子组件中:

import { Component } from "react";
​
export class Hello extends Component {
  render() {
    return <div>刀刀</div>
  }
}

父组件中:

import React from "react";
import ReactDOM from "react-dom";
​
import {Hello} from "./Hello";
​
ReactDOM.render(<Hello/>, document.getElementById('root'))

类组件的状态

  • 状态state即数据。(同Vue的data)

    • 类似Vue中的data函数。
    • React同样是数据驱动视图 更新UI
  • 状态state 是组件内部的私有数据,独立作用域,(同Vue的data)

  • 状态 state 的值是对象,表示一个组件中可以有多个数据

  1. 声明 class 组件

    class App extends React.Component {
    ​
      render() {
        return <h1>{}</h1>
      }
    }
    
  2. 声明 state 数据

    class App extends React.Component {
      state = {
        count :100
      }
      render() {
        return <h1>{}</h1>
      }
    }
    
  3. 获取 state

    class App extends React.Component {
      state = {
        count :100
      }
      render() {
        return <h1>{this.state.count}</h1>
      }
    }
    

总结:

  1. state的作用:声明数据。 React中同样是数据驱动视图,数据变化导致UI更新。
  2. 声明方式state = {} 是固定的写法,独立作用域。
  3. 使用方式:通过this.state.属性名 的方式来访问
  4. this指向组件实例对象。

拓展:类与继承

class 基本语法

在 ES6 之前通过构造函数创建对象,在 ES6 中新增了一个关键字 class, 类 和构造函数类似,用于创建对象。

类与对象的区别:

  • 类:指的是一类的事物,是个概念,比如车 手机 水杯等

  • 对象:一个具体的事物,有具体的特征和行为,比如一个手机,我的手机等, 类可以创建出来对象。

  • 类创建对象的基本语法

    • 基本语法class 类名{ }
    • 构造函数 constructor 的用法,创建对象
    • 在类中提供方法,直接提供即可
    • 在类中不需要使用分号,分隔(Vue因为是对象才需要分号分隔)

extends 实现继承

  • extends 基本使用
  • 类可以使用它继承的类中所有的成员(属性和方法)
  • 类中可以提供自己的属性和方法
  • 注意:如果想要给类中新增属性,必须先调用 super 方法

组件类型

  • 无状态组件 : 早期,函数组件是不能自己提供数据【前提:基于hooks之前说的】
  • 有状态组件 : 类组件可以自己提供数据,组件内部的状态(数据如果发生了改变,内容会自动的更新),数据驱动视图

状态组件 - 容器组件

请求的数据保存在组件内。

无状态组件 - 展示组件

负责样式与结构的渲染。

组件实例的三大核心属性

状态

声明状态

class Weather extends React.Component {
  constructor(props) {
    super(props) // 继承父类的属性,必须要写,不然报错
    this.state = {isHot: true}
  }
  render() {
    console.log(this) // Weather所有的属性,其中state对象内有一个isHot属性
    return <h1>今天天气很 {this.state.isHot ? '炎热' : '凉爽'}</h1>
  }
}
ReactDOM.render(<Weather/>, document.getElementById('test'))

事件绑定

  1. react把原生的事件都重写了一遍,事件要大写,如:onClick、onBlur
  2. 事件绑定右边要给一个函数,用花括号引起来,而且不能加括号
class Weather extends React.Component {
  constructor(props) {
    super(props) // 继承父类的属性,必须要写,不然报错
    this.state = {isHot: true}
    this.newDemo = this.demo.bind(this) // 这里的bind有两个作用
  }
  render() {
    console.log(this) // Weather所有的属性,其中state对象内有一个isHot属性
    // 函数demo已经在实例上可以调用,因此使用this来调用
    return <h1 onClick={this.demo}>今天天气很 {this.state.isHot ? '炎热' : '凉爽'}</h1>
  }
  // 把demo放在原型对象上,实例使用
  demo() {
    // demo是作为onClick的回调,所以不是通过实例调用的,而是直接调用。类中的方法默认开启局部的严格模式,this指向undefined
    console.log(this.state.isHot);
  }
}
ReactDOM.render(<Weather/>, document.getElementById('test'))

bind 的作用:

  1. 返回一个新的函数,此时实例身上生成一个 newDemo 的函数。
  2. 修改 this 指向,新函数的 this 指向函数 demo

setState

状态 state 不可直接更改,需要使用 setState 配合更改。

class Weather extends React.Component {
  constructor(props) {
    super(props)
    this.state = {isHot: true}
    this.newDemo = this.demo.bind(this)
  }
  render() {
    return <h1 onClick={this.demo}>今天天气很 {this.state.isHot ? '炎热' : '凉爽'}</h1>
  }
  demo() {
    const isHot = this.state.isHot
    this.setState({isHot: !isHot})
  }
}
ReactDOM.render(<Weather/>, document.getElementById('test'))

总结:

  • setState 是实质上合并,而不是替换。找到需要修改的地方修改新的值,合并其他不需要修改的值。
  • setState 修改时,constructor 只渲染一次,render 渲染 1+n 次(1是一开始渲染,n是修改了n次)。
  • 方法 demo 触发几次调用几次。

简写方式

一开始写构造器是为了初始化 state 状态,在类中,可以直接写 a=1 的形式来初始化赋值,因此可以省去构造器部分。事件也是一样,使用赋值的形式,把函数改为箭头函数。

class Weather extends React.Component {
  // 初始化状态
  this.state = {isHot: true}
  render() {
    return <h1 onClick={this.demo}>今天天气很 {this.state.isHot ? '炎热' : '凉爽'}</h1>
  }
  // 自定义事件
  demo = () => {
    const isHot = this.state.isHot
    this.setState({isHot: !isHot})
  }
}
ReactDOM.render(<Weather/>, document.getElementById('test'))

总结:

  1. 在构造器外部使用赋值的形式声明变量,可以省略构造器部分的代码。
  2. 箭头函数没有 this 指向,会去其外一层寻找 this 作为自己的指向。本案例函数 demothis 指向了类 Weather

props

基本使用

class Person extends React.Component {
  render() {
    const {name,age,sex} = this.props // 获取到给的自定义属性
    return (
      <ul>
        <li>{name}</li>
        <li>{age}</li>
        <li>{sex}</li>
      </ul>
    )
  }
}
ReactDOM.render(<Person name="daodao" age="22" sex="男"/>,document.getElementById('test'))

批量传递

使用展开运算符来获取所有数据并赋值过去。

class Person extends React.Component {
  render() {
    const {name,age,sex} = this.props // 获取到给的自定义属性
    return (
      <ul>
        <li>{name}</li>
        <li>{age}</li>
        <li>{sex}</li>
      </ul>
    )
  }
}
const p = {name: 'daodao', age: '21', sex: '男'}
ReactDOM.render(<Person {...p}/>,document.getElementById('test'))

添加限制

  1. 某项数据必传
Person.propTypes = {
  name: PropTypes.isRequired
}
  1. 数据类型限制
Person.propTypes = {
  name: PropTypes.func
}
  1. 不传给默认值
Person.defaultProps = {
  name: 'daodao'
}

propTypes 是规则方法,必须这么写,写了之后 react 才会添加规则。React 有一个内置方法 PropTypes ,用于限制属性。

但是这么写会把 React 这个包体积变得很大,因此需要额外引包,直接使用即可。

简写模式

class Person extends React.Component {
  static propTypes = {
    name: PropTypes.func
  }
  static defaultProps = {
    name: 'daodao'
  }
  render() {

  }
}

props与构造器

构造器是否接收props,是否传递super,取决于:是否希望在构造器中通过this来访问props

函数式组件使用props

函数式组件,本质上用函数的形参来接收数据,没有this指向。通过 函数名.propTypes 的方式设置类型等。

function Person(props) {
  const {name,age,sex} = props // 获取到给的自定义属性
  return (
    <ul>
      <li>{name}</li>
      <li>{age}</li>
      <li>{sex}</li>
    </ul>
  )
}
Person.propTypes = {
  name: PropTypes.func
}
ReactDOM.render(<Person name="daodao" age="22" sex="男"/>,document.getElementById('test'))

refs

组件内的标签可以使用ref来标识自己。

字符串类型的ref

目前已弃用 this.refs.ref名称 的形式,如果写多了会有效率的问题。

回调类型的ref

ref 赋值一个箭头函数,该箭头函数的this指向就是实例对象,有一个形参,获取到该ref的dom节点。通过 this.变量名 在实例对象中创建一个变量存储这个节点。

class Person extends React.Component {
  render() {
    return (
      <input ref={v => this.input = v} />
    )
  }
}

调用次数上,如果回调函数是以上方案例中内联函数的方式定义的,在更新的过程中会执行两次,第一次传入的参数是null,第二次传递的才是想要的节点数据。

class Person extends React.Component {
  saveInput = (v) => {
    this.input = v
  }
  render() {
    return (
      <input ref={this.saveInput} />
    )
  }
}

createRef

React.createRef 调用后可以返回一个容器,该容器可以存储被 ref 所标识的节点,该容器是专人专用,一个只能存储一个节点。

class Person extends React.Component {
  myRef = React.createRef()
  saveInput = () => {
    console.log(this.myRef.current.value)
  }
  render() {
    return (
      <input onClick={this.saveInput} ref={this.myRef} />
    )
  }
}

事件

事件处理

注册事件

语法on+事件名 ={事件处理程序 比如 onClick={this.handleClick}

  1. 少量代码:使用一个箭头函数

    class App extends React.Component {
      render() {
        return <button onClick = {() => alert(123)}>点我</button>
      }
    }
    
  2. 多行代码:自定义一个函数

    class App extends React.Component {
      render() {
        return <button onClick = {this.handlerClick}>点我</button>
      }
      handlerClick() {
        console.log('chao');
        console.log('tie');
        console.log('ze');
      }
    }
    

总结:

  1. React事件采用驼峰命名法,比如onMouseEnter, onClick
  2. class中自定义方法,通过this.方法名

常见错误

  • 函数后加括号

    class App extends React.Component {
      render() {
        return <button onClick = {this.handlerClick()}>点我</button>
      }
      handlerClick() {
        console.log('chao');
        console.log('tie');
        console.log('ze');
      }
    }
    

    结果是立即执行,没有等待点击事件触发。

  • 省略大括号

    class App extends React.Component {
      render() {
        return <button onClick = 'alert(123)'>点我</button>
      }
    }
    

    报错

获取事件对象

默认形参就是事件对象。

class App extends React.Component {
  render() {
    return <a href="www.baidu.com" onClick = {(e) => e.preventDefault()}>点我</a>
  }
}
class App extends React.Component {
  render() {
    return <a href="www.baidu.com" onClick = {this.handlerClick}>点我</a>
  }
  handlerClick(e) {
    e.preventDefault()
  }
}

事件传值

方法:包一层箭头函数,在箭头函数中,调用处理函数。

class App extends React.Component {
  render() {
    return <button onClick = {(e) => this.handlerClick(e, 2)}>点我</button>
  }
  handlerClick(e, num) {
    console.log(e, num);
  }
}

this指向

class App extends React.Component {
  render() {
    return <button onClick = {this.handlerClick}>点我</button>
  }
  handlerClick() {
    console.log(this);
  }
}

最终 this 指向打印出来的是 undefined

  1. render 里面用箭头函数

    React中自带的结构体中 this 指向正确,因为源码已经处理过了,因此利用自带结构体中的箭头函数

    class App extends React.Component {
      render() {
        return <button onClick = {() => this.handlerClick()}>点我</button>
      }
      handlerClick() {
        console.log(this);
      }
    }
    

    缺点:会把大量的js处理逻辑放到JSX中,将来不容易维护

  2. 自定义方法,改成箭头函数

    class App extends React.Component {
      render() {
        return <button onClick = {this.handlerClick}>点我</button>
      }
      handlerClick = () => {
        console.log(this);
      }
    }
    

    注意:这个语法是试验性的语法,但是有babel的转义,所以没有任何问题

  3. 利用 bind 改变指向(了解)

    class App extends React.Component {
      render() {
        return <button onClick = {this.handlerClick.bind(this)}>点我</button>
      }
      handlerClick() {
        console.log(this);
      }
    }
    

案例:购物车

复用同一个事件。

class App extends React.Component {
  state = {
    count: 0,
  };

  render() {
    return (
      <div>
        <p>当前数值为:{this.state.count}</p>
        <hr />
        <button onClick= {() => this.handlerClick(1)}>+1</button>
        <span>{this.state.count}</span>
        <button onClick= {() => this.handlerClick(-1)}>-1</button>
      </div>
    );
  }

  handlerClick = (num) => {
    const {count} = this.state
    this.setState({count: count + num})
  }
}

表单

受控组件

基本概念

  1. 表单元素的值,由 state 控制
  2. onChange 配合 setState 修改 state 的值

使用步骤

  1. 在state中添加一个状态,作为表单元素的value值(控制表单元素的值)
  2. 给表单元素添加change事件,设置state的值为表单元素的值(控制值的变化)
class App extends React.Component {
  state = {
    msg: 'hello react'
  }

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

  render() {
    return (
      <div>
        <input type="text" value={this.state.msg} onChange={this.handleChange}/>
      </div>
    )
  }
}

注意:

  1. 推荐使用onChange 事件监听用户输入,不推荐使用onInput
  2. reactchange 事件等同于html中的input 事件。
  3. react中的blur 事件,等同于html中的change 事件。

常见的受控组件

  • 文本框、文本域、下拉框(操作 value 属性)
  • 复选框(操作 checked 属性)

对象属性名称-插值技术

handleName = (e) => {
  this.setState({
    [e.target.name]: e.target.value
  })
}

非受控组件

表单元素的value由DOM对象控制,不受state的控制

class Demo extends React.Compoment {
    handelSave = () => {
        const { username } = this.refs
        console.log(username.value)
    }
    render() {
        return (
            <input ref="username" type="text" />
            <button onClick={this.handelSave}>提交</button>
        )
    }
}

总结:

  1. 表单元素,它的值由DOM控制,不使用state控制,称为非受控组件。
  2. 表单元素,它的值由state控制,由setState改变,称为受控组件。
  3. **推荐-**使用受控组件,React推荐数据驱动视图。

总结

今日总结.png