React组件和事件学习

一、组件

组件允许你将 UI 拆分为独立可复用的代码片段,并对每个片段进行独立构思。从概念上类似于 JavaScript 类或函数。它接受任意的入参(props),并返回用于描述页面展示内容的 React元素(jsx)

组件分为两种:函数组件、类组件

因为函数组件比较简单,在Recat官网中只有函数组件的教学,但是在公司的项目中类组件也会出现,也需要掌握

1.1 函数组件

  • 函数组件(无状态组件):使用JS的函数创建组件
  • 函数名称首字母必须以大写
  • 函数组件必须有返回值(jsx)/null,表示该组件的结构,且内容必须有顶级元素
import React from 'react'
function App() {
    return (
        <div>这是第一个函数组件</div>
    )
}
//或者使用箭头函数也可以
const App = () => {
  return (
    <div>这是第一个函数组件</div>
  )
}

export default App

明确此函数只能是函数,不能当作类来使用,函数组件不能被new,因为函数有二义性,只能当作函数,不能当作类

创建函数组件有几点注意事项:

  • react17之前,在定义组件时,一定要引入react类 import React from ‘react’,react17及之后,可以不用引入,因为react17之后,它在cli中进行了自动引入补全import React from ‘react’
  • 定义函数,可以为箭头函数,推荐使用箭头函数
  • 函数名称首字母大写
  • 返回jsx结构,react18及之后它是允许返回undefined,之前是不允许的,返回null是可以的
  • 通过esm导出对应的函数
  • 如果你安装了jsx插件,就可以通过它的快速方式来创建函数组件 【rfc】,需要在vscode中安装JS JSX Snippets这个插件,插件中有很多快速创建组建的简写,如果想要安装,可以浏览这篇文章进行安装:http://t.csdnimg.cn/wwWrX

1.2 类组件

  • 使用ES6语法的class创建的组件(状态组件)
  • 类名称必须要大写字母开头
  • 类组件要继承React.Component父类,从而可以使用父类中提供的方法或者属性
  • 类组件必须提供 render 方法,用于页面结构渲染,结构必须要有顶级元素,且必须return去返回
  • 如果你在vscode中安装了jsx插件,可以使用rcc来创建类组件
import React from 'react'
class App extends React.Component{
    render(){
        return (
            <div>这是第一个类组件</div>
        )
    }
}
export default App;
//或者
import React, { Component } from 'react';

class App extends Component {
  render() {
    return (
      <div>这是第一个类组件</div>
    );
  }
}
export default App;

1.3父子组件传值

为了方便理解,先写到一个文件里面,后期的项目就要拆开,模块化了

组件间传值,在React中是通过只读属性 props 来完成数据传递的。

props:接受任意的入参,并返回用于描述页面展示内容的 React元素。

props:中的数据是不能被修改的,只能读取。

props:是一个单向数据流。 父流向子,子不能直接去修改props中的数据

  • 函数组件父子传值
import React from 'react'

//子组件
//父通过自定义属性向子组件传递数据,通过函数的形参来接受,一般起的形参的名称为props,它为一个对象{}
//在开发中,我们会写成下面这种解构方式,可以给默认值
const Child = ({ num = 200 }) => {
  return (
    <div>
      <h3>Child组件: {num}</h3>
    </div>
  )
}

// 父组件
const App = () => {
  let num = 100

  return (
    <div>
      <h1>App组件</h1>
      <hr />
      {/* 注:在react中的jsx调用自定义组件时,组件名必须首字母大写开头 */}
      {/* 父通过自定义属性向子组件传递数据 */}
      <Child num={num} />
      <Child />
    </div>
  )
}

export default App

如果是公用组件,不要把设置默认值的要求安排给开发者,目前的组件是自己用,可以这样写默认值

函数组件没有强制刷新方法,没有办法让视图更新,后续可以通过hook来更新,后续的笔记中会有说明,类组件有强制刷新的方法

  • 类组件父子传值
import React, { Component } from 'react'

// 子组件
// 在子组件中,它是通过this.props来接受父组件通过自定义属性传入过来的数据
class Child extends Component {
  render() {
    // console.log(this.props)
    // 解构,此写法也是在自定义给自己所用的组件中使用
    const { num = 100 } = this.props
    return (
      <div>
        <div>child组件 -- {num}</div>
      </div>
    )
  }
}

// 父组件 -- 类组件它有this
class App extends Component {
  // 类的成员属性,这样的写法是在es8版本之后才支持的
  num = 200
  //es8版本之前需要写在constructor里面
  //constructor(){
      //this.num=200
  //}
  render() {
    return (
      <div>
        <h3>App组件 -- {this.num}</h3>
        <hr />
        <Child num={this.num} />
      </div>
    )
  }
}

export default App

二、事件

2.1 事件绑定

  • 类组件事件绑定

React 事件的命名采用小驼峰式,而不是纯小写

使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串,函数或方法不能用小括号

类组件与函数组件绑定事件是差不多的,只是在类组件中绑定事件函数的时候需要用到this,代表指向当前的类的引用,在函数中不需要调用this

类组件可以强制更新视图,父子组建的render方法都执行了,更新完成之后会触发里面的回调函数

this.forceUpdate(() => {
   console.log('更新完毕')
})

这种是不带参数

import React, { Component } from 'react'

// 子组件
class Child extends Component {
  render() {
    console.log('child --- render')
    const { num = 100 } = this.props
    return (
      <div>
        <div>child组件 -- {num}</div>
      </div>
    )
  }
}

// 父组件 
class App extends Component {
  // 定义一个普通成员属性,它的变化不会触发视图更新
  num = 200

  // 定义成员方法,要用到箭头函数来定义,是为了解决this指向问题
  addNum = () => {
    this.num++
    // 强制更新视图 -- 此方法只能在类组件中使用
    this.forceUpdate(() => {
      console.log('更新完毕')
    })
  }

  render() {
    return (
      <div>
        <h3>App组件</h3>
        {/*
          组当前button元素添加一个自定义的点击事件 onClick={绑定方法,它绑定是一个方法体}
          它绑定方法中自动注入event事件对象
        */}
        <button onClick={this.addNum}>+++++++</button>
        <hr />
        <Child num={this.num} />
      </div>
    )
  }
}

export default App

这种是带参数

import React, { Component } from 'react'

// 子组件
// 在子组件中,它是通过this.props来接受父组件通过自定义属性传入过来的数据
class Child extends Component {
  render() {
    console.log('child --- render')
    // 此写法也是在自定义给自己所用的组件中使用
    const { num = 100 } = this.props
    return (
      <div>
        <div>child组件 -- {num}</div>
      </div>
    )
  }
}

// 父组件 -- 类组件它有this
class App extends Component {
  // 定义一个普通成员属性,它的变化不会触发视图更新
  num = 200

  // 定义成员方法,要用到箭头函数来定义,是为了解决this指向问题
  addNum = n => () => {
    this.num += n
    // 强制更新视图 -- 此方法只能在类组件中使用
    this.forceUpdate(() => {
      console.log('更新完毕')
    })
  }

  render() {
    console.log('app --- render')
    return (
      <div>
        <h3>App组件 -- {this.num}</h3>
        {/*
          组当前button元素添加一个自定义的点击事件 onClick={绑定方法,它绑定是一个方法体}
          它绑定方法中自动注入event事件对象
        */}
        <button onClick={this.addNum(100)}>+++++++</button>
        <hr />
        <Child num={this.num} />
      </div>
    )
  }
}

export default App

注意addNum的方法多了一个箭头,返回的是函数体或方法体就可以带参数了

  • 函数组件事件绑定

这种不带参数

import React from 'react'

// 函数组件中绑定事件时没有this
const App = () => {
  let num = 100

  const addNum = () => {
    num++
  }

  return (
    <div>
      <h1>App组件</h1>
      <hr />
      {/* 切记:默认绑定的事件方法一定不要写小括号 */}
      <button onClick={addNum}>添加点击事件</button>
    </div>
  )
}

export default App

这种写法数据会变化,但是视图不会更新,后续会说如何更新视图

这种是带参数

import React from 'react'

// 函数组件中绑定事件时没有this
const App = () => {
  let num = 100

  const addNum = n => () => {
    num += n
    console.log(num)
  }

  return (
    <div>
      <h1>App组件</h1>
      <hr />
      {/* 
        绑定的是一个事件方法体,而不是执行的结果
        如果在此处写了小括号,为了传数据,就需要你在绑定的方法返回一个函数
      */}
      <button onClick={addNum(10)}>添加点击事件</button>
    </div>
  )
}

export default App

注意addNum的方法多了一个箭头,返回的是函数体或方法体就可以带参数了

2.2 事件合成

React合成事件是React 模拟原生DOM事件所有能力的一个事件对象。根据 W3C规范来定义合成事件,兼容所有浏览器,拥有与浏览器原生事件相同的接口。合成事件除了拥有和浏览器原生事件相同的接口,包括stopPropagetion()和preventDefault()。

在React中,所有事件都是合成的,不是原生DOM事件,可以通过 e.nativeEvent 属性获取原生DOM事件。合成事件不会映射到原生事件。

浏览器兼容,实现更好的跨平台

为什么出现这个技术?

**性能优化:**使用事件代理统一接收原生事件的触发,从而可以使得真实 DOM 上不用绑定事件。React 挟持事件触发可以知道用户触发了什么事件,是通过什么原生事件调用的真实事件。这样可以通过对原生事件的优先级定义进而确定真实事件的优先级,再进而可以确定真实事件内触发的更新是什么优先级,最终可以决定对应的更新应该在什么时机更新。

**分层设计:**解决跨平台问题,抹平浏览器差异。

import React, { Component } from "react";

// 在React17以前,都是委托给document容器的(而且只做了冒泡阶段的委托)
// 在React17及以后版本,都是委托给#root这个容器(捕获和冒泡都做了委托)只要挂载点的id不一样,可以做多入口,项目可以更复杂一些

class App extends Component {
  // 挂载完毕后执行的方法 onMounted
  componentDidMount() {
    document.body.addEventListener(
      "click",
      function () {
        console.log("原生 -- 捕获 -- body");
      },
      true
    );
    document.body.addEventListener(
      "click",
      function () {
        console.log("原生 -- 冒泡 -- body");
      },
      false
    );

    document.querySelector("#root").addEventListener(
      "click",
      function () {
        console.log("原生 -- 捕获 -- root");
      },
      true
    );
    document.querySelector("#root").addEventListener(
      "click",
      function () {
        console.log("原生 -- 冒泡 -- root");
      },
      false
    );

    document.querySelector("#box").addEventListener(
      "click",
      function () {
        console.log("原生 -- 捕获 -- box");
      },
      true
    );
    document.querySelector("#btn").addEventListener(
      "click",
      function (evt) {
        console.log("原生 -- 捕获 -- button");
        // 阻止事件链传递,它同元素中的已绑定的一样的事件会被触发
        // evt.stopPropagation()

        // 阻止事件链传递,它同元素中的已绑定的一样的事件不会被触发
        evt.stopImmediatePropagation();
      },
      true
    );
    document.querySelector("#btn").addEventListener(
      "click",
      function (evt) {
        console.log("原生 -- 捕获 -- button2");
      },
      true
    );
    document.querySelector("#box").addEventListener(
      "click",
      function () {
        console.log("原生 -- 冒泡 -- box");
      },
      false
    );
    document.querySelector("#btn").addEventListener(
      "click",
      function () {
        console.log("原生 -- 冒泡 -- button");
      },
      false
    );
  }

  // ------------ 合成事件
  onClickBoxCapture = (evt) => {
    console.log("合成 -- 捕获 -- box");
    // 阻止事件链传递
    // evt.stopPropagation()
  };
  onClickBtnCapture = () => {
    console.log("合成 -- 捕获 -- button");
  };

  onClickBox = () => {
    console.log("合成 -- 冒泡 -- box");
  };
  onClickBtn = () => {
    console.log("合成 -- 冒泡 -- button");
  };

  render() {
    return (
      <div>
        <h1>App组件</h1>
        <div
          id="box"
          onClickCapture={this.onClickBoxCapture}
          onClick={this.onClickBox}
        >
          <button
            id="btn"
            onClickCapture={this.onClickBtnCapture}
            onClick={this.onClickBtn}
          >
            点击事件
          </button>
        </div>
      </div>
    );
  }
}

export default App;

阻止事件链传递,它同元素中的已绑定的一样的事件会被触发,evt.stopPropagation()

阻止事件链传递,它同元素中的已绑定的一样的事件不会被触发,evt.stopImmediatePropagation()

注意:在React17以前,都是委托给document容器的(而且只做了冒泡阶段的委托),不可以做多入口,在React17及以后版本,都是委托给#root这个容器(捕获和冒泡都做了委托)只要挂载点的id不一样,可以做多入口,项目可以更复杂一些

执行的顺序:捕获阶段,先合成在原生,冒泡阶段,先原生后合成

2.3 事件绑定的this问题

在类组件事件绑定中会有this指向的问题,需要通过解决this指向来完成对于数据处理

import React, { Component } from 'react'
// 1. bind()方法
// 2. 箭头函数

class App extends Component {
  // 1.箭头函数
  // handleClick = evt => {
  //   console.log(evt, this)
  // }
  // 2.箭头函数写在jsx中
  // handleClick(evt, n) {
  //   console.log(evt, n, this)
  // }

  // 3.bind函数写在jsx中
  // handleClick(n, evt) {
  //   console.log(evt, n, this)
  // }

  // 4.bind函数写在constructor中
  // 如要你在子类中使用到自定义构造函数,则一定要调用一下父类中的构造函数 super()
  // con 就可以有了
  // 构造函数只会执行1次
  constructor(props) {
    super(props)
    this.handleClick = this.handleClick.bind(this)
  }
  handleClick(evt) {
    console.log(evt, this)
  }

  render() {
    return (
      <div>
        {/* 1.自动把event事件对象注入到绑定的事件方法中 */}
        {/* <button onClick={this.handleClick}>改变this指向</button> */}
        {/* 2. 箭头函数写在jsx中,可以手动event和其它参数*/}
        {/* <button onClick={evt => this.handleClick(evt, 100)}>改变this指向</button> */}
        {/* 3.bind函数写在jsx中 bind不但可以改变this,还可以传参数 */}
        {/* <button onClick={this.handleClick.bind(this, 200)}>改变this指向</button> */}
        {/*  */}
        <button onClick={this.handleClick}>改变this指向</button>
      </div>
    )
  }
}

export default App

三、补充

因为学习周期过长,我后面设置一个React学习的专栏,方便学习和管理笔记

猜你喜欢

转载自blog.csdn.net/wsq_yyds/article/details/134753957