列表和key

当我们在 React 要渲染一个列表时,如果没有在每一个被渲染的元件加上 key 这个 prop,就会跳出 Warning: Each child in a list should have a unique “key” prop. 这个错误讯息。直到开发者把 key 加上后,这个警告讯息才会消失。为什么要加上 key? 以及 key 有什么原则需要遵守? 这是在开发 React 时需要有的重要概念,也是面试经常会问的基础题。 为什么需要 key? key 就像一个独特身份,让 React 可以去分辨哪些子元件被新增、 移除,或是修改。 (编按:若要进一步说明此概念,推荐在面试时画下 Dan Abramov 的这个系列推文例子) 从上面的例子可以看到,当今天红黄蓝三个圈,变成红蓝黄;这会有两个可能性。可能性一第二个圈跟第三个圈的位置互换;可能性二是位置没互换,但第二个圈变蓝色、第三个圈变黄色。如果没有一个独特辨识的机制,我们会没办法知道,究竟是哪一个可能性。 如果没办法有效辨识,将可能出现 bug。举例来说,假如今天的圈圈是有状态的,例如里面有勾选方块,然后第个二圈有被勾选。假如今天换颜色是因为第二个圈被交换到第三个位置,这时表示新一次的渲染时,第三个圈要是被勾选的。不过假如我们没有一个辨识机制,要是 React 误以为换颜色不是因为换位置,而是单纯的第二个圈换颜色,那么将会渲染出仍是第二个圈是被勾选的;那么这就会是一个 bug。 然而,有了 key 这个辨识机制,React 就会知道在新的一次渲染时,原本的状态应该被保留在列表中的哪一个元件中。因此,React 之所以需要 key,正是因为 key 可以让 React 知道,哪些子元件被新增、 移除,或是修改。 除此之外,看到下面这段 React 官方文件的例子 (编按:面试时也推荐直接举这例子)。原本有个列表,我们在最上方新增一个 <li>Connecticut</li> ,如果没有 key,对 React 来说将会是,Duke 变成 Connecticut、Villanova 变成 Duke,而最后新增一个 Villanova。换句话说,整个列表都改变了,所以 React 会打掉旧的,重建一个新的列表。当列表变大时,就会很消耗效能。 ...

December 17, 2024

条件渲染的常见方法和注意事项

条件渲染方式 1. if 语句 先从 React 最基本的条件类型来看。如果有数据就显示组件,如果没有数据就不显示任何内容。posts 为需要渲染的列表: export default function App() { const { posts } = usePosts(); if (!posts) return null; return ( <div> <PostList posts={posts} /> </div> ); } 这种形式会生效的原因就是我们会提前返回,如果满足条件(posts 值不存在),就通过return null 在组件中不显示任何内容。 如果有多个要检查的条件时,也可以使用 if 语句。例如,在显示数据之前检查加载和错误状态: export default function App() { const { isLoading, isError, posts } = usePosts(); if (isLoading) return <div>Loading...</div>; if (isError) return <div>Error!</div>; return ( <div> <PostList posts={posts} /> </div> ); } 这里我们可以多次使用 if 语句,不需要再使用 else 或者 if-eles 语句,这样就减少了需要编写的代码,并且可读性更强。 2. 三元运算符 当我们想提前退出或者什么都不显示时,if 语句会很有用。但是,如果我们不想写一个与返回的 JSX 分开的条件,而是直接在其中写呢?那就可以使用三元表达式来编写条件。 在 React 中,我们必须在 JSX 中包含表达式,而不是语句。这就是为什么我们在 JSX 中只能使用三元表达式,而不是 if 语句来编写条件。 ...

December 17, 2024

Context API

先看看React官网对于Context的介绍 Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。在一个典型的 React 应用中,数据是通过 props 属性自上而下(由父及子)进行传递的,但这种做法对于某些类型的属性而言是极其繁琐的(例如:地区偏好,UI 主题),这些属性是应用程序中许多组件都需要的。Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props。 个人理解转成大白话:Context提供了一个局部的全局作用域,使用Context则无需再手动的逐层传递props。 本文主要介绍3种Context的使用方式: React.createContext提供的Provider和Consumer 函数组件:React.createContext提供的Provider和useContext钩子 Class组件:React.createContext提供的Provider和class的contextType属性 第一种:React.createContext提供的Provider和Consumer 先写好使用Context的基础环境条件,后续的代码都是基于此环境 //创建一个文件,暂且命名为context.js,导出createContext()的返回值 import { createContext } from "react"; export default createContext(); 在根组件App.jsx中,导入上面写的context,并使用context提供的Provider组件进行包裹,圈定局部的全局作用域,传值后可以提供给子组件进行消费 当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染。Provider 及其内部 consumer 组件都不受制于 shouldComponentUpdate 函数,因此当 consumer 组件在其祖先组件退出更新的情况下也能更新。 import React, { createContext } from "react"; import MyContext from "./context"; import GeneralC from "./GeneralC"; import FnC from "./FnC"; import ClassC from "./ClassC"; export default function App() { return ( //Provider组件接收一个value属性,此处传入一个带有name属性的对象 <MyContext.Provider value={{ name: `context's value is string!` }}> {/*这里写后面要进行包裹的子组件,此处先行导入后续需要消费context的3个组件*/} <GeneralC/> <hr/> <FnC/> <hr/> <ClassC/> </MyContext.Provider> ); } 在GeneralC组件中,导入context,使用其提供的Consumer组件来订阅Context的变更,需要一个函数作为子元素,函数的第一个形参便是Provider组件提供的value值 import React, { useReducer } from "react"; import MyContext from "./context"; const GeneralC = () => { return ( // <MyContext.Consumer> {(value) => { return ( <div> 第一种使用Context方式获取的值:{JSON.stringify(value)} </div> ); }} </MyContext.Consumer> ); }; export default GeneralC; 此时页面中应该出现json格式的value值 ...

December 17, 2024

react hooks

一 前言 React hooks是react16.8 以后,react新增的钩子API,目的是增加代码的可复用性,逻辑性,弥补无状态组件没有生命周期,没有数据管理状态state的缺陷。 本章节将介绍目前 React 提供的所有 hooks ,介绍其功能类型和基本使用方法。 1.1 技术背景 react hooks 解决了什么问题? 先设想一下,如果没有 Hooks,函数组件能够做的只是接受 Props、渲染 UI ,以及触发父组件传过来的事件。所有的处理逻辑都要在类组件中写,这样会使 class 类组件内部错综复杂,每一个类组件都有一套独特的状态,相互之间不能复用,即便是 React 之前出现过 mixin 等复用方式,但是伴随出 mixin 模式下隐式依赖,代码冲突覆盖等问题,也不能成为 React 的中流砥柱的逻辑复用方案。所以 React 放弃 mixin 这种方式。 类组件是一种面向对象思想的体现,类组件之间的状态会随着功能增强而变得越来越臃肿,代码维护成本也比较高,而且不利于后期 tree shaking。所以有必要做出一套函数组件代替类组件的方案,于是 Hooks 也就理所当然的诞生了。 所以 Hooks 出现本质上原因是: 让函数组件也能做类组件的事,有自己的状态,可以处理一些副作用,能获取 ref ,也能做数据缓存。 解决逻辑复用难的问题。 放弃面向对象编程,拥抱函数式编程。 为什么要使用自定义 Hooks ? 自定义 hooks 是在 React Hooks 基础上的一个拓展,可以根据业务需求制定满足业务需要的组合 hooks ,更注重的是逻辑单元。通过业务场景不同,到底需要React Hooks 做什么,怎么样把一段逻辑封装起来,做到复用,这是自定义 hooks 产生的初衷。 自定义 hooks 也可以说是 React Hooks 聚合产物,其内部有一个或者多个 React Hooks 组成,用于解决一些复杂逻辑。 ...

December 17, 2024

浏览器的存储机制

浏览器的存储机制涵盖多种技术,目的是满足 Web 应用在不同场景下的数据持久化、缓存和离线访问需求。它们各自具有不同的存储容量、生命周期、安全策略和访问方式,理解这些机制对于构建高效、安全的前端应用至关重要。 一、浏览器存储机制分类 1. Cookie 最早期的存储机制,主要用于会话管理和身份认证。 容量限制:单个约 4KB,总数有限制; 生命周期:可设置过期时间或为会话级; 访问方式:由浏览器自动在 HTTP 请求头中携带,也能通过 JS 访问(除非设置 HttpOnly); 安全性:可设置 HttpOnly、Secure、SameSite 防范安全风险。 2. Web Storage(LocalStorage 和 SessionStorage) HTML5 新增的简单键值对存储方案。 LocalStorage:永久存储,关闭浏览器数据仍保留,同源可访问; SessionStorage:页面会话存储,关闭标签页即清空; 容量:一般为 5~10MB; 访问方式:同步 API,容易使用但可能阻塞主线程。 3. IndexedDB 浏览器内置的底层结构化数据库,支持事务和索引,适合复杂和大容量数据存储。 容量:远大于 LocalStorage,通常以设备剩余空间为限; 访问方式:异步 API,支持复杂操作; 用途:离线应用、大数据缓存、文件存储等。 4. Cache Storage 由 Service Worker 管理的请求和响应缓存,用于离线和加速访问。 容量:较大,依赖设备和浏览器策略; 访问方式:异步 Promise API; 用途:缓存静态资源、接口响应,实现离线支持。 5. Service Worker 虽然不是存储机制本身,但作为浏览器后台代理进程,管理 Cache Storage,实现网络请求拦截和缓存策略,是现代离线应用的核心。 生命周期独立于页面,可在后台运行; 控制页面的网络请求,提供离线能力和资源预缓存; 可结合 Cache Storage 和 IndexedDB 进行数据管理。 二、存储机制的生命周期与访问作用域 Cookie:基于域和路径,可设置 HttpOnly、Secure 限制访问; LocalStorage/SessionStorage:基于同源策略,SessionStorage 进一步限定于标签页会话; IndexedDB 和 Cache Storage:基于同源,支持版本控制和升级; Service Worker:独立于页面,能控制同源下的所有相关页面。 三、容量限制与性能影响 Cookie 容量最小,且会随每次请求自动发送,影响网络性能; LocalStorage/SessionStorage 适合轻量存储,容量适中; IndexedDB 和 Cache Storage 容量大,适合海量数据和文件缓存; Service Worker 通过异步调度,避免阻塞主线程。 四、安全与隐私考虑 Cookie 可被服务器访问,设置 HttpOnly 避免脚本窃取; Web Storage、IndexedDB 只能由同源脚本访问,防止跨站数据泄漏; Service Worker 需 HTTPS 环境,避免被恶意注入; 用户隐私模式下,存储行为可能受限,数据不保证持久。 五、应用场景及选择建议 需要与服务器频繁交互、会话维持时用 Cookie; 存储简单配置、少量数据用 LocalStorage; 页面会话临时数据用 SessionStorage; 大规模结构化数据和离线存储用 IndexedDB; 静态资源及请求缓存用 Cache Storage,配合 Service Worker 提升离线体验和性能; Service Worker 负责管理缓存和拦截网络请求,实现 PWA 功能。 常见考点 面试考察点通常围绕存储类型、特点、使用场景、容量限制、安全性和生命周期展开: ...

December 17, 2024

React 生命周期

简介 React从v16.3的版本开始, 对生命周期的钩子进行了渐进式的调整,分别废弃和新增了一些生命周期的钩子函数,本文会从以下四点开始讲解: 新旧生命周期函数的对比 分析为什么要废弃旧的钩子函数 详解新的生命周期使用场景 实例代码演示并进行总结 新旧生命周期对比 一个完整的React组件生命周期会依次调用如下钩子: old lifecycle 挂载 constructor componentWillMount render componentDidMount 更新 componentWillReceiveProps shouldComponentUpdate componentWillUpdate render componentDidUpdate 卸载 componentWillUnmount new lifecycle 挂载 constructor getDerivedStateFromProps render componentDidMount 更新 getDerivedStateFromProps shouldComponentUpdate render getSnapshotBeforeUpdate componentDidUpdate 卸载 componentWillUnmount 从以上生命周期的对比,我们不难看出,React从v16.3开始废弃 componentWillMount componentWillReceiveProps componentWillUpdate 三个钩子函数 分析废弃原因 Facebook花了两年多的时间搞出了React Fiber, 因为在v15的版本,更新过程是同步的,往往一个主线程长时间被占用,会导致页面性能问题 而 React Fiber的机制: 利用浏览器 requestIdleCallback 将可中断的任务进行分片处理,每一个小片的运行时间很短,这样唯一的线程就不会被独占 需要详细了解可以点下面链接 Morgan大佬 - 知乎 那么React Fiber会对生命周期带来什么影响吗? 因为React Fiber Reconciliation 这个过程有可能暂停然后继续执行,所以挂载和更新之前的生命周期钩子就有可能不执行或者多次执行; 目前React为这几个生命周期钩子提供了别名,分别是: UNSAFE_componentWillMount UNSAFE_componentWillReceiveProps UNSAFE_componentWillUpdate React17将只提供别名,取个别名的目的就是恶心你,不让你使用。 ...

December 17, 2024

浏览器的垃圾回收机制

浏览器的垃圾回收(Garbage Collection, GC)机制是前端性能优化和内存管理的重要基础。 一、垃圾回收的基本概念 目的:自动回收不再使用的内存,避免内存泄漏,保证浏览器性能稳定。 GC 触发:当浏览器检测到内存不足或特定条件时,启动垃圾回收过程。 二、主要垃圾回收算法 1. 标记清除(Mark-and-Sweep) 浏览器从**根对象(Global、执行上下文中的变量)**开始,标记所有可达对象。 没被标记的对象被认为不可达,即不再被使用,进行回收。 是现代 JS 引擎普遍采用的算法。 2. 引用计数(Reference Counting) 每个对象维护引用计数,引用增加时计数+1,引用消失时计数-1。 计数为 0 的对象立即回收。 缺陷:无法处理循环引用,现代引擎一般不单独使用。 三、垃圾回收的触发时机 内存分配达到一定阈值时自动触发。 主动调用相关接口(如 Chrome DevTools 手动触发)。 页面卸载时进行清理。 四、内存泄漏常见原因 全局变量未释放 全局变量一直被引用,无法回收。 闭包导致的变量无法释放 闭包作用域内变量被外部引用。 定时器未清除 setInterval、setTimeout 未正确清除,导致引用保留。 DOM 节点引用未释放 JS 中持有对已删除 DOM 的引用。 事件监听未移除 绑定事件后,未及时解绑,导致内存无法回收。 五、性能优化建议 避免不必要的全局变量。 使用完定时器及时清除。 解绑不再使用的事件监听。 谨慎使用闭包,避免无用变量持久存在。 小心操作 DOM,及时释放引用。 常见考点 面试中考察点主要包括 GC 的原理、算法、触发时机、内存泄漏原因及避免方法: 浏览器垃圾回收的原理和常见算法? 标记清除与引用计数的区别与优缺点? 什么是内存泄漏?常见的内存泄漏类型? 如何避免内存泄漏? JS 引擎如何判断对象是否可回收? 浏览器中 GC 触发的时机? 如何用 Chrome DevTools 监测内存泄漏? 事件监听和闭包如何导致内存泄漏?

December 17, 2024

state 与 props

state 基本定义 state是组件内部的状态(数据),不能够直接修改,必须要通过setState来改变值的状态,从而达到更新组件内部数据的作用。 props 基本定义 props是指组件间传递的一种方式,props自然也可以传递state。由于React的数据流是自上而下的,所以是从父组件向子组件进行传递;另外组件内部的this.props属性是只读的不可修改。 代码示例 // 父组件 import stateJj from './stateJj.js'; // 引入子组件 class stateJjFather extends React.Component { constructor(props) { super(props); // 设置state的默认值 且只能在constructor中设置 this.state = { mes: '给子组件的信息', obj: { }, arr: [] } } fun(e) { } render() { const {mes, obj, arr} = this.state; return ( <div> // 不写箭头函数 this指向会发生错误 也可以使用bind的方式绑定this <stateJj name={'给子组件的信息'} name1={mes} fun={(e) => this.fun(e)} obj={obj} arr={arr} /> </div> ); } } // 子组件 class stateJj extends React.Component { constructor(props) { super(props); this.props; // 这里面就有父组件传的值 // 设置 默认state this.state = { text: props.initialValue || 'placeholder' }; // ES6 类中函数必须手动绑定 this.handleChange = this.handleChange.bind(this); } handleChange(event) { this.setState({ text: event.target.value }); } render() { return ( <div> {mes} </div> ); } } // 设置props值的类型 stateJj.propTypes = { optionalArray: PropTypes.array, optionalBool: PropTypes.bool, optionalFunc: PropTypes.func, optionalNumber: PropTypes.number, optionalObject: PropTypes.object, optionalString: PropTypes.string, optionalSymbol: PropTypes.symbol, // 目前可以声明的所有变量类型 }; // 设置默认的props值 stateJj.defaultProps = { }; props的特性 只读性 props经常被用作渲染组件和初始化状态,当一个组件被实例化之后,它的props是只读的,不可改变的。如果props在渲染过程中可以被改变,会导致这个组件显示的形态变得不可预测。只有通过父组件重新渲染的方式才可以把新的props传入组件中。 ...

December 17, 2024

浏览器的缓存机制

一、前言 缓存可以说是性能优化中简单高效的一种优化方式了。一个优秀的缓存策略可以缩短网页请求资源的距离,减少延迟,并且由于缓存文件可以重复利用,还可以减少带宽,降低网络负荷。 对于一个数据请求来说,可以分为发起网络请求、后端处理、浏览器响应三个步骤。浏览器缓存可以帮助我们在第一和第三步骤中优化性能。比如说直接使用缓存而不发起请求,或者发起了请求但后端存储的数据和前端一致,那么就没有必要再将数据回传回来,这样就减少了响应数据。 接下来的内容中我们将通过缓存位置、缓存策略以及实际场景应用缓存策略来探讨浏览器缓存机制。 如需获取思维导图或想阅读更多优质文章请猛戳GitHub博客 二、缓存位置 从缓存位置上来说分为四种,并且各自有优先级,当依次查找缓存且都没有命中的时候,才会去请求网络。 Service Worker Memory Cache Disk Cache Push Cache 1.Service Worker Service Worker 是运行在浏览器背后的独立线程,一般可以用来实现缓存功能。使用 Service Worker的话,传输协议必须为 HTTPS。因为 Service Worker 中涉及到请求拦截,所以必须使用 HTTPS 协议来保障安全。Service Worker 的缓存与浏览器其他内建的缓存机制不同,它可以让我们自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,并且缓存是持续性的。 Service Worker 实现缓存功能一般分为三个步骤:首先需要先注册 Service Worker,然后监听到 install 事件以后就可以缓存需要的文件,那么在下次用户访问的时候就可以通过拦截请求的方式查询是否存在缓存,存在缓存的话就可以直接读取缓存文件,否则就去请求数据。 当 Service Worker 没有命中缓存的时候,我们需要去调用 fetch 函数获取数据。也就是说,如果我们没有在 Service Worker 命中缓存的话,会根据缓存查找优先级去查找数据。但是不管我们是从 Memory Cache 中还是从网络请求中获取的数据,浏览器都会显示我们是从 Service Worker 中获取的内容。 2.Memory Cache Memory Cache 也就是内存中的缓存,主要包含的是当前中页面中已经抓取到的资源,例如页面上已经下载的样式、脚本、图片等。读取内存中的数据肯定比磁盘快,内存缓存虽然读取高效,可是缓存持续性很短,会随着进程的释放而释放。 一旦我们关闭 Tab 页面,内存中的缓存也就被释放了。 那么既然内存缓存这么高效,我们是不是能让数据都存放在内存中呢? 这是不可能的。计算机中的内存一定比硬盘容量小得多,操作系统需要精打细算内存的使用,所以能让我们使用的内存必然不多。 当我们访问过页面以后,再次刷新页面,可以发现很多数据都来自于内存缓存 内存缓存中有一块重要的缓存资源是preloader相关指令(例如<link rel="prefetch">)下载的资源。总所周知preloader的相关指令已经是页面优化的常见手段之一,它可以一边解析js/css文件,一边网络请求下一个资源。 需要注意的事情是,内存缓存在缓存资源时并不关心返回资源的HTTP缓存头Cache-Control是什么值,同时资源的匹配也并非仅仅是对URL做匹配,还可能会对Content-Type,CORS等其他特征做校验。 3.Disk Cache Disk Cache 也就是存储在硬盘中的缓存,读取速度慢点,但是什么都能存储到磁盘中,比之 Memory Cache 胜在容量和存储时效性上。 ...

December 17, 2024

元素、组件、实例和节点

React 中的元素、组件、实例和节点,是React中关系密切的4个概念,也是很容易让React 初学者迷惑的4个概念。现在,老干部就来详细地介绍这4个概念,以及它们之间的联系和区别,满足喜欢咬文嚼字、刨根问底的同学(老干部就是其中一员)的好奇心。 元素 (Element) React 元素其实就是一个简单JavaScript对象,一个React 元素和界面上的一部分DOM对应,描述了这部分DOM的结构及渲染效果。一般我们通过JSX语法创建React 元素,例如: const element = <h1 className='greeting'>Hello, world</h1>; element是一个React 元素。在编译环节,JSX 语法会被编译成对React.createElement()的调用,从这个函数名上也可以看出,JSX语法返回的是一个React 元素。上面的例子编译后的结果为: const element = React.createElement( 'h1', {className: 'greeting'}, 'Hello, world!' ); 最终,element的值是类似下面的一个简单JavaScript对象: const element = { type: 'h1', props: { className: 'greeting', children: 'Hello, world' } } React 元素可以分为两类:DOM类型的元素和组件类型的元素。DOM类型的元素使用像h1、div、p等DOM节点创建React 元素,前面的例子就是一个DOM类型的元素;组件类型的元素使用React 组件创建React 元素,例如: const buttonElement = <Button color='red'>OK</Button>; buttonElement就是一个组件类型的元素,它的值是: const buttonElement = { type: 'Button', props: { color: 'red', children: 'OK' } } 对于DOM类型的元素,因为和页面的DOM节点直接对应,所以React知道如何进行渲染。但是对于组件类型的元素,如buttonElement,React是无法直接知道应该把buttonElement渲染成哪种结构的页面DOM,这时就需要组件自身提供React能够识别的DOM节点信息,具体实现方式在介绍组件时会详细介绍。 有了React 元素,我们应该如何使用它呢?其实,绝大多数情况下,我们都不会直接使用React 元素,React 内部会自动根据React 元素,渲染出最终的页面DOM。更确切地说,React元素描述的是React虚拟DOM的结构,React会根据虚拟DOM渲染出页面的真实DOM。 组件 (Component) React 组件,应该是大家最熟悉的React中的概念。React通过组件的思想,将界面拆分成一个个可以复用的模块,每一个模块就是一个React 组件。一个React 应用由若干组件组合而成,一个复杂组件也可以由若干简单组件组合而成。 React组件和React元素关系密切,React组件最核心的作用是返回React元素。这里你也许会有疑问:React元素不应该是由React.createElement() 返回的吗?但React.createElement()的调用本身也是需要有“人”负责的,React组件正是这个“责任人”。React组件负责调用React.createElement(),返回React元素,供React内部将其渲染成最终的页面DOM。 既然组件的核心作用是返回React元素,那么最简单的组件就是一个返回React元素的函数: function Welcome(props) { return <h1>Hello, {props.name}</h1>; } Welcome是一个用函数定义的组件。如果使用类(class)定义组件,返回React元素的工作具体就由组件的render方法承担,例如: ...

December 17, 2024