小试牛刀 React

226 阅读20分钟

什么是 react

react 是一个用于构建用户界面的 JavaScript 库(声明式,基于组件的视图库),它本身是一个库为什么我们还要称它为框架?是因为之后 react 结合 reduxreact-router 之后具有开发常规的单元应用程序;

有什么特点

  • 虚拟 DOM
    • web 开发中总是要将 DOM 的变化实时展现在 UI 上,这时我们就要对 DOM 操作,所以对 DOM 复杂且频繁的操作会使性能成为问题;
    • 现在如何进行高性能复杂 DOM 的操作也是衡量前端开发人员技能的重要指标;
    • react 在浏览器中用 JavaScript 实现了一套 DOM Api,底层用 DOM Diff 算法来更新有差异的部分,大大提升了性能。
  • 组件化
    • 所谓组件是指完成某个特定的功能,独立可重用的代码;
    • 目前的开发人员都会基于组件将庞大的应用分解成若干个组件,每个组件只负责对应的功能;
    • 优点: 可重用;
  • 声明式
    • 相当于面向数据编程,只要把数据建立好,react 根据数据自动构建网站
    • 采用声明规范,用 ReactDOM.render() 来声明
  • 灵活
    • 能够和已知的库或框架很好的配合:reduxmobileX
  • 高效
    • 通过对 DOM 的模拟,更少的操作 DOM
  • 面向对象化
    • 封装组件内部的各类组件和数据流动,可以将其看成整体的处理,而不是部分

Vue & React 如何选择

  1. React 相对于 Vue 来说,React 的灵活性和协作性更好一些
  2. Vue 有着丰富的 Api,实现起来简单快速
  3. Vue 的上手成本,学习成本相对较低
  4. 大型复杂项目还是推荐 React

构建基本 React 项目

前置环境:node, create-react-app

若没有 node,可跳转官网nodejs.org/zh-cn/downl…下载长期支持版,下载完成傻瓜式安装即可;create-react-app 安装如下

# 执行node和npm版本查看,出现如下,说明安装成功
$: node -v
v14.15.4
$: npm -v
6.14.7
# 全局安装react脚手架构建工具
$: npm i -g create-react-app
C:\Users\A\AppData\Roaming\npm\create-react-app -> C:\Users\A\AppData\Roaming\npm\node_modules\create-react-app\index.js
+ create-react-app@4.0.1

环境安装完成再执行 create-react-app react-demo 命令,就在当前目录下自动创建对应react-demo目录,下面就是进入对应文件夹执行操作:

# 进入react-demo目录
$: cd react-demo
# 若是没有node_modules目录则执行此操作下载依赖,否则跳过
$: npm i
# 执行内部应用
$: npm start

执行完成后,我们就可以看的页面了,若是想了解的更全面跳转React 官网

可用脚本

npm start

在开发模式下运行应用程序。打开 http://localhost:3000 在浏览器中查看。 如果进行编辑,页面将重新加载。您还将在控制台中看到任何错误。

npm test

在交互式监视模式下启动测试运行程序。

npm run build

生成用于生产的应用程序到 build 文件夹。它在生产模式下正确捆绑了 React,并优化了构建以获得最佳性能。 最小化构建,文件名包含哈希。如有必要,可以启用类名和函数名以进行概要分析。

npm run eject

我们打开 package.json 文件,会发现 script 下有个 eject 命令 这个是对构建工具和配置选择不满意并且自己的 webpack 有一定的熟悉,则可以执行 eject注意:这是单向操作。一旦 eject,将无法返回)。 此命令将从项目中删除单个构建依赖项,然后会将所有配置文件和依赖项复制到 package.json,目录中会多出 scripts 和 config 目录。

hello World

新建项目后,这里面的目录结构并不满足我们的开发需求,所以我们需要做一些整改:

// 删除src下不必要的文件,css文件移入静态文件目录中
├─src
|  ├─App.js
|  ├─index.js
|  ├─logo.svg
|  ├─tests // 测试文件
|  ├─components // 放组件
|  ├─assets // 静态文件
|  |   ├─js
|  |   ├─images
|  |   ├─css
|  |   |  ├─App.css
|  |   |  └index.css
├─scripts
├─public
├─config

接下来我们只要修改 src 目录下 App.js 文件,就可以在页面上看到Hello World了:

import React from "react";
import "./assets/css/App.css";

// 根组件
class App extends React.Component {
  render() {
    return (
      <div className="App">
        <h2>Hello World</h2>
      </div>
    );
  }
}

export default App;

JSX 详解

什么是 JSX

JSX(Javascript and XML) 是 JavaScript 的一种可拓展的标记性语言。React 推崇的是一切皆 JS(all in JS),并不是强制使用 JSX,但还是推荐使用。

从代码上看,HTML 在 JS 文件中,刚开始,可能会认为它违背了样式、行为、动作分离的原则;但我们仔细想一下,其实 Html、css、js 只是分文件进行管理,并没有逻辑上的分离。

那现在来看,为什么我们不能把它们放在一个文件中管理,实际上,也可以看作是宏观代码的封装,一个组件就是一个模块,模块中实现了样式功能,所以说实现 JSX 并不是一种倒退,而是一种语法糖

说明:JSX 是 React 中一项技术,并不是全新的语言。

import React from "react";
import "./assets/css/App.css";

function App() {
  return (
    <div className="App" tabIndex="1" dataId="1">
      <h2>Hello World</h2>
    </div>
  );
}

export default App;

JSX 中元素的样式属性名为什么只能用className属性?因为classjavascript 中是关键字,所以不得已只能用 className,当然其它属性也是为了遵循 javascript 的的规则使用驼峰命名法或者规避关键字使用其它命名。

JSX 原理

页面中的 DOM 元素结构都可以使用 javascript 对象描述的,例如标签名、属性、子元素,事件对象等信息;在 JS 中一切皆对象,对象的特点是有属性和方法。下面继续看代码:

<div title="Hello">
  <h2 className="App">Hello World</h2>
</div>;
// webpack+babel编译后------>
React.createElement(
  "div",
  { title: "Hello" },
  React.createElement("h2", { className: "App" }, "Hello World")
);
/**
 * React.createElement(标签名,属性对象,子元素)
 */

JSX 语法的本质:还是以 React.createElement 的形式来实现的,并没有直接把用户写的 HTML 代码,渲染到页面上;

这也让我们联想到 JS 中的document.createElement,这和 React 方法有着异曲同工之妙;从更深层次来说,它们都是基于原型对象构建出来的,React 本身就是一个实例化的对象。

从上述代码中看,使用 JS 对象代码会比较繁琐,结构也不清晰,所以 JSX 写法更加形象直观。

JSX 具体用法

  • 标签
    1. DOM 标签:div,p 等,首字母必须小写;
    2. 自定义标签(组件),首字母必须大写;
  • 内联样式
    • 内联样式使用对象,属性名为驼峰命名;
  • 表达式
  • 标签属性
    • 标签属性使用驼峰命名:onClick、tabIndex,className 等;
  • 注释
  • 拓展运算符

我们就以 App.js 文件举例:

import React from "react";
import "./assets/css/App.css";

// 点击事件调用
function handled() {
  console.log("点击触发");
}

// 组件传值
function User(props) {
  return (
    <div>
      <h2>我是user组件</h2>
      <p>
        {props.name} --- {props.age}
      </p>
    </div>
  );
}

function Component1() {
  const divTitles = "我是组件"; // 属性变量
  const divStyles = {
    // 内联样式
    color: "red",
    fontSize: "26px",
  };
  let flag = true; // 表达式变量

  const props = {
    // 使用拓展运算符传入User组件
    name: "某某",
    age: 22,
  };
  return (
    <div style={divStyles} className="App" title={divTitles}>
      <h3>
        {divTitles} Component1 {flag ? " 表达式在这" : "无"}
      </h3>
      <p onClick={handled}>p标签</p>
      {/* 这是注释规范 */}
      <User {...props} />
    </div>
  );
}

class App extends React.Component {
  render() {
    return (
      <div className="App">
        <h2>Hello World</h2>
        <Component1 />
      </div>
    );
  }
}

export default App;

虚拟 DOM 与非 DOM 属性

什么是虚拟 DOM(virtual DOM)

在 React 中,render 执行的结果得到的并不是真正的 DOM 节点,结果仅仅是轻量级的 JavaScript 对象,我们称之为 virtual DOM。

虚拟 DOM 是 React 的一大亮点,具有 batching(批处理) 和高效的 Diff 算法。

这让我们可以无需担心性能问题而 “毫无顾忌” 的随时 “刷新” 整个页面,由虚拟 DOM 来确保只对界面上真正变化的部分进行实际的 DOM 操作。在实际开发中基本无需关心虚拟 DOM 是如何运作的,但是理解其运行机制不仅有助于更好的理解 React 组件的生命周期,而且对于进一步优化 React 程序也会有很大帮助。

官网也对 Virtual DOM 做了解释说明:zh-hans.reactjs.org/docs/faq-in…

动画版虚拟 DOM 移至 yylifen.github.io/sundries-tr…

非 DOM 属性以及如何使用

dangerousSetInnerHTML

在 React 中,dangerousSetInnerHTML是 DOM 元素下的一个属性;

按照官方的文档说明,dangerousSetInnerHTML在浏览器中是替代了innerHTML的存在;也就是说在 React 里,想要在外部去插入 HTML 内容,就必须使用dangerousSetInnerHTML方法。

现在我们来看一下传统的innerHTMLdangerousSetInnerHTML代码实现: innerHTML: 实现 codepen.io/qiuliang1/p… dangerousSetInnerHTML: 实现 codepen.io/qiuliang1/p…

dangerousSetInnerHTML有 2 层{},第一层{}代表 jsx 语法开始,第二个是代表dangerouslySetInnerHTML接收的是一个对象键值对,以'__html'为键,值可以是标签也可以是字符串;

为什么不用 innerHTML

不正确地使用 innerHTML 可能会遭受 cross-site scripting(XSS)攻击。dangerousSetInnerHTML这个 prop 名称也是明确的警示我们危险的使用,它的 prop 值(用对象代替字符串)用来表示是清理过的数据。 在将 HTML 插入页面之前,需要确保 HTML 的结构正确且经过清理。 您可以使用 dompurify 之类的库来这样做。

ref

React 支持一个特殊的、可以附加到任何组件上的 ref 属性。此属性可以是一个由 React.createRef() 函数创建的对象、或者一个回调函数、或者一个字符串(遗留 API)。当 ref 属性是一个回调函数时,此函数会(根据元素的类型)接收底层 DOM 元素或 class 实例作为其参数。这能够让你直接访问 DOM 元素或组件实例

ref 的不能在函数组件使用,可以在类组件和函数组件内部使用;

// 类组件
class Footer extends React.Component {
  render () {
    return (
      <div>
        <h2>我是类组件</h2>
      </div>
    )
  }
}

// 函数组件
function User(props) {
  return (
    <div>
      <h2>我是user组件</h2>
      <p>{props.name} --- {props.age}</p>
    </div>
  )
}
// 函数组件
function Component1() {
  // 创建一个ref
  const userRef = React.createRef()
  // 函数组件内部使用ref
  const inputRef = React.createRef()

  function handled() {
    console.log('点击触发', userRef);
    /*
      {current: 组件内部信息}
    */
    // 实现点击按钮聚焦到输入框
    inputRef.current.focus();
  }

  return (
    <div className="App">
      <button onClick={handled}>点击我</button>
      <input type="text" ref={inputRef}>
      <User {...props} />
      <Footer ref={userRef} />
    </div>
  );
}

key

“key” 是在创建元素数组时,需要用到的一个特殊字符串属性。key 帮助 React 识别出被修改、添加或删除的 item。应当给数组内的每个元素都设定 key,以使元素具有固定身份标识。

  • 提高渲染性能
    • 我们都知道 react 中虚拟 dom 是通过 diff 算法提高性能的,如果在数组渲染的 dom 中增加 key,diff 算法可以更精确的比较变化的部分;
    • 数组渲染中不推荐使用 index 作为 key,当数组中有增删操作也会造成隐患(以前的数据和重新渲染后的数据随着 key 值的变化从而没法建立关联关系. 这就失去了 key 值存在的意义. 也是导致数据出现诡异的罪魁祸首);
  • 唯一标识

props(属性)介绍和使用

什么是 props

react 应用开发中,我们更多的是用组件化开发,组件中最重要的便是数据,其中数据分为两种:propsstate;一个 UI 中所渲染的结果就是通过这两个属性在 render(渲染函数)方法中映射成对应的 html 结构。

props 可以默认为是对外的接口,只接受外部传入的数据,内容是只读的数据,强制更改则会报错。 React 数据流是单向的,这么规定也是为了组件的复用,如果组件内部可以更改,可能会有不可预测的 bug,也会影响开发者的调试。

但组件内部并非不可修改,我们借助 state 来实现 props 的更改需求,其实最终也没有更改 props 值,通过 React 内置的 setState 方法间接改变内部的值。

function User(props) {
  return (
    <div>
      <h2>我是user组件</h2>
      <p>
        {props.name} --- {props.age}
      </p>
    </div>
  );
}

function com() {
  const props = {
    name: "某某",
    age: 22,
  };
  return <User {...props} />;
}

代码中通过父组件传入子组件 props 属性值,子组件接受这两个数据,通过 render 函数映射成对应的结构;

需要注意的是,当 propsstate 值改变是都会触发 render 使组件重新渲染。

如何使用

基本用法

下面代码包括了 props 在函数组件和类组件的用法,state 在类组件的使用:

<User name="某某" age="" />;
<Footer name="某某" age="22" />;

function User(props) {
  return (
    <p>
      {props.name}---{props.age}
    </p>
  );
}
// 默认值
User.defaultProps = {
  age: 18,
};

// 类组件
class Footer extends React.Component {
  // 构造器函数,如果没有声明props  React会默认添加
  constructor(props) {
    // es6固定写法
    super(props);
    // 组件内部状态  setState
    this.state = {
      name1: props.name + "www",
      count: 0,
    };
    // this绑定事件
    this.handleAdd = this.handleAdd.bind(this);
  }
  // 动态修改值
  handleAdd() {
    this.setState((state) => ({
      count: ++state.count,
    }));
  }
  render() {
    return (
      <div>
        <p>
          {this.state.name1}---{this.props.age}
        </p>
        <button onClick={this.handleAdd}>自增加{this.state.count}</button>
      </div>
    );
  }
}

User 组件就可以通过传 props 参数取值了,如果是类组件可以通过 this.props.name 获取值; 有些时候父组件没有传数据到子组件,这个时候可以使用User.defaultProps方法来设置默认值。

子组件到父组件传值

上段代码是展示的父组件到子组件的传值方法,那我们现在再了解一下子组件到父组件的传值;

这里我们还是利用 props 的特性:接收任意数据类型的值,那么我们就可以传函数:

function getChildData(data) {
  console.log("接收子组件的值", data);
}

// 类组件
class Footer extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0,
    };
  }
  handleApp = () => {
    this.setState((state) => {
      count: ++state.count;
    });
    this.props.getChildData(this.state.count);
  };
  render() {
    return <button onClick={this.handleClick}>test</button>;
  }
}

class App extends React.Component {
  render() {
    return <Footer getChildData={getChildData} />;
  }
}

函数组件和类组件

  • 函数组件(function):只有 props ,无状态组件
  • 类组件(class):除 props 外,还有 state 和生命周期,使用 state 为有状态组件,没有则是无状态组件

如果类组件不使用state和生命周期,推荐使用函数组件代替,因为函数组件相对效率更高;有了 hooks 后,函数组件也可以使用state,后续会提到这里就不介绍了。

state 介绍与应用

React 的核心思想是组件化的思想,应用由组件搭建而成, 而组件中最重要的概念是 State(状态)。

什么是 state

React 把组件看成是一个状态机(State Machines)。通过与用户的交互,实现不同状态,然后渲染 UI,让用户界面和数据保持一致。 React 里,只需更新组件的 state,然后根据新的 state 重新渲染用户界面(不要操作 DOM)。

setState

下面我们还是用 Footer 组件来做一个控制显示隐藏的操作:

class Footer extends React.Component {
  // 构造器函数,如果没有声明props  React会默认添加
  constructor(props) {
    // es6固定写法
    super(props);
    // 组件内部状态  setState
    this.state = {
      name1: props.name + "www",
      count: 0,
      isShow: true,
    };
    // this绑定事件
    this.handleAdd = this.handleAdd.bind(this);
  }
  // 动态修改值
  handleAdd() {
    // 函数形式setState
    this.setState((state) => ({
      // 基于当前state进行计算,保证state一定是最新的
      count: ++state.count,
    }));
    this.props.getChildData(this.state.count);
  }
  /***************点击控制显示**************************/
  handleClick = () => {
    // 对象形式setState
    this.setState({ isShow: !this.state.isShow });
  };
  render() {
    return (
      <div>
        {/*动态显示*/}
        {this.state.isShow ? <h2>我是显示、隐藏</h2> : ""}
        <p>
          {this.state.name1}---{this.props.age}
        </p>
        <button onClick={this.handleAdd}>自增加{this.state.count}</button>
        <button onClick={this.handleClick}>控制</button>
      </div>
    );
  }
}

当我们调用 handleClick 函数时,就会通过 setState 改变状态,从而重新调用 render 方法更新页面内容;

还要注意一点的是,React 控制的事件处理程序(onClick,onChange)、生命周期钩子函数中setState 的更新是异步操作,不会立马更新组件,它会把 state 对象放到一个更新队列中,然后从队列中把新的状态提取出来(批量延迟更新),再合并到 state,最后触发 render 函数组件重新渲染更新页面;

不属于 React 控制事件处理会同步更新,如:原生 js 绑定的事件(setTimeout)。

组件的 state 如何划分

我们知道 stateprops 都是组件的数据,会影响组件 UI 的最终展示,那么我们究竟怎么区分组件是否应该需要 state 去设置,这个时候就需要做一些原则:

  1. 让组件尽可能少状态: UI 渲染、数据展示、没有复杂交互,通过外部组件传入 props,不使用 state(一般推荐使用 function 函数组件)
  2. 随着时间产生变化的数据: 在一定时间后,点击按钮改变了状态;页面有交互,使用 state

props 与 state 对比

  1. state 是组件自己管理数据,控制自己的状态,可变;
  2. props 是外部传入的数据参数,不可变;
  3. 没有 state 的叫做无状态组件,有 state 的叫做有状态组件;
  4. 多用 props,少用 state。也就是多写无状态组件。

React 组件生命周期

React 生命周期( 挂载 => 卸载 )钩子函数是针对组件的,且是再 class 类组件中所有;React 生命周期有三个阶段:挂载阶段 => 更新阶段 => 卸载阶段 => 错误处理(React v16image

挂载阶段

constructor(props)

constructor 中,还得添加 super(props) 才能保证 props 传入到组件中,该方法还有初始化 statethis 绑定处理

constructor(props) {
  // super调用父类props
  super(props);
  // 组件内部状态初始化
  this.state = {
    name1: props.name + "www",
    count: 0,
    isShow: true,
  };
  // this绑定事件
  this.handleAdd = this.handleAdd.bind(this);
}

componentWillMount(v16.3 后过时)

从语义上看就是组件即将挂载,这是在 render 之前被调用,且只会调用一次,但如果在这个方法中使用 setState 是不会引起重新渲染(setState 是同步的,不会触发额外的 render 处理),还会产生副作用或订阅,该方法可以初始化的事情都可放在constructor方法中,所以很少使用

componentWillMount() {
  console.log("即将挂载")
}
// 即将挂载
// Warning: componentWillMount has been renamed, and is not recommended for
// use. See https://reactjs.org/link/unsafe-component-lifecycles for details.
UNSAFE_componentWillMount() {
  console.log("即将挂载")
}
  • 当使用 componentWillMount 时,可以打印出结果,但是控制台会提出警告:该方法已被重命名,不建议使用,官方也提出该名称将继续使用至 React 17

  • 于是我们就用 UNSAFE_componentWillMount 来代替,是没问题的,如果在全局添加严格模式还是会报错:不建议在严格模式下使用。

render

唯一必要的方法,组件的其它生命周期都可以省略,唯独它不能省略;没有这个方法,元素就不会被渲染出来,该方法需要返回一个 react 元素(state,props); 不负责组件的实际渲染工作,只是返回一个 UI 的描述;

注意:

  • render 必须是一个纯函数,在这里面不能改变 state(调用 setState),不能执行任何副作用的操作,否则可能会造成无限渲染、死循环。

作用:

  • 计算 props/state 返回对应的结果;
  • 通过 React.createElement 将 jsx 转换成 vDOM 对象模型。

componentDidMount

  • 组件挂载到 dom 后触发,只会调用一次,可以获取到真实的 dom;

  • 通常用于向服务端请求数据的原因:

    1. 可以保证获取到数据时,组件已经处于挂载状态,可以直接操作 dom;
    2. 保证在任何情况下只会调用一次,不会发送多余数据的请求;
  • 只能在浏览器端调用,服务端不可能产生 dom 树;

  • 在这个方法中调用 setState 就会引起组件的重新渲染;

  • 作用:

    1. 可以获取到真实 dom 和数据
    2. 可以进行数据请求和修改
    3. 操作真实 dom,第三方库的实例化

更新阶段

componentWillReceiveProps(nextProps)

  • 只在接收 props 引起组件更新过程中触发,使用 this.setState 是不会触发该方法的;

  • 只要父组件的 render 函数被调用,无论父组件传给子组件的 props 有没有改变都会触发;

  • 官方建议使用 getDerivedStateFromProps() 代替,该方法也和componentWillMount一样已过时,如果使用需要注意的地方也在官方指出。

shouldComponentUpdate(nextProps)

  • 直接翻译是这个组件是否应该被更新,如果更新就会重新渲染,然后触发 render; 用来通知 react 组件是否更新,有权利阻止更新,尽量遵循默认行为,状态改变,组件就会被重新渲染;

  • 不能调用 setState

  • 要求必须有返回结果,返回 false 就不会更新;

  • 该方法可以减少组件不必要的渲染,提高性能。

####  componentWillUpdate(nextProps,nextState)

  • 在更新发生前执行,一般很少用,不能调用 setState

componentDidUpdate(prevProps, prevState)

  • 更新完成调用,有机会来操作 dom

  • 适合判断是否发送网络请求

    注意:一定要做好条件比较,否则可能会造成死循环

卸载阶段

componentWillUnmount

  • 会在组件卸载及销毁之前直接调用,在此方法中执行必要的清理操作,例如,清除 timer,取消网络请求或清除在 componentDidMount() 中创建的订阅等。
  • 不能调用 setState(),因为该组件将永远不会重新渲染。组件实例卸载后,将永远不会再挂载它。

错误处理阶段

componentDidCatch(error, info)

此生命周期在后代组件抛出错误后被调用。 它接收两个参数:

  1. error:抛出的错误
  2. info:带有 componentStack key 的对象,其中包含有关组件引发错误的栈信息

查看文档详细了解

可控组件与非可控组件

可控组件与非可控组件主要用于表单,它们的核心区别在于输入的值是否与 state 的值对应

可控组件

  • 可控组件依赖于状态,默认值实时映射到状态,通过 state 来管理表单里面的值;

  • 必须使用 onChange 事件来进行值的处理,若不使用无法改变值;

  • 优点:

    • 符合 react 的数据流,单向
    • 修改使用更加方便,只需要对想要的值修改,value 值就会改变,完全不需要获取 dom
    • 便于数据的处理
class Footer extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      username: "受控组件默认值",
    };
  }
  handleChange = (e) => {
    this.setState({
      username: e.target.value,
    });
  };
  render() {
    return (
      <div>
        <input
          type="text"
          onChange={this.handleChange}
          value={this.state.username}
        />
        {/*不加onChange报错 ->  Warning: 你给表单字段提供了一个没有onChange处理程序的“value”道具。这将呈现一个只读字段。如果字段是可变的,请使用' defaultValue '。否则,设置' onChange '或' readOnly '*/}
      </div>
    );
  }
}

非可控组件

  • 不可控组件中间的数据的状态是未知的,只能在 dom 节点中获取,而可控组件中当值变化时会随时同步到 state 中,状态的变化一直是保持同步的

  • 非可控组件获取值时需要使用 ref 来从 dom 节点中拿值

  • 优点:

    • 很容易与第三方组件结合
function Component() {
  const inputRef = React.createRef();

  function handled() {
    console.log("非受控组件的值", inputRef.current.value);
  }

  return (
    <div className="App">
      <button onClick={handled}>p标签</button>
      <div>
        <label htmlFor="name">用户名</label>
        <input
          id="name"
          type="text"
          defaultValue="非受控默认值"
          ref={inputRef}
        />
      </div>
    </div>
  );
}

react 中的事件和 This

react 中的事件

react 中的事件绑定是直接卸载 jsx 中,不需要原生 js 中 addEventListener 来绑定事件;

事件名组成:on+EventType,如:onClick、onChange、onBlur 等驼峰式命名;

自定义标签不可使用事件,只能在原生标签使用;

在 react 中事件内部无法使用return false来阻止默认行为,只能通过e.preventDefault()显式调用才行;

handleClick = (event) => {
  // e.preventDefault() => yes | return false => no
  //
  // do something
};

// jsx
<button onClick={handleClick}>p标签</button>;

代码中 eventreact 内部进行了一层封装,对外提供了 api 接口,并且不需要考虑兼容性

事件监听 this

我们都知道 this 的指向与执行上下文有关;全局里在严格模式下的函数调用,thisundefined,非严格模式下指向 window 的全局对象; 如果作为方法调用,谁调用就指向谁; 作为构造器函数调用,this 就会指向该创建实例化对象,通过 callapply 调用,this 就会指向它们的第一个参数。

this 的四种绑定方式

  1. constructor 中用 bind 绑定(推荐使用) 为了避免在 render 中绑定 this 引发可能的性能问题,我们可以在 constructor 中预先进行绑定。

    class App extends React.Component {
      constructor(props) {
        super(props);
        this.handleClick = this.handleClick.bind(this);
      }
      handleClick() {
        console.log("this > ", this);
      }
      render() {
        return <div onClick={this.handleClick.bind(this)}>test</div>;
      }
    }
    
  2. JSX 中使用 bind(不推荐使用)

    class App extends React.Component {
      handleClick() {
        console.log("this > ", this);
      }
      render() {
        return <div onClick={this.handleClick.bind(this)}>test</div>;
      }
    }
    

    这种方法很简单,然而由于组件因为props值改变会执行 render,内部函数也会跟着重新创建,这就会影响性能。特别是在你做了一些性能优化之后,它会破坏 PureComponent 性能。

  3. 使用箭头函数(推荐)

    class App extends React.Component {
      handleClick = () => {
        console.log("this > ", this);
      };
      render() {
        return <div onClick={this.handleClick.bind(this)}>test</div>;
      }
    }
    

    这种方式自动绑定到当前组件的作用域中,不会被 call 改变,也避免了在 JSX 内部绑定存在性能问题,不需要在构造函数中书写大量重复代码。

  4. JSX 元素中使用箭头函数

    class App extends React.Component {
      handleClick = () => {
        console.log("this > ", this);
      };
      render() {
        return <div onClick={() => this.handleClick}>test</div>;
      }
    }
    

    这种方式也是在 render 函数里操作,会有性能问题,所以也不推荐

事件传参

  1. 传递参数的方式

    • bind
    class Footer extends React.Component {
      handleBtnClick = (event, params) => {
        console.log("我是参数传递的值", params);
      };
      render() {
        return (
          <div>
            <button onClick={this.handleBtnClick.bind(this, "传递的参数123")}>
              传递参数
            </button>
          </div>
        );
      }
    }
    
    • 箭头函数
    class Footer extends React.Component {
      handleBtnClick = (params, event) => {
        console.log("我是参数传递的值", params);
      };
      render() {
        return (
          <div>
            <button onClick={(e) => this.handleBtnClick(e, "传递的参数456")}>
              传递参数
            </button>
          </div>
        );
      }
    }