react-组件

react 02 - 组件

react中的组件定义方式有两种: 函数组件(无状态/UI组件) 类组件(状态组件/容器组件)
react组件一定要有返回值: 描述页面展示内容的React元素(jsx)

2.1 组件的创建方式

函数组件创建

函数组件本质: 就是一个函数

  • 要满足一下要求:
  1. 函数名首字母要大写,因为jsx要求在组件调用的时候首字母要大写
  2. 函数组件可以接受一个参数,此参数可以是任意类型的数据,用来获取父组件的数据(父子组件传值细说)
  3. 函数必须返回一个jsx,并且jsx必须要有一个顶层元素包裹,也可以返回null, 18之后还可以返回undefined
  4. 定义好的函数一定要通过默认导出: export default 函数名
import React from 'react';
const AppFunction = () => {
  return (
    <div>
      具体的页面内容
    </div>
  );
}

export default AppFunction;

安装插件: js jsx Snippets ===> rfc 可以快速创建模版代码

类组件创建

类组件: 一个类

  • 要求:
  1. 只能使用es6中的类来定义类组件, 类名必须要大写
  2. 此类必须继承一个 React.Component 父类
  3. 此类中必须要重写 render方法,且此方法中必须要返回return一个 jsx/null/undefined
  4. 此类要导出,默认导出
import React from "react";

class App extends React.Component {
   render(){
    return (
      <div>
        <h3>class组件</h3>
      </div>
    )
   }
}

export default App

快捷方式: rcc
函数具有二义性 : 在js中函数可以被当作构造函数通过new来调用,为了和类区分用new.target来判断是否是被new执行的

function Person(){
// 用new.target 来禁用函数被new, 只能当作函数来调用
    if(new.target){
        throw new Error('如果不是通过new执行的就返回undefined, 报次错误')
    }
}

2.2 react父子组件传值

在react中一个函数一个类就是一个组件,区别与vue(一个文件就是一个组件),所以react中可以有多个组件

  • 函数组件的父子间传值
  1. 单向数据流,只能父向子传值,父组件通过在调用子组件的时候添加自定义属性来传值
  2. 子组件接受props参数来获取父组件的传值, 可以直接接受props对象,也可以解构并赋予初值
  3. 子组件中props的值是只读的,不可修改
import React from 'react';

// const Child = (props) => {  // 两种接受props的方式,一种可以直接接受整个props,但有个问题 ,在子组件中使用的时候,要通过对象调用的方式一个一个向外拿
const Child = ({title,num ='第二种:props是个对象,所以可以直接将里面的内容结构出来,还可以结构的同时进行赋初值'}) => {
  // console.log(props)
  return (
    <div>
      {/* <h2>子组件 --{props.title}</h2> */}
      <h2>子组件 --{title} {num}</h2>
    </div>
  );
}
const AppProps01 = () => {
  const title = '我是父组件传过来的标题'
  const num = undefined
  return (
    <div>
      <h2>父组件</h2>
      {/* 
        父组件,他是通过自定义属性的方式向子组件传递数据‘props’
        props是只读属性并且是单向数据流,只能父传递给子 ,子只能读取值,不能修改值
      */}

      {/*  调用自组建 */}
      <Child title={title} num={num}/>
    </div>
  );
}

export default AppProps01;
  • 类组件的父子间传值
  1. props单向数据流,父向子传值,只读属性
  2. 区别去函数组件中的传值方式: 类组件中的自定义属性传值时传递的是在类中定义的成员属性,并且一定要注意加this
  3. 子组件在接受父组件传过来的值时,在render函数中通过this.props成员属性来获取值,props依旧是对象形式的值,可以解构
import React, { Component } from 'react';
// 类组件间传值
class Child extends Component {
  //  类组件中的子组件通过成员属性 this.props 来获取父组件中传过来的值
  //  this.props 是一个对象
  render() {
    // console.log(this.props)
    const {title} = this.props
    return (
      <div>
        <h2>class子组件 -- {title}</h2>
      </div>
    );
  }
}
class AppClassProps01 extends Component {
  // es8以后的成员属性,可以不用在construct中定义
  title = 'class父类中的标题'
  render() {
    return (
      <div>
        <h2>class父组件</h2>
        {/* 在类组件中,获取成员属性和成员方法时 ,一定要通过this来获取 */}
      <Child title={this.title} />
      </div>
    );
  }
}

export default AppClassProps01;

面试题: TS中已经可以用联合属性来解决问题问什么还要用多态?

2.3 react事件

react中的事件分为原生事件和合成事件: 原生就是通过addEventLisenter的js操作进行的,而合成事件: 直接定义在react中的事件会被react处理,语法上有一些不同

要求

  1. 事件的命名严格采用小驼峰命名法
  2. jsx语法中当要传入一个函数作为事件的处理函数时,不能加小括号
  3. 在使用类组件时,还要注意this的指向问题,函数组件中没有这个问题

2.3.1 函数组件

import React from 'react';

const clickHandler2 = () => ()=>{
  console.log('webg == 我必搞')
}

 //定义在组件函数的上面,const var let 没有限制
 var clickHandler = () =>{
  console.log('clickHandler')
 }

const AppFunction01 = () => {
  return (
    <div>
      {/* 
          1, 函数名用小驼峰
          2, 绑定的事件方法不能加小括号,并且如果将执行的函数体放在jsx下面(组件函数的后面)的时候,一定要用function 来定义函数
          3, 因为return会中断后面的代码执行,要用function来声明方法,是的变量提升并赋值      
      */}
      <button onClick={clickHandler}>点击触发事件</button>

      {/*  如果想用小括号,那么事件绑定的是一个函数体就可以 */}
      <button onClick={clickHandler2()}>小括号触发事件</button>
    </div>
  );
}

 // function clickHandler(){
  //   console.log('clickHandler')
  // }
  
  // 小括号触发事件
  // function clickHandler2(){
  //   return function(){
  //     console.log('wgb==我必搞')
  //   }
  // }

  // function clickHandler2(){
  //   return ()=>{
  //     console.log('wbg === 我必搞')
  //   }
  // }

export default AppFunction01;

2.3.2 类组件

思考两个问题 1,this的指向问题 2,call、apply、bind的作用和用法都是什么

import React, { Component } from 'react';

class AppFunction02 extends Component {

  clickHandle1(){
    console.log('clickHandle1')
  }

  clickHandle3(){
    return ()=>{
      console.log('clcikHandler3')
    }
  }
  num = 100
  clickHandle4(){
    return ()=>{
      console.log( this.num )  // 这样不报错
    }
    // console.log(this.num)  // 这样就报错了,为什么, 解决方案可以用bind来改变this指向,为社么可以用bind解决
  }

  clickHandle5(){
    console.log(this.num)
  }

  render() {
    return (
      <div>
        {/*  类组件中的方法定义,不用关注方法的书写顺序是在return前还是return后 */}
        <button onClick={this.clickHandle1}>clickHandle1</button>
        <button onClick={this.clickHandle2}>clickHandle2</button>
        {/*  同样的如果要加小括号,可以用柯里化函数 */}
        <button onClick={this.clickHandle3()}>clickHandler3</button>
        {/* 类组件中有函数的调用有this指向问题 ,  */}
        <button onClick={this.clickHandle4()}>this问题</button> 
        <button onClick={this.clickHandle5.bind(this)}>bind解决this指向问题</button>
      </div>
    );
  }
  clickHandle2(){
    console.log('clickHandle2')
  }
}

export default AppFunction02;

2.3.3合成事件

react将原生事件进行处理:将所有的事件都绑定到挂在节点,目的: 对事件进行统一代理,使dom上不用绑定事件也可以用react挟持事件触发来实现操作,进行性能优化 , 这样可以通过对原事件的优先级定义来确定真是事件的优先级,再进而确定真实事件内触发的更新是什么优先级,最终确定对应更新应在什么时机更新可以解决跨平台问题,抹平浏览器差异
react16之前绑定到body上,缺点: 只能有一个委托节点
16之后委托到挂载节点元素中(root),可以有多个委托节点,如果有多入口的操作应用时,可以分开委托(root1 , root2 ,…)
事件的执行分为: 捕获阶段 --> 目标阶段 --> 冒泡阶段

以类来举例

import React, { Component } from "react";

 class APPs extends React.Component {
  click() {
    console.log("合成-冒泡- click1")
  }
  clickCapture() {
    console.log('合成-捕获-click2')
  }
  componentDidMount() {
    // addEventListener("事件类型" , function(){事件函数} , boolean(true : 捕获阶段执行 / false: 冒泡阶段执行))
    document.getElementById('btn').addEventListener('click', () => {
      console.log('原生- 冒泡 - btnClick')
    }, false)

    document.getElementById('btn').addEventListener('click', () => {
      console.log('原生 - 捕获 - btnClick')
    }, true)

    document.getElementById('root').addEventListener('click', () => {
      console.log('原生 - 冒泡 - 顶层元素click')
    } , false )

    document.getElementById('root').addEventListener('click', () => {
      console.log('原生 - 捕获 - 顶层元素click')
    } , true )

    document.body.addEventListener('click' , () => {
      console.log('原生 - 冒泡 - bodyClick')
    } ,false )

    document.body.addEventListener('click' , ()=>{
      console.log('原生 - 捕获 - bodyClick')
     } , true)
  }
  render() {
    return (
      <div>
        <button id="btn" onClick={this.click} onClickCapture={this.clickCapture} >click</button>
      </div>
    )
  }
}

export default APPs

合成事件和原生事件相结合的具体执行顺序: body-原生-捕获 —> 目标元素上级各节点绑定合成事件的捕获(capture) —> root-原生-捕获 (这两个阶段可以看作委托的合成事件在挂载节点先执行,然后执行挂在节点的原生捕获) —> 目标元素-原生-捕获 —> 目标元素-原生-冒泡 —> 目标元素上级个节点绑定的合成事件的冒泡执行 —> root-原生-冒泡(捕获的回程冒泡)—> body-原生-冒泡
react中合成事件的冒泡函数就是正常默认的函数,捕获阶段的函数为: 莫某Capture事件
原生事件的绑定: addEventLisenter(‘事件类型’ , 事件函数 , true/false 控制冒泡/捕获执行) 默认为false=== 冒泡执行,true===捕获执行

2.3.4 事件阻止

event.stopPropergation() event.stopImmediatePropergation()

  1. 相同点: 都是用来阻止事件传播的
  2. 不同点: stopPropergation: 阻止当前元素之后的父级元素的冒泡事件执行,但是自身的其他事件的冒泡依旧可以执行, 而stopImmediatePropergation在js顺序执行到此事件时, 后面所有的事件都不在执行,包括当前元素自身的冒泡事件, 但书写顺序在此事件之前的冒泡事件不受影响(阻止当前元素中的未执行事件)

2.3.5合成事件模拟 — 实际就是做事件委托

react中为了减少元素增删时,相应绑定事件的销毁和再创建带来的性能损耗,将事件做成了合成事件,其实现原理如下

<div id="root">
       <div id="box">
               <div id="son" οnclick="clickHandler">
               我是一个按钮
           </div>
       </div>
   </div>

<script>
document.getElementById('root').addEventListener('click' , () => {
           // event.target  事件的触发元素
   console.log(event.target  , '查看事件触发元素' ,         event.target.getAttribute('onclick') , '获得元素中的自定义属性')
           // 一种事件委托的方式,将son元素上的事件在root元素上代为执行,
   window[event.target.getAttribute('onclick')]()
       } ,false)

       function clickHandler(){
           console.log('原生事件')
       }
</ script>

这样在数据更新时,执行的事件,只会当做一个属性去获取, 在react 中要拿到原生事件:event.nativeEvent

再js中的function定义的函数,可以看做实在window对象中的添加的属性,属性名是函数名,值是函数体

function eventHandler(){
    console.log('事件')
}
window['eventHandler']()
// 就类似
let obj = {
    a : function b(){
        console.log('shijian')
    }
}
obj['a']()      // 至于是否是你名函数不重要

this指向问题(仅存在于类组件)

问题: this的指向问题,细分为11类,可以在日常积累里面找,这里不做整理


// 在类组件中,需要解决一下this的指向问题,在函数组件中没有this问题
// 解决方案: 
/* 
    1, 剪头函数 : 键头函数中的this是指向当前函数上下文环境中的this
    2, bind 
 */
Class AppThis extends Component {
  num = 100

  addNum1() {
    this.num++
    this.forceUpdate()
  }

  //键头函数解决this指向问题
  addNum2 = () => {
    this.num++
    // 强制试图刷新,此方法只有类组件中有,函数组件要自己写方法实现
    this.forceUpdate()
  }

  addNum3(){
    this.num++
    this.forceUpdate()
  }

  // 在类组件中如果重新定义了构造函数,一定要调用父类中的构造方法
  constructor(props){
    super(props)
    this.addNum4 = this.addNum4.bind(this)
  }

  addNum4(){
    this.num++
    this.forceUpdate()
  }

  addNum5(num) {
    this.num += num
    this.forceUpdate()
  }

  addNum6 = n => event => {
    console.log(n , event )
    this.num += n 
    this.forceUpdate()
  }
  addNum7(n){
    this.num += n
    this.forceUpdate()
  }
  render() {
    console.log('Rerender')
    return (
      <div>
        <h3>{this.num}</h3>
        {/*  1, 箭头函数获取正确的this */}
        <button onClick={() => this.addNum1()}>++num1++</button>
        <button onClick={this.addNum2}>++num2++</button>
        {/*  2, 使用bind改变this */}
        <button onClick={this.addNum3.bind(this)}>++num3++</button>
        {/* 高性能写法 */}
        <button onClick={this.addNum4}>++num4高性能++</button>


        {/*  事件传入参数 */}
        {/* 在react中绑定的方法都会自动注入一个event对象, 此方法的事件对象, 里面包含的是事件信息 */}
        {/*  1, 还是用箭头函数 */}
        <button onClick={evt => this.addNum5(10)}>++参数5++</button>
        <button onClick={this.addNum6(100)}>++参数6++</button>
        {/* 2, 使用bind传参 */}
        <button onClick={this.addNum7.bind(this , 10)}>++num7++</button>
      </div>
    );
  }
}
  • 26
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值