学习目标

独立完成React项目;

掌握淘系React解决方案;

掌握常见的面试题;

预计完成天数46天,共计七周;

笔记代码地址2023年最新珠峰React全家桶

课程视频链接:2023年最新珠峰React全家桶【基础-进阶-项目-源码-淘系-面试题】

Create-react-app基础操作

当下以及未来的前端开发一定是组件化/模块化。

  1. 有助于团队协作开发
  2. 便于组件的复用:提高开发效率、方便后期维护、减少页面中的冗余代码

如何划分组件?

业务组件:针对项目需求封装的

  • 普通业务组件:复用性低,只是单独拆选出来的一个模块;
  • 通用业务组件:具备复用性

功能组件:适用于多个项目(列如:UI组件库中的组件)

  • 通用功能组件

组件话开发必然带来工程化,基于webpack/vite/turbopack等工具实现组件的合并、压缩、打包等。

安装Create-react-app

React官方提供的脚手架,便于创建React项目。

全局安装create-react-app脚手架:

1
npm i create-react-app -g

检查 create-react-app版本。

1
create-react-app --version

使用Create-react-app创建项目基于脚手架床创建React工程化项目:

  • 项目名称要遵循npm包命名规范:使用”数字、小写字母、_”命名;
1
create-react-app 项目名称

一个React项目中,默认会安装:

  • react:React框架的核心;
  • react-dom:React视图渲染的核心【基于React构建WebApp(HTML页面)】
  • react-scripts:一组脚本,帮忙运行或打包项目;

在项目根目录的package.json中定义了许多项目参数:

image-20230215005200864

暴露webpack配置,一旦暴露就不能还原。在暴露之前建议提交到git。

1
yarn eject

脚手架的进阶应用

image-20230215123313020

image-20230215124615696

基于环境变量修改端口号

image-20230215125037256

修改兼容列表

image-20230215125117135

处理Proxy跨域

在src目录中,新建setupProxy.js,安装 http-proxy-middleware

1
yarn add http-proxy-middleware
image-20230215130005785

MVC模式和MVVM模式

React是Web前端框架

  1. 目前市面上比较主流的前端框架
  • React
  • Angular
  • Vue

主流的思想:不再直接操作DOM,而是改为“数据驱动思想”。

操作DOM思想:

  • 操作DOM比较消耗性能,主要原因是:可能导致DOM重拍
  • 操作起来也相对麻烦一些

数据驱动思想:

  • 操作数据,框架会按照相关的数据,让页面重新渲染
  • 框架底层构建虚拟DOM->真实DOM的渲染体系
  • 有效避免DOM的重排/重绘

优点:

  • 开发效率更高
  • 性能更优
  1. React框架才用的是MVC体系;Vue采用的是MVVM体系

MVC:model数据层+view视图层+controler控制层

image-20230215131821095

数据驱动视图的渲染,单向驱动;

MVVM:model数据层+view视图层+viewModel数据/视图监听层

JSX语法使用上的细节

JSX:JavaScript and xml(HTML) 把JS和HTML标签混合在一起

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React from 'react';  // React语法核心
import ReactDOM from 'react-dom/client'; // 构建HTML(WebApp)的核心
import App from "./App";

// 获取页面中的#root的容器,作为“根”容器,注意:根容器不能使用HTML/BODY元素
const root = ReactDOM.createRoot(document.getElementById("root"));
// 基于render方法渲染我们编写的视图,把渲染后的内容,全部插入到#root元素中
root.render(
<>
{/*空标签,不会增加层结构 React.Fragment,既保证了只有一个根节点,又不增加一个HTML层级结构*/}
<div>hello world</div>
{/* 每一个构建的视图是能有一个根节点
<button>取消注释会报错</button>*/}
</>
);

JSX的具体应用

{}胡子语法中嵌入不同的值,所呈现出来的特点

  • number/string:值是啥,渲染出来啥

  • bool/null/undefined/Symbol/Bigint:渲染内容是空

  • 不支持渲染:普通对象

  • 数组对象:把每一项拿出来,分别渲染

  • 函数对象:不支持在{}中渲染,但是可以作为函数组件,作为<componment/>渲染

元素设置样式

行内样式,要以对象的形式设置

1
2
3
4
5
6
7
root.render(
<>
<div style={{
color:'red',
}}>hello world</div>
</>
);

设置样式类名:要把class替换为className

1
<h2 className="box"></h2>

需求一:基于数据的值,来判断元素的显示隐藏

1
2
3
4
5
6
7
8
9
10
 <div>
{/*控制元素显示不显示,本身已经渲染出来了*/}
<div style={{
display: this.flag ? "block" : "None"
}}>显示
</div>
{/*控制元素渲染或不渲染*/}
{this.flag ? <button>渲染/不渲染</button> : null}
<button onClick={this.handleClick}>点击隐藏/显示</button>
</div>

需求二:从服务器获取了一组列表数据,循环动态绑定相关的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import React from 'react';

class App extends React.Component {
constructor(props) {
super(props);
this.state = {
flag: false,
data: [],
};
}

handleClick = (event) => {
this.flag = !this.flag;
this.setState({flag: this.flag});
}

componentDidMount() {
// 模拟从服务请求的数据
this.setState({
data: [
{id: 1, title: "藏器于身"},
{id: 2, title: "潜龙勿用"},
{id: 3, title: "似水流年"},
{id: 4, title: "万物生长"},
],
})

}

render() {
return (
<div>
{/*控制元素显示不显示,本身已经渲染出来了*/}
<div style={{
display: this.flag ? "block" : "None"
}}>显示
</div>
{/*控制元素渲染或不渲染*/}
{this.flag ? <button>渲染/不渲染</button> : null}
<button onClick={this.handleClick}>点击隐藏/显示</button>

<ul>
{this.state.data.map((item, index) => {
// 循环创建的元素一定要设置key属性,属性值是本次循环中的唯一值 [优化DOM-DIFF]
return <li key={index + 1}>{index + 1}.{item.title}</li>;
})}
</ul>
</div>

)
}
}

export default App;

JSX底层渲染机制[创建虚拟DOM]

  1. 关于JSX的底层处理机制
  • 第一步:把JSX编译为虚拟DOM对象(virtualDOM)

虚拟DOM:框架内部构建大一套对象体系(对象的相关成员都是React内部规定的),基于这些属性描述出我们所构建视图中的DOM节点的相关特征。

@1 基于babel-prest-react-app把JSX编译为React.createElement(…)这种格式!!只要是元素节点,必然基于createElement进行处理:

1
2
3
4
React.createElement(ele, props, ...children)
+ ele:元素标签名【或组件】
+ props:元素的属性集合【如果没有设置过任何属性,则为null
+ children:第三个及以后参数,都是当前元素的子节点

@2 再把createElement方法执行,创建出virtualDOM虚拟DOM对象,【也有称之为:JSX元素、JSX对象、ReactChild对象】

1
2
3
4
5
6
7
8
9
10
11
vitrualDOM = {
$$typeof: Symbol(react.element),
ref: null,
key: null,
type: 标签名 【或组件】
// 存储了元素的相关属性&&子节点信息
props: {
元素的相关属性,
children: 子节点信息 [没有子节点则没有这个信息、属性可能是一个值、也可能是一个数组]
}
}
  • 第二步:把构建的virtualDOM渲染为真实DOM

真实 DOM:浏览器页面中,最后渲染出来,让用户看见的DOM元素。

基于ReactDOM中render方法处理

1
2
3
4
5
6
7
8
9
10
11
// V16
ReactDOM.render(
<>...</>
document.getElementById('root')
);

// V18
const root = ReactDOM.creatRoot(document.getElementById('root'));
root.render{
<>...</>
};

补充说明:第一次渲染页面是直接从virtualDOM->真实DOM;但是后期视图更新的时候需要经过一个DOM-DIFF的对比,计算出补丁包PATCH(两次视图差异的部分),把PATCH补丁包进行渲染。

image-20230217005329246

[Babel 中文网 · Babel - 下一代 JavaScript 语法的编译器 (babeljs.cn)](https://www.babeljs.cn/repl#?browsers=defaults%2C not ie 11%2C not ie_mob 11&build=&builtIns=false&corejs=3.21&spec=false&loose=false&code_lz=DwEwlgbgfAUABHYALAjFAEgUwDbYPZwDueATtiMAPSqxXjRA&debug=false&forceAllTransforms=false&shippedProposals=false&circleciRepo=&evaluate=false&fileSize=false&timeTravel=false&sourceType=module&lineWrap=true&presets=env%2Creact%2Cstage-2&prettier=false&targets=&version=7.20.15&externalPlugins=&assumptions={})

image-20230217005805085

函数组件的底层渲染机制

React中的组件化开发:

  1. 函数组件
  2. 类组件
  3. Hooks组件:在函数组件中使用React Hooks函数

函数组件

创建一个函数返回JSX元素:

1
2
3
4
5
6
const DemoOne = function DemoOne(){
return <div className="demo-box">
我是DEMO-ONE
</div>;
};
export default DemoOne;

在Src目录中创建一个xxx.jsx文件,创建一个函数,让函数返回JSX视图【或者JSX元素、virtualDOM虚拟DOM对象】,这就是创建了一个”函数组件”。

1
2
<Componment/> //单闭合调用
<Componment>...</Componment> //双闭合调用

组件的命名:一般采用PascalCase(大驼峰命名方法)

调用组件,可以给组件设置(传递)各种属性:

1
<DemoOne title="我是标题" x={10} data={100, 200} style={{fontSize: '20px'}}/>

如果设置的属性不是字符串,需要用{}嵌套。

基于双闭合调用,还可以传递子节点:

1
2
3
<Componment>
<span>hello</span>
</Componment>

props属性的细节知识

对象的设置规则:

  • 冻结对象:Object.freeze(obj),检测对象是否被冻结:Object.isFrozen(obj)=>true/false;被冻结的对象不能被修改成员值、不能修改成员值、不能删除成员值;
  • 密封对象:Object.seal(obj);检测对象是否密封Object.isSealed(obj);不能新增、删除;但是可以读取、修改;
  • 扩展对象:把对象设置为不可扩展:Object.preventExtension(obj);检测是否可扩展:Object.isExtensible(obj);被设置不可扩展的对象:除了不能新增成员,其余的操作都可以处理;被冻结的对象是密封、也是不可扩展;
  1. 属性props的处理
  • 调用组件:传递进来的属性是“只读”的【原理:props对象被冻结了】
    • Object.isFrozen(props)=>true
    • 获取:props.xxx
    • 修改:props.xxx=xxx =>报错
1
2
3
4
5
6
7
const DemoOne = function DemoOne(props){
let {className, style, title } = props;
return <div className={`demo-box ${className}`} style={style}>
<h2>{title}</h2>
我是DEMO-ONE
</div>;
};
  • 作用:父组件(index.jsx)调用子组件(DemoOne.jsx)的时候,可以基于属性,把不同的信息传递给子组件,子组件接收响应的属性值,呈现不同的效果,让组件复用性更强;

  • 虽然对传递进来的属性不能直接修改,但是可以做一些规则校验

    • 设置默认值

      1
      2
      3
      4
      /*通过把函数当做对象,设置静态的私有属性方法,【把函数当做对象】,用来给其设置属性的规则*/
      DemoOne.defaultProps = {
      x: 0
      }
    • 设置其他规则,列如:数据值格式、是否必传…【依赖于官方的一个插件:prop-types

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      import PropTypes from 'prop-types';
      函数组件.propTypes = {
      // 类型是字符串,必传
      title: PropTypes.string.isRequired,
      // 类型是数字
      x: PropTypes.number,
      // 多种校验规则中的一个
      y: PropTypes.oneOfType([
      PropTypes.number,
      PropsTypes.bool,
      ])
      };

      传递进来的属性,首先会经历规则校验,不管校验成功还是失败,都会把属性给形参props,只不过如果不符合设定规则,控制台会抛出错误(不影响属性值的获取)

    • 如果就想把传递的属性值进行修改:

      • 把props中的某个属性赋值给其他内容【列如:变量、状态】

      • 不直接操作props.xxx=xxx,但是可以修改变量/状态值

        1
        2
        let y = props.y;
        y = 1000;

React中的插槽处理机制

image-20230219224955930
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let {className, style, title,x, children } = props;

children = React.Children.toArray(children);

let headSolt = [];
let headerSlot = [],
footerSlot = [],
defaultSlot = [];
children.forEach(child=>{
// 传递进来的插槽信息,都是编译为virtualDOM后传递进来的[而不是传递的便签]
let {slot} = child.props;
if (slot == 'header'){
headerSlot.push(child);
}else if(slot =='footer'){
footerSlot.push(child);
}else {
defaultSlot.push(child);
}
});

image-20230219231152425

初步尝试组件的封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import PropTypes from "prop-types";
import React from "react";

const Dialog = function (props) {
// 获取传递的属性和插槽信息
let {title, content, children} = props;
children = React.Children.toArray(children);

return <div className="dialog-box" style={{
width: '300px',
}}>
<div className="header" style={{
display:'flex',
justifyContent: 'space-between',
alignItems: 'center',
}}>
<h2 className="title">{title}</h2>
<span>X</span>
</div>
<div className="main">
{content}
</div>
{
children.length > 0 ?
<div className="footer">
{children}
</div> : null
}
</div>;
}

//属性规则校验
Dialog.defaultProps = {
title: '温馨提示'
};

Dialog.propTyprs = {
title: PropTypes.string,
content: PropTypes.string.isRequired
};

export default Dialog;

静态组件和动态组件

函数组件是静态组件,第一次渲染组件把函数执行:

  • 产生一个私有的上下文
  • 把解析出来的props(含children)传递进来 但是被冻结了
  • 对函数返回JSX元素 virtualDOM进行渲染

当我们点击按钮时,会把绑定的小函数执行:

  • 修改上级上下文中的变量
  • 私有变量发生了改变
  • 但是视图不会更新

也就是说函数组件在第一次渲染完成后,组件中的内容,不会根据组件内的某些操作,再进行更新,所以称为静态组件。

除非在父组件中,重新调用这个函数组件 【可以传递不同的属性信息】

动态组件

类方法实现:

1
2
3
4
5
6
7
8
9
import React from "react";

// 创建一个构造函数,要求必须继承于React.Component这个类

class Vote extends React.Component {

}

export default Vote;

HOOKS实现;

ES6中class语法和继承的原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Parent{
// new 的时候,执行构造函数
constructor(x, y) {
this.total = x + y;
}
num = 200; // 等价于this.num = 200;
getNum = ()=>{
// 箭头函数没有自己的this,所用到的是宿主环境中的this
console.log(this); // this->当前创建的实例
};
sum(){
// 类似于sum = function sum(){}不是箭头函数
// 它是给Parent.prototype上设置公共的方法【sum函数是不可枚举的】
};

// 把构造函数当做一个普通对象,为其设置静态私有属性方法 Parent.xxx
static avg = 1000;
static average(){

}

};

Parent.prototype.y = 2000; // 在外部手动给构造函数原生上设置公共的属性
let p = new Parent(10, 20);
console.log(p);

类组件第一次渲染的底层逻辑

render函数在渲染的时候,如果type是:

  • 字符串:创建一个标签
  • 普通函数:把函数执行,并且把props传递给函数
  • 构造函数:把构造函数基于new执行,也就是创建类的一个实例,也会把解析出来的props传递过去,

每次调用一次类组件都会创建一个单独的实例。

把在类组件中编写的render函数执行,把返回的jsx virtualDOM当做组件视图进行渲染。

从调用类组件new Vote({…})开始,类组件内部发生的事情:

  1. 初始化属性 && 规则校验

    先规则校验,校验完毕后,再处理属性其他操作

    方案一:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import PropTypes from "prop-types";
    static defaultProps = {
    // 属性校验规则
    num: 0
    }
    static propTypes = {
    title: PropTypes.string.isRequired,
    }
    constructor(props){
    super(props); // 会把传递进来的属性挂载到this实例
    console.log(this.props); // 获取到传递的属性
    }

    方案二:

    即使不在constructor中处理,【或者constructor都没写】,在constructor处理完毕后,React内部也会把传递的props挂载到实例上;所以在其他的函数中,只要保证this是实例,就可以基于this.props获取传递的属性。

    • 同样this.props获取的属性对象也是被冻结的(只读的)Object.isFrozen(this.props)->true
    • 设置规则校验
  2. 初始化状态

    状态:后期修改状态,可以触发视图的更新

    需要手动初始化,如果没有做相关的处理,则默认会往实例上挂载一个state,初始值是null=>this.state=null

    1
    2
    3
    4
    state = {
    supNum: 0,
    oppNum: 3
    }

    修改状态,控制视图更新

    @1this.setState({})既可以修改状态,也可以让视图更新【推荐使用这种方法】

    @2 this.forceUpdate()强制更新

  3. 触发周期函数componmentWillMount周期函数(钩子函数)

    钩子函数:在程序运行到某个阶段,可以基于提供一个处理函数,让开发者在这个阶段做一些自定义的事情,一般也叫生命周期函数

    此周期函数,目前是不安全的,虽然可以用,但是未来可能要被移除,不建议使用。

类组件更新的底层逻辑

当修改了相关状态,组件会更新;

第一种组件更新的逻辑是组件的内部状态做了修改,组件更新:

  1. 触发周期函数 shouldComponentUpdate

    1
    2
    3
    4
    5
    6
    7
    8
    9
    shouldComponentUpdate(nextProps, nextState) {
    // nextState:存储要修改的最新状态
    // this.state:存储的还是修改前状态 [此时状态还没有改变]
    console.log(this.state, nextState)
    // 此周期函数需要返回一个true或false
    // 返回true:允许更新,会继续执行下一个操作
    // 返回false:不允许更新,接下来啥都不处理
    return true;
    }
  2. 触发 componmentWillUpdate周期函数:更新之前

    此周期函数也是不安全的

    在这个阶段,状态还没有被修改

  3. 修改状态值/属性值 让this.state.xxx改为最新的值

  4. 触发render周期函数:组件更新

    按照最新的状态/属性,把返回的JSX编译为virtualDOM

    和上一次渲染的virtualDOM进行对比,

    把差异的部分进行渲染为真实DOM

  5. 触发componmentDidUpdate周期函数:组件更新完毕

    特殊说明:如果我们是基于this.forceUpdate()强制更新视图,会跳过shouldComponentUpdate周期函数的校验,直接从componmentWillUpdate开始更新(也就是说视图一定会更新);

第二种,组件的父组件更新,触发子组件更新

  1. 触发componementReceiveProps周期函数:接收最新属性之前

    周期函数本事是不安全的

    1
    2
    3
    4
    5
    UNSAFE_componmentReceiveProps(nextProps){
    // this.props:存储之前的属性
    // nextProps:传递进来的最新属性值
    console.log('componmentWillReceiveProps:', this.props, nextgProps);
    }
  2. 触发周期函数 shouldComponentUpdate

​ ….同上

​ 父子组件嵌套,处理机制上遵循深度优先原则:父组件在操作中,遇到子组件,一定是把子组件处理完,父组件才能继续处理:

  • 父组件第一次渲染

    父willMount——>父render [子willMount ——>子 render ——>子didMount] ——>父didMount

  • 父组件更新:

    父shouldUpdate——>父willUpdate——>父render[子willReceiveProps ——>子 shouldUpdate ——>子willUpdate ——> 子render ——>子 didUpdate] ——>父didUpdate

  • 父组件销毁:

    父willUmount——>处理中[子willUnmount——>子销毁] ——>父销毁

周期函数示意图

image-20230224231412476

PureComponment和Componment的区别

PureComponent会给类组件默认加一个shouldComponment周期函数。

  • 在此周期函数中,会对新老的属性/状态进行浅比较
  • 如果经过浅比较,发现属性和状态并没有改变,则返回false

image-20230225142842527

有关REF操作的解读

受控组件:基于修改数据/状态,让视图更新,达到需要的效果 【推荐】

非受控组件:基于ref获取DOM元素,操作DOM元素,来实现需求和效果

基于ref获取DOM元素的方法

  1. 给需要获取的元素设置ref='xxx',后期基于this.refs.xxx去获取相应的DOM元素 【不推荐使用这种用法】

<h2 className="title" ref="titleBox">温馨提示</h2>

​ 获取:this.refs.titleBox

  1. 把ref属性设置为一个函数

    ref={x=>thhis.xxx=x}

    • x是函数的形参:存储的就是当前的DOM元素
    • 然后获取的DOM元素”x”直接挂载在实例的某个属性上(列如box2)

原理:在render渲染的时候,会获取virtualDOM的属性

  • 如果属性值是一个字符串,则会给this.refs增加这样一个成员,成员值就是当前的值
  • 如果属性值是一个函数,则会把函数执行,把当前DOM传递给这个函数【x->DOM元素】,而在函数执行的内部,一般会把DOM元素直接挂到实例的某个属性上
  1. 基于React.createRef()方法创建一个REF对象->{current:null}

    ref={REF对象}

    获取:this.xxx.current

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import React from "react";


class Demo extends React.Component {
box3 = React.createRef(); //this.box3 =xxx

render() {
return <div>
<h2 className="title" ref="titleBox">温馨提示</h2>
<h2 className="title" ref={x => this.box2 = x}>友情提示</h2>
<h2 className="title" ref={this.box3}>严重警告</h2>
</div>
}

componentDidMount() {
// 第一次渲染完毕VitualDOM已经变为真实DOM;此时我么可以获取需要操作的DOM元素
console.log(this.refs);
console.log(this.refs.titleBox);
console.log("box2" + this.box2);
console.log("box3" + this.box3.current);

}
}

export default Demo;

给元素标签设置ref ,目前:获取对应的DOM元素

给类组件设置ref,目的:获取当前组件的实例,后续可以获取子组件中的相关信息

给函数组件设置ref直接报错,但是可以让其配合React.forwardRef实现ref的转发

目的:获取函数子组件内部的某个元素

关于setState的进阶处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import React from "react";

class Demo extends React.Component {
state = {
x: 10,
y: 5,
z: 0
}

handle = () => {
// this->实例 【宿主环境】
let { x, y, z } = this.state;
// 同时修改三个状态值:只会触发一次视图更新
this.setState({
x: x + 1,
y: y + 1,
z: z + 1
}, () => {
console.log('更新完毕x');
})
}

componentDidUpdate() {
console.log('视图更新完毕');
}

render() {
console.log('视图渲染:RENDER')
let { x, y, z } = this.state;
return <div>
x:{x} - y:{y} - z:{z}
<br />
<button onClick={this.handle}>按钮</button>
</div>;
}
};

export default Demo;

/*
this.setState([partialState], [callback]])
[partialState]:支持部分状态修改
this.setState({
x: 100 // 不论总共有多少状态,只修改了x,其余的状态不动
})
[callback]:在状态更改,视图更新完毕后触发执行
发生在componmentDidUpdate周期函数之后[DidUpdate会在任何状态更改后都触发执行;而回调函数方式,可以再指定状态更新后处理
一些事情;]
特殊:即便基于shouldComponmentUpdate阻止了状态/视图更新,DidUpdate周期函数肯定不会执行了,但是我们设置的这个callback回调函数
依然会被触发执行!!
*/

image-20230303221808879

image-20230303223336884

合成事件

合成事件是围绕浏览器原生事件,充当浏览器包装器的对象;它们将不同浏览器的行为合并为一个API,这样做是为了确保事件在不同浏览器中显示一致的属性。

合成事件的基本操作

在JSX元素上,直接基于onXxx={函数}进行事件绑定。

浏览器标准事件,在React中大部分都支持。

基于React内部的处理,如果我们给合成事件绑定一个普通函数,当事件行为触发,绑定的函数执行,方法中的this会是undefined

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import React from 'react'


class Demo extends React.Component {

handle() { //Demo.prototype => Demo.prototype.handle=function handle() {}
console.log(this); // undefined ???
}

render() {
return <div>
<button onClick={this.handle}>按钮</button>
</div>
}
}

export default Demo;

使用bind绑定函数,可以解决这个问题:

1
2
3
4
5
render() {
return <div>
<button onClick={this.handle.bind(this)}>按钮</button>
</div>
}

这时候的this就是当前实例对象。

当然还有更方便的方法,那就是使用箭头函数定义事件触发函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import React from 'react'


class Demo extends React.Component {

handle = () => {
console.log(this); // Demo...
}

render() {
return <div>
<button onClick={this.handle}>按钮</button>
</div>
}
}

export default Demo;

合成事件对象

在事件函数中输出event事件变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import React from 'react'


class Demo extends React.Component {

handle = () => { //Demo.prototype => Demo.prototype.handle=function handle() {}
console.log(this); // undefined ???
}

handle1 = (ev) => {
console.log(this);//实例
console.log(ev); //SyntheticBaseEvent 合成事件对象
}
render() {
return <div>
<button onClick={this.handle}>按钮</button>
<button onClick={this.handle1}>按钮1</button>
</div>
}
}

export default Demo;

React内部经过特殊处理,把各个浏览器的事件对象统一化后,构建的一个事件对象SyntheticBaseEvent。合成事件对象包含了浏览器内置事件对象中的事件和方法

  • clientX/clientY
  • pageX/pageY
  • target
  • type
  • preventDefault
  • stopPropagation
  • nativeEvent:基于这个属性,可以获取浏览器内置的原生事件对象

给事件函数传递参数,使用bind函数处理,基于bind函数处理会把事件对象以最后一个实参传递给函数。

1
2
3
4
5
6
7
8
9
10
11
handle2 = (x,y, ev)=> {
console.log(x, y, ev);
}

render() {
return <div>
<button onClick={this.handle}>按钮</button>
<button onClick={this.handle1}>按钮1</button>
<button onClick={this.handle2.bind(null, 1, 2,)}>按钮2</button>
</div>
}

事件及事件委托

image-20230309224908756

阻止事件传播:

ev.stopPropagation();

image-20230310203609837

image-20230311112115822

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import React from 'react'


class Demo extends React.Component {

render() {
return <div className='outer'
onClick={() => {
console.log('outer冒泡')
}}
onClickCapture={() => {
console.log('outer捕获')
}}>

<div className='inner'
onClick={() => {
console.log('inner冒泡')
}}
onClickCapture={() => {
console.log('inner捕获')
}}>

</div>
</div>
}
}

export default Demo;

React中合成事件处理原理

绝对不是给当前元素基于addEventListener单独做的事件绑定,React中的合成事件,都是基于“事件委托”处理的

在React17以后版本,都是委托给#root这个容器,捕获和冒泡都做了委托;

在React17版本以前,都是委托给doucment容器,而且只做了冒泡阶段的委托;

对于没有实现事件传播机制的委托,才是单独做的事件绑定;

循环事件绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import React from "react";

class Demo extends React.Component {
state = {
arr: [{
id: 1,
title: '新闻'
}, {
id: 2,
title: '体育'
}, {
id: 3,
title: '娱乐'
}]
};

handle(item){
console.log(item.title)
};

render() {
let { arr } = this.state;
return <div>
{arr.map(item => {
let { id, title } = item;
return <span key={id} style={{
padding: '5px 15px',
marginRight: 10,
border: '1px solid #DDD',
cursor: 'pointer'
}}
onClick={this.handle.bind(this, item)}>
{title}</span>
})}
</div>
}
}

export default Demo;

Redux的基础操作和思想

image-20230311122232593

除了Redux这5步操作之外,还需要一些其他的知识做配合。