React基础知识点(二)

108 阅读21分钟

六、State

stateclass组件的内置对象,用于class组件内部数据更新

state就是组件描述某种显示情况的数据,由组件自己设置和更改,也就是说由组件自己维护,使用state的目的就是为了在不同的状态下使组件的显示不同(自己管理)

6.1 state及其特点

State 与 props 类似,但是 state 是私有的,并且完全受控于当前组件

不要直接修改state

state更新可能是异步的:出于性能考虑,React 可能会把多个 setState() 调用合并成一个调用。

state更新会被合并:当你调用 setState() 的时候,React 会把你提供的对象合并到当前的 state

6.2 state的定义和使用

目前react中的状态有两种使用方式:

6.2.1 es6的类 - 构造函数

src/index.js

// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'import App from './02_state/01App_state_es6' // 使用es6的构造函数创建stateconst root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
​

src/02_state/01App_state_es6.jsx

// src/02_state/01App_state_es6.jsximport React, { Component } from 'react';
​
class App extends Component {
​
  constructor (props) {
    super(props)
    this.state = {
      count: 10,
      msg: 'hello state'
    }
  }
  
  render() {
    return (
      <div>
        { this.state.msg } - { this.state.count }
      </div>
    );
  }
}
​
export default App;

6.2.2 es7的类 - 属性初始化器

src/index.js

// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'// import App from './02_state/01App_state_es6' // 使用es6的构造函数创建state
import App from './02_state/02App_state_es7' //  使用es7属性初始化器const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
​

src/02_state/02App_state_es7.jsx

// src/02_state/02App_state_es7.jsx
import React, { Component } from 'react';
​
class App extends Component {
​
  state = {
    msg: 'hello es7 state'
  }
​
  render() {
    return (
      <div>
        { this.state.msg }
      </div>
    );
  }
}
​
export default App;

6.3 如何正确的修改state

setState() 将对组件 state 的更改排入队列,并通知 React 需要使用更新后的 state 重新渲染此组件及其子组件。这是用于更新用户界面以响应事件处理器和处理服务器数据的主要方式.

setState() 视为请求而不是立即更新组件的命令。为了更好的感知性能,React 会延迟调用它,然后通过一次传递更新多个组件。

setState() 并不总是立即更新组件。它会批量推迟更新。这使得在调用 setState() 后立即读取 this.state 成为了隐患。为了消除隐患,请使用 componentDidUpdate 或者 setState 的回调函数(setState(updater, callback)),这两种方式都可以保证在应用更新后触发。

记住修改状态的三大原则:

  • 不要直接修改 State
state = { a: 10 }
this.state.a = 100 // ❌
  • state 的更新可能是异步的
state = { a: 10 }
this.setState({a: this.state.a + 1 })
this.setState({a: this.state.a + 1 })
this.setState({a: this.state.a + 1 })
console.log(this.state.a) // 10
  • state 的更新会被合并

6.4 this.setState()方法及其特点

setState() 会对一个组件的 state 对象安排一次更新。当 state 改变了,该组件就会重新渲染。

setState()可以添加两个参数,

setState() 的第二个参数为可选的回调函数,它将在 setState 完成合并并重新渲染组件后执行

6.4.1 传递函数

参数一为带有形式参数的 updater 函数:

this.setState((state, props) => stateChange[, callback] )

src/index.js

// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'// import App from './02_state/01App_state_es6' // 使用es6的构造函数创建state
// import App from './02_state/02App_state_es7' //  使用es7属性初始化器
import App from './02_state/03App_setState_function' // 使用函数修改组件的状态const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
​

src/02_state/03App_setState_function.jsx

// src/02_state/03App_setState_function.jsx
import React, { Component } from 'react';
​
class App extends Component { 
  state = {
    msg: 'hello setState',
    count: 10
  }
  render() {
    return (
      <div>
        <button onClick={ () => {
          this.setState((prevState) => { // prevState 上一次状态
            console.log('1', prevState) // {msg: 'hello setState', count: 10}
            return { // 返回一个新的状态
              count: prevState.count + 1
            }
          })
          this.setState((prevState) => { // prevState 上一次状态
            console.log('2', prevState) // {msg: 'hello setState', count: 11}
            return { // 返回一个新的状态
              count: prevState.count + 1
            }
          })
        } }>加1</button> { this.state.count }
      </div>
    );
  }
}
​
export default App;

updater 函数中接收的 stateprops 都保证为最新。updater 的返回值会与 state 进行浅合并。

6.4.2 传递对象

src/index.js

// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'// import App from './02_state/01App_state_es6' // 使用es6的构造函数创建state
// import App from './02_state/02App_state_es7' //  使用es7属性初始化器
// import App from './02_state/03App_setState_function' // 使用函数修改组件的状态
import App from './02_state/04App_setState_object' // 使用函数修改组件的状态const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
​

src/02_state/04App_setState_object.jsx

// src/02_state/04App_setState_object.jsx
import React, { Component } from 'react';
​
class App extends Component {
  state = {
    msg: 'hello setState Object',
    count: 100
  }
  render() {
    return (
      <div>
        <button onClick={ () => {
          this.setState({
            count: this.state.count + 10
          })
          this.setState({
            count: this.state.count + 10
          })
          // 点击之后期望结果是120,但是结果却是110
          // 使用对象的形式的时候, 进行了 修改对象的合并{ count: this.state.count + 10 }
          // 使用函数的时候,函数是有自己的作用域的
        }}>加10</button> { this.state.count }
        <button onClick={ () => {
          this.setState({
            count: this.state.count + 100
          }, () => { // setState的第二个参数,回调函数,表示视图更新完毕
            this.setState({
              count: this.state.count + 100
            })
          })
          
        }}>加100</button>
      </div>
    );
  }
}
​
export default App;

这种形式的 setState() 是异步的,并且在同一周期内会对多个 setState 进行批处理,相当于

​
Object.assign(
prevState,
{count: this.state.count + 1},
{count: this.state.count + 1},
...
)

后调用的 setState() 将覆盖同一周期内先调用 setState 的值,因此商品数仅增加一次。如果后续状态取决于当前状态,建议使用 updater 函数的形式代替(前面案例已经实现)。或者在第二个参数中再继续操作。

思考题:

1.何时以及为什么 setState() 会批量执行?

2.为什么不直接更新 this.state

七、生命周期

组件的生命周期可分成三个状态:

  • Mounting(挂载,初始化):已插入真实 DOM
  • Updating(更新,运行时):正在被重新渲染
  • Unmounting(卸载,销毁):已移出真实 DOM

生命周期图谱可以参考链接:projects.wojtekmaj.pl/react-lifec…

image-20240109105936412

7.1 三个阶段

7.1.1 挂载时阶段-装载时阶段

当组件实例被创建并插入 DOM 中时,其生命周期调用顺序如下:

  • constructor(): 在 React 组件挂载之前,会调用它的构造函数。

    如果不需要对类组件添加初始化数据以及绑定事件,那么就不需要写 constructor

  • static getDerivedStateFromProps(): 在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。

  • render(): render() 方法是 class 组件中唯一必须实现的方法。

  • componentDidMount(): 在组件挂载后(插入 DOM 树中)立即调用。

render() 方法是 class 组件中唯一必须实现的方法,其他方法可以根据自己的需要来实现。

7.1.2 更新时阶段 - 运行时阶段

每当组件的 state 或 props 发生变化时,组件就会更新。

当组件的 props 或 state 发生变化时会触发更新。组件更新的生命周期调用顺序如下:

  • static getDerivedStateFromProps(): 在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。根据 shouldComponentUpdate() 的返回值,判断 React 组件的输出是否受当前 state 或 props 更改的影响。
  • shouldComponentUpdate():当 props 或 state 发生变化时,shouldComponentUpdate() 会在渲染执行之前被调用。
  • render(): render() 方法是 class 组件中唯一必须实现的方法。
  • getSnapshotBeforeUpdate(): 在最近一次渲染输出(提交到 DOM 节点)之前调用。
  • componentDidUpdate(): 在更新后会被立即调用,如果你需要执行副作用(例如,数据提取或动画)以响应 props 中的更改。

render() 方法是 class 组件中唯一必须实现的方法,其他方法可以根据自己的需要来实现。

7.1.3 卸载时阶段 - 销毁阶段

当组件从 DOM 中移除时会调用如下方法:

  • componentWillUnmount(): 在组件卸载及销毁之前直接调用。

7.1.4 Error boundaries

  • static getDerivedStateFromError
  • componentDidCatch

Error boundaries 是 React 组件,它会在其子组件树中的任何位置捕获 JavaScript 错误,并记录这些错误,展示降级 UI 而不是崩溃的组件树。Error boundaries 组件会捕获在渲染期间,在生命周期方法以及其整个树的构造函数中发生的错误。

项目中需要使用的最多的生命周期的钩子函数为 render, componentDidMount,componentDidUpdate,componentWillUnmount

详细介绍范例:zhuanlan.zhihu.com/p/392532496

// src/ErrorBoundary.jsx
import React from 'react'
class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }
​
  static getDerivedStateFromError(error) {
    // 更新 state 使下一次渲染能够显示降级后的 UI
    return { hasError: true };
  }
​
  // try catch 
  componentDidCatch(error, errorInfo) {
    // 你同样可以将错误日志上报给服务器
    // logErrorToMyService(error, errorInfo);
    console.log(error, errorInfo);
  }
​
  render() {
    if (this.state.hasError) {
      // 你可以自定义降级后的 UI 并渲染
      return <h1>Something went wrong.</h1>;
    }
​
    return this.props.children; 
  }
}
​
export default ErrorBoundary

src/index.js

// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'
import ErrorBoundary from './ErrorBoundary';
​
// import App from './02_state/01App_state_es6' // 使用es6的构造函数创建state
// import App from './02_state/02App_state_es7' //  使用es7属性初始化器
// import App from './02_state/03App_setState_function' // 使用函数修改组件的状态
// import App from './02_state/04App_setState_object' // 使用函数修改组件的状态
import App from './02_state/05App_lifecycle' // 生命周期钩子函数const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <ErrorBoundary>
    <App />
  </ErrorBoundary>
);
​

src/02_state/06App_lifeCycle.jsx

// src/02_state/05App_lifecycle.jsximport React, { Component, PureComponent } from 'react';
​
// class Child extends Component {
//   render() {
//     console.log('666')
//     return (
//       <div>
//         子组件
//       </div>
//     );
//   }
// }// PureComponent性能提升
class Child extends PureComponent {
  render() {
    console.log('666')
    return (
      <div>
        子组件
      </div>
    );
  }
}
​
class App extends Component {
  constructor (props) {
    super(props)
    this.state = {
      count: 10
    }
  }
  static getDerivedStateFromProps (props, state) {
    // 在初始挂载及后续更新时都会被调用。
    // 它应返回一个对象来更新 state,如果返回 null 则不更新任何内容。
    // state 的值在任何时候都取决于 props
    // return {
    //   count: 20
    // }
    return null
  }
  render() {
    return (
      <div>
        { this.state.count }
        <button onClick={() => {
          this.setState({ count: this.state.count + 1})
        }}>add 1</button>
        <Child />
      </div>
    );
  }
​
  componentDidMount () {
    // 在组件挂载后(插入 DOM 树中)立即调用。依赖于 DOM 节点的初始化应该放在这里。
    // 如需通过网络请求获取数据,此处是实例化请求的好地方。
    // 这个方法是比较适合添加订阅的地方。
    // 定时器、计时器
    setTimeout(() => {
      this.setState({
        count: 100
      })
    }, 2000);
  }
​
  shouldComponentUpdate (nextProps, nextState) {
    // 返回值,判断 React 组件的输出是否受当前 state 或 props 更改的影响。
    // 默认行为是 state 每次发生变化组件都会重新渲染。大部分情况下,你应该遵循默认行为。
    return true
  }
​
  getsnapshotbeforeupdate (prevProps, prevState) {
    //  在最近一次渲染输出(提交到 DOM 节点)之前调用。
    // 它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。
    // 我们是否在 list 中添加新的 items ?
    // 捕获滚动••位置以便我们稍后调整滚动位置。
    return null
  }
​
  componentDidUpdate (prevProps, prevState, snapshot) {
    // 典型用法(不要忘记比较 props): 监听属性
    if (this.props.userID !== prevProps.userID) {
      // this.fetchData(this.props.userID);
      // 数据请求
    }
  }
​
  componentWillUnmount () {
    // 在组件卸载及销毁之前直接调用。
    // 在此方法中执行必要的清理操作,例如,清除 timer,
    // 取消网络请求或清除在 componentDidMount() 中创建的订阅等。
  }
}
​
export default App;

? shouldComponentUpdate 作为组件提升性能的一个关键,但是不要轻易使用,可以考虑使用 PureComponent

7.2 两个时期

将应用的渲染过程分为mount阶段(应用首次渲染)和update阶段(应用状态更新),无论在mount阶段还是update阶段,都会经历两个子阶段,一个是render阶段,一个是commit阶段。

mount时: 在render阶段会根据jsx对象构建新的workInProgressFiber树,然后将相应的fiber节点标记为Placement,表示这个fiber节点需要被插入到dom树中,然后会这些带有副作用的fiber节点加入一条叫做Effect List的链表中。 在commit阶段会遍历render阶段形成的Effect List,执行链表上相应fiber节点的副作用,比如Placement插入,或者执行Passive(useEffect的副作用)。将这些副作用应用到真实节点上 update时: 在render阶段会根据最新状态的jsx对象对比current Fiber,再构建新的workInProgressFiber树,这个对比的过程就是diff算法diff算法又分成单节点的对比和多节点的对比,对比的过程中同样会经历收集副作用的过程,也就是将对比出来的差异标记出来,加入Effect List中,这些对比出来的副作用例如:Placement(插入)、Update(更新)、Deletion(删除)等。 在commit阶段同样会遍历Effect List,将这些fiber节点上的副作用应用到真实节点上。

参考链接: blog.csdn.net/bemystery/a…

7.3 入门理解React Fiber架构

在 React 16 之前,VirtualDOM 的更新采用的是Stack架构实现的,也就是循环递归方式。不过,这种对比方式有明显的缺陷,就是一旦任务开始进行就无法中断,如果遇到应用中组件数量比较庞大,那么VirtualDOM 的层级就会比较深,带来的结果就是主线程被长期占用,进而阻塞渲染、造成卡顿现象。

为了避免出现卡顿等问题,我们必须保障在执行更新操作时计算时不能超过16ms,如果超过16ms,就需要先暂停,让给浏览器进行渲染,后续再继续执行更新计算。而Fiber架构就是为了支持“可中断渲染”而创建的。

React中,Fiber使用了一种新的数据结构fiber tree,它可以把虚拟dom tree转换成一个链表,然后再执行遍历操作,而链表在执行遍历操作时是支持断点重启的,示意图如下。 image.png

官方介绍中,Fiber 被理解为是一种数据结构,但是我们也可以将它理解为是一个执行单元。

Fiber 可以理解为一个执行单元,每次执行完一个执行单元,React Fiber就会检查还剩多少时间,如果没有时间则将控制权让出去,然后由浏览器执行渲染操作。React Fiber 与浏览器的交互流程如下图。 image.png

可以看到,React 首先向浏览器请求调度,浏览器在执行完一帧后如果还有空闲时间,会去判断是否存在待执行任务,不存在就直接将控制权交给浏览器;如果存在就会执行对应的任务,执行完一个新的任务单元之后会继续判断是否还有时间,有时间且有待执行任务则会继续执行下一个任务,否则将控制权交给浏览器执行渲染,这个流程是循环进行的。

所以,我们可以将Fiber 理解为一个执行单元,并且这个执行单元必须是一次完成的,不能出现暂停。并且,这个小的执行单元在执行完后计算之后,可以移交控制权给浏览器去响应用户,从而提升了渲染的效率。

在官方的文档中,Fiber 被解释为是一种数据结构,即链表结构。在链表结构中,每个Virtual DOM 都可以表示为一个 fiber,如下图所示。 image.png 通常,一个 fiber包括了child(第一个子节点)、sibling(兄弟节点)、return(父节点)等属性,React Fiber 机制的实现,就是依赖于上面的数据结构。

通过介绍,我们知道Fiber使用的是链表结构,准确的说是单链表树结构。为了方便理解 Fiber 的遍历过程,下面我们就看下Fiber链表结构。

image.png

在上面的例子中,每一个单元都包含了payload(数据)和nextUpdate(指向下一个单元的指针)两个元素

参考链接:segmentfault.com/a/119000004…

八、事件绑定

React 元素的事件处理和 DOM 元素的很相似,但是有一点语法上的不同:

  • React 事件的命名采用小驼峰式(camelCase),而不是纯小写。

  • 使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串。\

    * 保证是一个函数
    * 如果需要使用this关键词,保证this指向
    

8.1 ES5语法绑定事件

8.1.1 无参数的绑定

8.1.1.1 方法一

  • 定义函数
handleClick(e) { // e - 事件对象
  e.preventDefault();
  // doSomething ...
}
  • constructor 中绑定函数执行上下文
this.handleClick = this.handleClick.bind(this);
  • jsx中调用
<button onClick={this.hanleClick} />

8.1.1.1 方法二

  • 定义函数
handleClick(e) { // e - 事件对象
  e.preventDefault();
  // doSomething ...
}
  • jsx 中调用
<button onClick={this.hanleClick.bind(this)} />

8.1.2 有参数的绑定

  • 定义函数
handleClick(param1, param2, e) {
  e.preventDefault();
  // do something ...
}

注意此时无论多少个参数, e 一定放在最后

  • jsx 中调用
<button onClick={this.hanleClick.bind(this, 'x', 'xx')} />

src/index.js

// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'
import ErrorBoundary from './ErrorBoundary';
​
import App from './03_event/01App_event_es5' // es5 绑定事件const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <ErrorBoundary>
    <App />
  </ErrorBoundary>
);
​

src/03_event/01App_event_es5.jsx

// src/03_event/01App_event_es5.jsx
import React, { Component } from 'react';
​
// 保证写的是一个函数
// 需要使用this的时候,保证this的指向class App extends Component {
​
  constructor(props) {
    super(props)
    console.log('constructor', this)
    // this.fn4() // 得到app实例
    this.fn4Fn = this.fn4.bind(this)
  }
​
  fn9 (val1, val2, event) { // 一旦传递参数,event将作为函数的最后一个参数
    console.log('9999', val1 + val2, event)
  }
​
  fn8 = (event) => {
    console.log('88888', this, event) 
  }
  fn7 = function (event) {
    console.log('7777', this)  
  }
  fn6 = function (event) {
    console.log('66666', this)  
  }.bind(this)
  fn5 (event) {
    console.log('类函数-改变this指向', this)  
  }
  fn4 (event) {
    console.log('类函数-构造函数', this)  
  }
  fn3 (event) {
    console.log('类函数', this)  // 匿名函数 undefined
  }
  render() {
    function fn1 (event) {
      console.log('外部声明函数', this) // 匿名函数 undefined
    }
    function fn2 (event) {
      console.log('外部声明函数', this) // 匿名函数 App
    }
    return (
      <div>
        <h1>js规则</h1>
        <button onClick={ function (event) {
          console.log('匿名函数', this) // 匿名函数 undefined
        }}>绑定点击事件 - 匿名函数</button>
        <button onClick={ function (event) {
          console.log('匿名函数', this) // 匿名函数 App
        }.bind(this) }>绑定点击事件 - 匿名函数 - 改变this指向</button>
        <button onClick={ (event) => {
          console.log('箭头函数', this) // 匿名函数 App
        }}>绑定点击事件 - 箭头函数</button>
        <button onClick={ fn1 }>绑定点击事件 - 外部声明函数</button>
        <button onClick={ fn2.bind(this) }>绑定点击事件 - 外部声明函数 - 改变this指向</button>
        <button onClick={ this.fn3 }>绑定点击事件 - 类中添加事件</button>
        <button onClick={ this.fn4Fn }>绑定点击事件 - 类中添加事件 - 构造函数</button>
        <button onClick={ this.fn5.bind(this) }>绑定点击事件 - 类中添加事件 - 改变this指向</button>
        <button onClick={ this.fn6 }>绑定点击事件 - 类中添加属性表达式事件 </button>
        <button onClick={ this.fn7.bind(this) }>绑定点击事件 - 类中添加属性表达式事件 </button>
        <button onClick={ this.fn8 }>绑定点击事件 - 类中添加属性表达式事件 </button>
        <h1>react中常用</h1>
        <button onClick={ () => {
          console.log('箭头函数', this) // 匿名函数 App
        }}>绑定点击事件 - 箭头函数</button>
        <button onClick={ fn2.bind(this) }>绑定点击事件 - 外部声明函数 - 改变this指向</button>
        <mark>以上的函数都可以带有event的默认参数</mark>
        <h1>react事件绑定并且传递参数</h1>
        <button onClick={ this.fn9.bind(this, 'hello', 'world') }>绑定点击事件 - 外部声明函数 - 改变this指向 - 传递参数</button>
      </div>
    );
  }
}
​
export default App;

8.2 ES6语法绑定事件

8.2.1 无参数绑定

8.2.1.1 方法一

  • 定义函数
handleClick = (e) => {
  e.preventDefault();
  // do something ...
}
  • jsx中调用
<button onClick={this.hanleClick} />

比起 es 5 中的无参数函数的绑定调用, es 6 不需要使用 bind 函数;

8.2.1.2 方法二

jsx中定义箭头函数

<button onClick={ () => {}} />

8.2.2 有参数绑定

8.2.2.1 方法一

  • 定义函数
handleClick = (param1, e) => {
  e.preventDefault();
  // do something ...
}
  • jsx调用
<button onClick={this.hanleClick.bind(this, 'x')} />

有参数时,在绑定时依然要使用 bind; 并且参数一定要传,不然仍然存在 this 指向错误问题;

8.2.2.2 方法二

  • 定义函数
handleClick = (param1, e) => {
  // do something ...
}
  • jsx调用
<button onClick={() => this.handleClick('c')} />
// 如果需要对 event 对象进行处理的话,需要写成下面的格式
<button onClick={(e) => this.handleClick('c', e)} />

src/index.js

// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'
import ErrorBoundary from './ErrorBoundary';
​
// import App from './03_event/01App_event_es5' // es5 绑定事件
import App from './03_event/02App_event_es6' // es6 绑定事件const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <ErrorBoundary>
    <App />
  </ErrorBoundary>
);
​

src/03_event/02App_event_es6.jsx

// src/03_event/02App_event_es6.jsx
import React, { Component } from 'react';
​
class App extends Component {
  fn2 = (event) => {
    console.log('2', event, this)
  }
  fn3 = (event) => {
    console.log('3', event, this)
  }
  fn4 = (val1, val2, event) => {
    console.log('4', event, this, val1 + val2)
  }
  fn5 = (event, val1, val2) => {
    console.log('5', event, this, val1 + val2)
  }
  render() {
    return (
      <div>
        <h1>无参</h1>
        <button onClick={ (event) => {
          console.log('1', event, this)
        } }>jsx内部定义箭头函数 ********************</button>
        <button onClick={ this.fn2 }>类组件内部定义表达式函数</button>
        <button onClick={ (event) => {
          this.fn3(event)
        } }>类组件内部定义表达式函数,jsx内部定义箭头函数调用外部事件</button>
​
        <h1>传参</h1>
        <button onClick={ this.fn4.bind(this, 'hello', 'world') }>类组件内部定义表达式函数,通过bind传递参数 **************</button>
        <button onClick={ (event) => {
          this.fn5(event, 'hello', 'react')
        } }>类组件内部定义表达式函数,jsx内部定义箭头函数调用外部事件并且传递参数</button>
      </div>
    );
  }
}
​
export default App;

8.3 合成事件的特点

以上案例react事件对象打印为 SyntheticBaseEvent,原生js事件打印为 PointerEvent

8.3.1 事件机制

  • react自身实现了一套事件机制,包括事件的注册、事件的存储、事件的合成及执行等。
  • react 的所有事件并没有绑定到具体的dom节点上而是绑定在了document 上,然后由统一的事件处理程序来派发执行。
  • 通过这种处理,减少了事件注册的次数,另外react还在事件合成过程中,对不同浏览器的事件进行了封装处理,抹平浏览器之间的事件差异。

8.3.2 对合成事件的理解

(1)对原生事件的封装

react会根据原生事件类型来使用不同的合成事件对象,比如: 聚焦合成事件对象SyntheticFoucsEvent(合成事件对象:SyntheticEventreact合成事件的基类,定义了合成事件的基础公共属性和方法。合成事件对象就是在该基类上创建的,原生js的 事件对象为 PointerEvent

(2)不同浏览器事件兼容的处理

在对事件进行合成时,react针对不同的浏览器,也进行了事件的兼容处理

8.3.3 事件机制的流程

1、事件注册

在组件挂载阶段,根据组件内声明的事件类型-onclickonchange 等,给 document 上添加事件 -addEventListener,并指定统一的事件处理程序 dispatchEvent

2、事件存储

完成事件注册后,将react dom ,事件类型,事件处理函数fn放入数组存储,组件挂载完成后,经过遍历把事件处理函数存储到 listenerBank(一个对象)中,缓存起来,为了在触发事件的时候可以查找到对应的事件处理方法去执行。

开始事件的存储,在react 里所有事件的触发都是通过 dispatchEvent方法统一进行派发的,而不是在注册的时候直接注册声明的回调,来看下如何存储的 。 react 把所有的事件和事件类型以及react 组件进行关联,把这个关系保存在了一个map里,也就是一个对象里(键值对),然后在事件触发的时候去根据当前的 组件id和 事件类型查找到对应的 事件fn

3、事件执行

1、进入统一的事件分发函数(dispatchEvent) 2、结合原生事件找到当前节点对应的ReactDOMComponent对象 3、开始 事件的合成

  • 根据当前事件类型生成指定的合成对象
  • 封装原生事件和冒泡机制
  • listenerBank事件池中查找事件回调并合成到 event(合成事件结束)

4.处理合成事件内的回调事件(事件触发完成 end)

8.3.4 合成事件、原生事件之间的冒泡执行关系

结论:

  • 原生事件阻止冒泡肯定会阻止合成事件的触发。
  • 合成事件的阻止冒泡不会影响原生事件。

原因:

  • 浏览器事件的执行需要经过三个阶段,捕获阶段-目标元素阶段-冒泡阶段

节点上的原生事件的执行是在目标阶段,然而合成事件的执行是在冒泡阶段,所以原生事件会先合成事件执行,然后再往父节点冒泡,所以原生事件阻止冒泡会阻止合成事件的触发,而合成事件的阻止冒泡不会影响原生事件。

理解react事件中出现的undefined:www.inte.net/news/275774…

九、条件渲染

在 React 中,你可以创建不同的组件来封装各种你需要的行为。然后还可以根据应用的状态变化只渲染其中的一部分。

React 中的条件渲染和 JavaScript 中的一致,使用 JavaScript 操作符 if 或条件运算符来创建表示当前状态的元素,然后让 React 根据它们来更新 UI。

9.1 &&

你可以通过用花括号包裹代码在 JSX 中嵌入任何表达式 ,也包括 JavaScript 的逻辑与 &&,它可以方便地条件渲染一个元素。

src/index.js

// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'
import ErrorBoundary from './ErrorBoundary';
​
import App from './04_condition/01App_condition_yu' // 条件渲染 与 或 非const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <ErrorBoundary>
    <App />
  </ErrorBoundary>
);
​

src/04_condition/01App_condition_yu.jsx

// src/04_condition/01App_condition_yu.jsx
import React, { Component } from 'react';
​
class App extends Component {
  state = {
    list: []
  }
​
  sendEmail = () => {
    const arr = this.state.list // 获取数据
    for (var i = 0; i < 5; i++) { // 处理数据
      arr.push(`第${i + 1}封邮件`)
    }
    this.setState({ // 修改状态
      list: arr
    })
  }
​
  deleteItem = (index) => {
    const arr = this.state.list
    arr.splice(index, 1)
    this.setState({
      list: arr
    })
  }
  render() {
    return (
      <div>
        <button onClick={ this.sendEmail }>发送5封邮件</button>
        <h3>邮件展示</h3>
        <ul>
          {
            // jsx中遍历数组渲染代码需要使用 map
            // 面试题: 为什么不适用forEach
            // && 挂载时阶段 不遍历, 更新时阶段 遍历
            this.state.list && this.state.list.map((item, index) => {
              return (
                <li key = { index }>
                  { item } 
                  <button onClick={ this.deleteItem.bind(this, index) }>阅读</button>
                </li>
              )
            })
          }
        </ul>
      </div>
    );
  }
}
​
export default App;

9.2 三元运算符

条件渲染的另一种方法是使用 JavaScript 的条件运算符:

condition ? true : false

src/index.js

// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'
import ErrorBoundary from './ErrorBoundary';
​
// import App from './04_condition/01App_condition_yu' // 条件渲染 与 或 非
import App from './04_condition/02App_condition_san' // 条件渲染 与 或 非const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <ErrorBoundary>
    <App />
  </ErrorBoundary>
);
​

src/04_condition/02_App_condition_san.jsx

// src/04_condition/02App_condition_san.jsx
import React, { Component } from 'react';
​
// 不可以在jsx代码中使用if else 语句
// 但是可以在if else中返回jsx代码
// if else 可以使用三元运算符代替,直接在jsx代码中使用class App extends Component {
  state = {
    flag: false
  }
  // render() { // 三元运算符
  //   return (
  //     <div>
  //       <button onClick={ () => { this.setState({ flag: !this.state.flag }) } }>切换flag</button>
  //       {
  //         this.state.flag ? <h1>真</h1> : <h6>假</h6>
  //       }
  //     </div>
  //   );
  // }
​
  // render() { // if else 带变量
  //   let val = <h1>真1</h1>
  //   if (this.state.flag) {
  //     val = <h1>真1</h1>
  //   } else {
  //     val = <h6>假1</h6>
  //   }
  //   return (
  //     <div>
  //       <button onClick={ () => { this.setState({ flag: !this.state.flag }) } }>切换flag</button>
  //       {
  //         val
  //       }
  //     </div>
  //   );
  // }
  render () { // if else 返回jsx代码
    if (this.state.flag) {
      return (
        <div>
          <button onClick={ () => { this.setState({ flag: !this.state.flag }) } }>切换flag</button>
          <h1>真2</h1>
        </div>
      )
    } else {
      return (
        <div>
          <button onClick={ () => { this.setState({ flag: !this.state.flag }) } }>切换flag</button>
          <h6>假2</h6>
        </div>
      )
    }
  }
}
​
export default App;

9.3 动态className

Vue中有很方便的动态绑定class属性的方式,v-bind:class,那么react怎么实现这样的效果呢?

<button class="btn btn-success btn-sm"></button>

<button class="btn btn-danger btn-md"></button>

<button class="btn btn-warning btn-lg"></button>

{ this.state.type === 'success' ? 'btn btn-success btn-sm' : 'btn btn-sm'}

如果传递了 type 以及 size 的时候,又该如何呢

通过classnames这个插件可以实现

$ cnpm i classnames -S

src/index.js

// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'
import ErrorBoundary from './ErrorBoundary';
​
// import App from './04_condition/01App_condition_yu' // 条件渲染 与 或 非
// import App from './04_condition/02App_condition_san' // 条件渲染 与 或 非
import App from './04_condition/03App_condition_classnames' // 动态classconst root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <ErrorBoundary>
    <App />
  </ErrorBoundary>
);
​

src/04_condition/03App_condition_classname.jsx

// src/04_condition/03App_condition_classnames.jsx
import React, { Component } from 'react';
import classnames from 'classnames'
/**
 * 
 */
class App extends Component {
  state = {
    type: 'success',
    size: 'sm'
  }
  render() {
    return (
      <div>
        <div>
          切换
          <button onClick={ () => this.setState({ type: 'success'})}>success</button>
          <button onClick={ () => this.setState({ type: 'danger'})}>danger</button>
          <button onClick={ () => this.setState({ type: 'warning'})}>warning</button>
        </div>
        <hr />
        <div>
          模板
          <button className='btn btn-success btn-sm'>success</button>
          <button className='btn btn-danger btn-md'>danger</button>
          <button className='btn btn-warning btn-sm'>warning</button>
        </div>
        <div>
         
          <button className={ this.state.type === 'success' ? 'btn btn-success btn-sm' : 'btn btn-sm' }>success</button>
          动态渲染
          <button className={
            classnames({
              btn: true,
              'btn-success': this.state.type === 'success',
              'btn-danger': this.state.type === 'danger',
              'btn-warning': this.state.type === 'warning',
              'btn-sm': this.state.size === 'sm',
              'btn-md': this.state.size === 'md',
              'btn-lg': this.state.size === 'lg'
            })
          }>{ this.state.type } - { this.state.size }</button>
        </div>
      </div>
    );
  }
}
​
export default App;

9.4 动态style

src/index.js

// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'
import ErrorBoundary from './ErrorBoundary';
​
// import App from './04_condition/01App_condition_yu' // 条件渲染 与 或 非
// import App from './04_condition/02App_condition_san' // 条件渲染 与 或 非
// import App from './04_condition/03App_condition_classnames' // 动态class
import App from './04_condition/04App_style' // 动态 styleconst root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <ErrorBoundary>
    <App />
  </ErrorBoundary>
);
​

src/04_condition/05App_style.jsx

// src/04_condition/04App_style.jsx
import React, { Component } from 'react';
​
class App extends Component {
  state = {
    size: 12,
    color: '#333'
  }
  changeSize = (event) => {
    this.setState({
      size: event.target.value
    })
  }
  changeColor = (event) => {
    this.setState({
      color: event.target.value
    })
  }
  render() {
    return (
      <div>
        <input type="number" min={12} max={32} step={1} onChange={ this.changeSize }/>
        <input type="color"  onChange={ this.changeColor }/>
        <div style={ { 
          fontSize: this.state.size + 'px',
          color: this.state.color
        } }>
          随着你的应用不断变大,更有意识的去关注应用状态如何组织,以及数据如何在组件之间流动会对你很有帮助。
          冗余或重复的状态往往是缺陷的根源。
          在本节中,你将学习如何组织好状态,如何保持状态更新逻辑的可维护性,以及如何跨组件共享状态。
        </div>
      </div>
    );
  }
}
​
export default App;