React 组件与状态

2023-11-11

企业项目实战 > 第二部分 > React 基础回顾

React 组件与 State

  • 什么是组件

组件是什么?每个程序员都有自己的理解:在传统语言中, 组件的定义一般来说是一个从特定的组件类中派生出来的特定的对象;而在早期的前端开发者眼里, 组件是一个可复用的独立 UI 模块;在 React 中, 得益于 JSX 语法, 所有的页面元素都被转换成了 React 对象。只要你的方法 return 的是一个 React 元素, 小到一个 text, 大到一个 page, 都可以认为是 React 组件。
归纳起来一句话:如果一个方法接受一个唯一的属性, 返回一个 React 元素, 那它就是一个 React 组件。

  • 实例:最简单的组件实现

最简单的组件是函数组件, 如同我们在上面所说明的那样, 如果一个函数接受一个唯一的属性, 返回一个 React 元素;那么我们就可以认为它是一个标准的 React 函数组件, 这种也可以称之为无状态组件, 因为它没有自己的 constructor 方法, 无法定义自己的状态集 state;

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

也可以使用 ES 中所学习的类来定义, 下面这个实现与上面的实现在 React 中是等效的, 我们一般将这种使用类的方式定义的组件称之为类组件或者有状态组件, 它可以在自己内部的 constructor 中定义一个自身的状态集 state, 然后可以在组件中对自己进行一些改变;

class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}
  • 什么是 State

React 中的 state 主要作用是组件用于保存、控制及修改组件自己的状态, 它只能在 constructor 中初始化, 我们可以用 state 来完成对行为的控制、数据的更新及界面的渲染, 由于组件不能修改父组件传入的 props, 所以我们需要使用 state 来存储组件自身需要的数据, 它的每次改变都会引发组件的更新。即每次数据的更新都是通过修改 state 属性的值, 然后 ReactJS 内部会监听 state 属性的变化, 一旦发生变化, 就会触发组件的 render 方法来更新 DOM 结构。
归纳起来一句话: state 是组件用来存储自身数据的一个对象, 它是可以改变的, 它的每次改变都会引发组件的更新。

  • 实例:State 的简单操作

class Welcome extends React.Component {
  // 构造函数
  constructor(props) {
    // 继承父类React.Component的属性和方法
    super(props);
    // 设定组件state对象
    this.state = {
      msg: 'Hello',
    };
  },
  // 事件执行方法
  onClick() {
    // 事件被触发后, 通过steState来改变state中的属性
    this.setState({
      msg: 'Hallo'
    })
  },
  render() {
    return <h1 onClick="onClick">${this.state.msg}, {this.props.name}</h1>;
  }
}

  • 哪些属性应该放到 state 中去

上面我们说到了, 我们可以用 state 来完成对行为的控制、数据的更新及界面的渲染, 所以为了避免不必要的函数调用或 Dom 渲染, 我们需要判断每一个变量是否需要记录成一个 state。

.1 变量如果是通过 props 从父组件中获取, 就不需要
.2 如果这个变量可以通过其他的 state 属性或者 props 属性经过数据处理得到, 就不需要
.3 如果变量在 render 中没有使用到, 就不需要
.4 变量在整个生命周期中都保持不变时, 也不需要

  • setState

在 hooks 出现之前, setState 是 React 中使用频率最高的一个 API, 因为 React 中并没有像 Vue 中那样去实现了一个 Object.defineProperty 来监听数据的变化, 所以, 我们想要在数据改变时能让 react 知道数据发生了变化并且重新渲染 view 层就必须使用 setState 方法来通知 React 数据发生了变化:

  1. 最常用的一种: 我们不需要进行计算, 也不需要实时获取改变后的内容, 直接 setState 就行
onClick() {
  this.setState({
    msg: 'Hallo'
  });
}

  1. 带回调函数的: setState 方法主要是告诉 React 组件有数据需要更新, 可能会导致重新渲染。所以, 为了避免每一次 setState 都重新渲染, React 在这里做了一个节流的封装, 在接收到一个 setState 操作后, 首先会将它放到一个队列内, 然后去检查是否正在更新组件, 如果正在更新组件, 无论你调用了多少次 setState, 它只会将你的操作放入这个队列, 等待当前的更新操作完成后再执行。
onClick() {
  this.setState({
    msg: 'Hallo'
  });
  // 节流封装会让上面的改变无法实时呈现
  console.log(this.state.msg)
  // 'Hello'
}

这样的话就有点小尴尬了, 我们总会有些基于最新的 state 来实现的业务流程, React 这样一搞可能就没办法进行了, 虽然官方推荐我们在 componentDidUpdate 中获取并实现业务, 但这样的话整个的代码逻辑就分离了, 可能会引起一些阅读的不便, 甚至造成一些代码冗余:

// 举个不太好的例子, 比如我们某次点击时需要对某个属性累加两次
onClick() {
  this.setState({
    num: this.state.num + 1
  });
  this.setState({
    num: this.state.num + 1
  });
}

所以, React 在这里给我们提供了一个方法可以实时获取被改变的属性: setState 方法还可以接受第二个参数用于接收一个回调函数, 当 setState 队列被执行完毕时, React 会执行这个回调函数, 这样的话我们就可以在这个回调函数中获取被改变的 state 属性的值了:

onClick() {
  this.setState({
    msg: 'Hallo'
  },
  // 这个是setState成功后的回调, 它在setState执行完成后组件开始渲染前被调用
  () => {
    console.log(this.state.msg)
  });
}

  1. 需要进行一些计算的: setState 的第一个参数不仅仅只是一个 state 对象, 它也可以是一个同步返回 state 对象的回调函数, 这个函数提供两个参数:参数一是当前的 state 对象, 参数二是当前的 props 对象, 我们可以在这个函数中对它们进行一些简单的计算后再返回; 注意这里不要使用 this.state, 因为我们刚才说了, setState 是一个异步的操作, 所以这里你使用 this.state 极有可能就会拿到一个在刚刚被改变的 state:
onClick() {
  this.setState(prevState => {
    // 我们可以在这里去做一些简单的计算
    return {
      msg: 'Hallo'
    }
  }, () => {
    // 这个是setState成功后的回调
    console.log(this.state.msg);
    // 'Hallo'
  });
}
  1. 刚刚上面说到了, setState 是一个异步的操作, 需要使用回调来重新读取被改变的值, 但也有例外的时候, 比如我们在一些类似于 setTimeout 这些异步方法中调用 setState 时, 因为 React 无法感知开发者的渲染顺序, 所以采用了直接更新 state 的操作, 而不会进行批量更新, 因为这种操作会导致 Dom 的立即渲染, 所以我们不建议使用这种方法。
componentDidMount() {
  this.setState({val: this.state.val + 1});
  console.log('第 1 次 log:', this.state.val);
  this.setState({val: this.state.val + 1});
  console.log('第 2 次 log:', this.state.val);

 setTimeout(() => {
  this.setState({val: this.state.val + 1});
  console.log('第 3 次 log:', this.state.val);
  this.setState({val: this.state.val + 1});
  console.log('第 4 次 log:', this.state.val);
 }, 0);
}
  • setState 实现的简单描述

在 React 的 setState 函数实现中, 会根据一个变量 isBatchingUpdates 判断是直接更新 this.state 还是放到 enqueueSetState 队列中, isBatchingUpdates 默认值是 false, 也就表示 setState 会同步更新 this.state。

但是 setState 中有一个函数 batchedUpdates, 这个函数会把 isBatchingUpdates 修改为 true, 而当 React 在进行队列事件处理之前就会调用这个 batchedUpdates 函数, 造成的后果, 就是由 React 在接收到一个 setState 请求时, 不会直接更新 state。关于 isBatchingUpdates 方法,除了 enqueueSetState 队列更新时会调用 batchedUpdates 来标记当前更新状态, 所有的 React 生命周期函数在执行的时候也会修改 isBatchingUpdates 的值为 true。

最后有一种例外不得不提的是,当我们在一些类似于 setTimeout 这种异步函数中执行 setState 时, 因为 React 无法感知我们的渲染顺序, 所以它放弃了修改 isBatchingUpdates 而是直接更新了 state。

let oldState = { msg: '原始值' }
// 判断是否正在执行事件的标记
let isBatchingUpdates = false // isBatchingUpdates react通过这个判断是否正在进行更新state
// react.setState中这样存储数据队列
let enqueueSetState = [] // setState队列
// 这是我杜撰的..原码中叫什么忘了
let callbackList = [] // callback队列

// 这个方法在整个setState中很多地方在调用
function batchedUpdates() {
  isBatchingUpdates = !isBatchingUpdates;
}
// 假装这是个setState
function mySetState(state, callback) {
  // 首先将需要改写的state扔到一个叫enqueueSetState的队列里
  enqueueSetState.push(state)
  // 如果有回调函数把回调函数也扔到一个队列里
  callback && Object.prototype.toString.call(callback) === 'function' && callbackList.push(callback)
  // 判断是否正在更新,如果正在更新,等待更新完成后第45行的调用
  if (isBatchingUpdates) {
    return
  }
  // 修改状态表示正在更新state
  batchedUpdates()

  // 创建一个队列副本
  const newSetState = [...enqueueSetState]
  // 清空原有队列
  enqueueSetState = []
  // 上面的作法是为了在处理数据的同时不影响后续的操作排队

  // 写state的方法
  function setState(state) {
    console.log(Object.assign(oldState, state), '处理完毕,over!')
  }
  setTimeout(() => {
    // 如果没有在更新状态,开始处理数据
    const newState = newSetState.reduce((prev, next) => {
      return Object.assign(prev, next)
    }, {})
    // 数据合并完成,写入当前state
    setState(newState)
    // 数据写入完成,修改更新状态,表示可以进行另一个队列的操作了
    batchedUpdates()
    // 检查是不是还有数据在排队,如果有排队的,从头再来
    if (enqueueSetState.length) {
      mySetState()
    }
  })
}
mySetState({ msg: '哥是第一个,后来的等着' })
mySetState({ msg: '后来的排个队' })
mySetState({ msg: '排队ing...' })
setTimeout(() => {
  mySetState({ msg: '又没赶上。。继续排队ing...' })
  mySetState({ msg: 'me too...' })
}, 500)

小节结束

什么是组件,什么是 state,这两个问题,是需要大家死记的,不一定要用我讲的这些词语描述,但要大致能说清楚

setState 是一个很灵活的方法,它接受两个参数,参数一可以是一个 state 对象,也可以是一个实时返回 state 对象的函数,参数二是一个回调,用于实时获取改变后的 state 进行更多的业务处理

然后,setState 的简单描述也要记一下,最好是把这些英文单词发音都背下来,这样面试时一旦问起,你娴熟的术语描述将会大大提升你在面试官眼中的形象,关键是,这段描述代表着你已经把 setState 的这段源码看完且理解透彻了。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

React 组件与状态 的相关文章

随机推荐

  • 洛谷 1969 积木大赛——水题

    题目 https www luogu org problemnew show P1969 include
  • 常见的几种网络故障案例分析与解决

    故障1 交换机刚加电时网络无法通信 故障现象 交换机刚刚开启的时候无法连接至其他网络 需要等待一段时间才可以 另外 需要使用一段时间之后 访问其他计算机的速度才快 如果有一段时间不使用网络 再访问的时候速度又会慢下来 故障分析 由于这台交换
  • 完全背包问题求组合数和排列数

    518 零钱兑换 II 这个是完全背包问题的典型应用 由于只是求个数 不涉及到零钱排列情况不一样算两次的情况 所以两层for循环 外层遍历物品 内层遍历背包 class Solution public int change int amou
  • 阿里云播放器prismplayer抓包的一些理解

    Prismplayer是一套在线视频播放技术方案 同时支持Flash和Html5两种播放技术 可对播放器进行功能配置和皮肤定制 其在线使用文档地址为 入口 在甘肃交通视频云联网平台中用的就是该播放器 通过抓包发现 播放的是hls的ts流 下
  • mysql存储过程

    CREATE DEFINER root localhost PROCEDURE test BEGIN DECLARE i int DEFAULT 0 DECLARE classSize int DEFAULT 0 SELECT count
  • 给jupter设置新环境

    文章目录 给jupternotebook设置新环境 遇到的报错 添加路径的方法 给jupternotebook设置新环境 先在anaconda界面新建环境 conda env list 查看conda prompt下的有的环境变量 带星号的
  • Vue2使用插件合集

    Quill 插件描述 Vue 富文本编辑器 1 下载 vue quill editor npm i vue quill editor S 2 将vue quill editor引入到main js import VueQuillEditor
  • 任务调度器leetcode621

    问题 来自LeetCode 给你一个用字符数组 tasks 表示的 CPU 需要执行的任务列表 其中每个字母表示一种不同种类的任务 任务可以以任意顺序执行 并且每个任务都可以在 1 个单位时间内执行完 在任何一个单位时间 CPU 可以完成一
  • springcloudgateway集成hystrix

    目录 一 pom引入依赖 二 RestTemplate开启ribbon的负载均衡 LoadBalanced 三 yml配置和熔断降级的fallback接口 四 技术资料 springcloudgateway和Hystrix springcl
  • 如何设计高性能的分布式锁

    什么是分布式锁 在 JVM 中 在多线程并发的情况下 我们可以使用同步锁或 Lock 锁 保证在同一时间内 只能有一个线程修改共享变量或执行代码块 但现在我们的服务都是基于分布式集群来实现部署的 对于一些共享资源 在分布式环境下使用 Jav
  • Antd Table 可编辑表格

    antd Table 官方文档提及了 可编辑单元格 可编辑行 这里解决 可编辑表格 主要思路是将 antd Table 可编辑行 与 antd Form List 相结合 将Table视为 Form List 中循环的 Form Item
  • 如何使用Hexo搭建属于自己的博客

    Hexo安装步骤 Hexo官网 环境准备 Nodejs Git node v npm v 安装Hexo npm install hexo cli g cd到你需要创建博客的文件夹 hexo init blog cd blog npm ins
  • Ubuntu系统连接罗技K380键盘

    近日向学习LInux系统的使用 便把windows系统卸载装上了Ubuntu 下面是罗技K380连接Ubuntu 系统的方法 先打开K380键盘的蓝牙 我选择2 然后进入电脑的终端 输入如下命令 bluetoothctl devices 此
  • 超详细的tomcat的下载安装和配置教程

    tomcat运行的前提是安装并配置了JDK 若没有安装配置JDK 先去安装配置JDK 如下链接 JDK 1 8的下载安装和环境变量的配置 详细步骤 一 下载tomcat 1 进入tomcat的下载 tomcat下载官网 2 点击进入 点击
  • FastDFS在Docker集群安装

    一 简介 FastDFS是由国人余庆所开发 其项目地址 https github com happyfish100 FastDFS是一个轻量级的开源分布式文件系统 主要解决了大容量的文件存储和高并发访问的问题 文件存取时实现了负载均衡 Fa
  • 基于Matlab模拟宇宙射线μ

    作者简介 热爱科研的Matlab仿真开发者 修心和技术同步精进 matlab项目合作可私信 个人主页 Matlab科研工作室 个人信条 格物致知 更多Matlab仿真内容点击 智能优化算法 神经网络预测 雷达通信 无线传感器 电力系统 信号
  • springboot+react实现前后端交互

    一 搭建springboot后台 1 创建一个Springboot项目 然后导入pom依赖 本着一切从简的原则 如果不连接数据库的话 有个spring boot starter web的依赖就够用了
  • webpack5 loader

    文章目录 作用 基本使用 内联loader loader执行顺序 loader pitch 中断 pitch pitch示例 自定义loader 查找loader loader方法参数 同步loader return方式 this call
  • mysql - 索引

    索引的分类 主键索引 唯一索引 普通索引 组合索引 以及全文索引 主键索引 非空唯一索引 一个表只有一个主键索引 在 innodb 中 主键索引的B 树包含表数据信息 PRIMARY KEY key 唯一索引 不可以出现相同的值 可以有NU
  • React 组件与状态

    企业项目实战 gt 第二部分 gt React 基础回顾 React 组件与 State 什么是组件 组件是什么 每个程序员都有自己的理解 在传统语言中 组件的定义一般来说是一个从特定的组件类中派生出来的特定的对象 而在早期的前端开发者眼里