react 状态提升

在开始介绍状态提升之前,我想先介绍一下 Prop Drilling(属性钻取)的概念。 什么是Prop Drilling ? 那么,什么是状态提升呢?我们都知道 React 中的prop drilling(属性钻取)。prop drilling意味着从父组件向子组件传递一些 props。通过这种方式,我们可以在不同的组件之间共享数据或函数。可以使用 prop 传递任何类型的数据。 在上面的示例代码中,你可以看到,Home 组件具有一个名为 name 的状态,并且她与子组件共用 name 变量。在 React 中,只能将数据从父组件传递到子组件。 什么是 State Lifting 一般来说,状态提升允许你将数据从子组件移动到父组件,与Props Drilling不同,你可以在父组件使用子组件的状态值。这是不是很疯狂? 在 React 生态中,我们知道数据只能从父组件流向子组件,但现在将学习如何让数据反过来流动。 但是,为什么要这么做? 我们可以在父组件中定义状态并将其以 prop 的方式传递给子组件,但是这么做有可能会导致性能损失和代码复杂度提升。在我之前的博客中有提到:保持状态本地化很重要。因为当状态更新时,组件以及该组件的子组件都会重新渲染。 此外对于代码复杂性,对于如 Modal 这样的组件,选择在子组件中声明状态而不是在父组件中声明状态,将能够使代码更易于阅读。使用这种方法,你不必在每个父组件中声明状态。 通常对应用程序中的大多数组件来说,这是不必要的。但是,对于某些类型的组件,特别是可重用组件,会有这种需求。下面描述一下最通用的场景。 多说无用,上代码: 例如,我们有一个常见的 Modal 组件,我们想要在 Modal 中保留可见性状态。当一些组件想要使用这个 Modal 时,通常会定义自己的可见状态,但是通过 State Lifting,这个步骤将不再必要。 在 上面的 Modal 组件中,做了如下操作: 首先,使用forwardRef执行 ref 转发,ref 转发是一种自动将 ref 通过组件传递给子组件的技术。 使用 useState 声明状态以控制Modal组件的展示或隐藏。 使用 React 内置的 useImperativeHandle hook 将属性或者方法暴露出去,这里我们添加了一些数据和函数供父组件使用。现在父组件可以直接调用子组件的show、hide、isVisble这些方法和属性。 ...

December 17, 2024

BFC

BFC 是前端面试中经常会被问到的一个知识点,今天就来带大家深入了解下什么是 BFC 以及它的作用。 文档流 在介绍BFC之前,需要先给大家介绍一下文档流。 我们常说的文档流其实分为定位流、浮动流、普通流三种。 绝对定位(Absolute positioning) 如果元素的属性 position 为 absolute 或 fixed,它就是一个绝对定位元素。 在绝对定位布局中,元素会整体脱离普通流,因此绝对定位元素不会对其兄弟元素造成影响,而元素具体的位置由绝对定位的坐标决定。 它的定位相对于它的包含块,相关CSS属性:top、bottom、left、right; 对于 position: absolute,元素定位将相对于上级元素中最近的一个relative、fixed、absolute,如果没有则相对于body; 对于 position:fixed,正常来说是相对于浏览器窗口定位的,但是当元素祖先的 transform 属性非 none 时,会相对于该祖先进行定位。 浮动 (float) 在浮动布局中,元素首先按照普通流的位置出现,然后根据浮动的方向尽可能的向左边或右边偏移,其效果与印刷排版中的文本环绕相似。 普通流 (normal flow) 普通流其实就是指BFC中的FC。FC(Formatting Context),直译过来是格式化上下文,它是页面中的一块渲染区域,有一套渲染规则,决定了其子元素如何布局,以及和其他元素之间的关系和作用。 在普通流中,元素按照其在 HTML 中的先后位置至上而下布局,在这个过程中,行内元素水平排列,直到当行被占满然后换行。块级元素则会被渲染为完整的一个新行。 除非另外指定,否则所有元素默认都是普通流定位,也可以说,普通流中元素的位置由该元素在 HTML 文档中的位置决定。 BFC 概念 先看下MDN上关于BFC的定义: 块格式化上下文(Block Formatting Context,BFC) 是Web页面的可视CSS渲染的一部分,是块盒子的布局过程发生的区域,也是浮动元素与其他元素交互的区域。 具有 BFC 特性的元素可以看作是隔离了的独立容器,容器里面的元素不会在布局上影响到外面的元素,并且 BFC 具有普通容器所没有的一些特性。 通俗一点来讲,可以把 BFC 理解为一个封闭的大箱子,箱子内部的元素无论如何翻江倒海,都不会影响到外部。 除了 BFC,还有: IFC(行级格式化上下文)- inline 内联 GFC(网格布局格式化上下文)- display: grid FFC(自适应格式化上下文)- display: flex或display: inline-flex 注意:同一个元素不能同时存在于两个 BFC 中。 ...

November 9, 2024

离线存储

HTML5 离线存储是指通过 Web Storage API 提供的一种在客户端本地存储数据的机制,主要有两种方式:localStorage 和 sessionStorage。与传统的 Cookie 相比,Web Storage 提供了更大容量、灵活性,并且数据不会随着 HTTP 请求自动发送到服务器,这可以优化性能和安全性。此外,HTML5 还支持 离线应用缓存(Application Cache) 和 IndexedDB,用于更加复杂的数据存储需求和离线应用。 工作原理 HTML5 的离线存储主要通过以下几个机制来实现数据存储和访问: localStorage:一种持久性存储机制,数据存储在用户的浏览器中,即使关闭浏览器或重新加载页面,数据也不会丢失,除非明确被删除。其特点是跨页面、跨会话存储数据。 sessionStorage:会话级别的存储机制,只在当前页面的会话期间有效。当会话结束或浏览器窗口关闭时,数据会自动删除。其特点是只能在当前窗口或标签页中存储数据,不能跨窗口或标签页共享。 Application Cache(已被 Service Worker 替代):允许开发者将网站的某些资源缓存到本地,使得用户即使在离线的情况下也能访问特定页面。虽然该机制已被弃用,但它曾经是实现离线访问的核心。 IndexedDB:一种更为复杂的浏览器内置数据库,支持存储结构化数据,如对象和文件。它适用于更大规模的数据存储需求,并且允许通过索引进行高效的查询和数据操作。 使用方式 1. localStorage 特点:持久性存储,浏览器关闭后数据依然存在,除非手动清除。 使用方式: // 存储数据 localStorage.setItem('key', 'value'); // 获取数据 let value = localStorage.getItem('key'); // 删除数据 localStorage.removeItem('key'); // 清空所有 localStorage localStorage.clear(); 2. sessionStorage 特点:仅在当前会话期间有效,浏览器关闭或页面重新加载后数据会被清除。 使用方式: // 存储数据 sessionStorage.setItem('sessionKey', 'sessionValue'); // 获取数据 let sessionValue = sessionStorage.getItem('sessionKey'); // 删除数据 sessionStorage.removeItem('sessionKey'); // 清空所有 sessionStorage sessionStorage.clear(); 3. IndexedDB 特点:适合存储大量的结构化数据,支持异步 API 和事务。 使用方式: IndexedDB 的使用较为复杂,通常涉及打开数据库、创建事务、存储和检索数据等操作。一个简单的示例如下: let request = indexedDB.open('myDatabase', 1); request.onupgradeneeded = function(event) { let db = event.target.result; let objectStore = db.createObjectStore('myObjectStore', { keyPath: 'id' }); }; request.onsuccess = function(event) { let db = event.target.result; let transaction = db.transaction(['myObjectStore'], 'readwrite'); let objectStore = transaction.objectStore('myObjectStore'); objectStore.add({ id: 1, name: 'John Doe' }); }; 4. Application Cache(弃用) 特点:通过 manifest 文件指定需要缓存的资源,允许在离线时访问应用。 使用方式: 在 HTML 文档中指定 manifest 文件: <html manifest="app.manifest"> 然后在 app.manifest 文件中列出需要缓存的资源: ...

October 16, 2024

工具函数

一、Vue.util:Vue.js的工具函数集合 Vue.util是Vue.js内部的一个工具函数集合,包含了多种实用的方法,用于处理各种常见的任务。然而,需要注意的是,Vue.util并不是Vue.js公开API的一部分,这意味着它的使用可能会随着Vue.js版本的更新而发生变化。因此,在开发过程中,应尽量避免直接使用Vue.util中的方法,而是使用Vue.js提供的公开API或其他更稳定的解决方案。 尽管如此,了解Vue.util中的一些方法仍然有助于我们更好地理解Vue.js的内部机制。例如,Vue.util.extend方法用于将一个或多个对象的属性混合到目标对象中,这在需要将多个对象合并到一个新对象中时非常有用。然而,随着Vue.js的发展,许多原本在Vue.util中的方法已经被移动到其他更合适的API中,或者被更现代的方法所替代。 二、Vue.set:动态添加响应式属性 Vue.set是Vue.js提供的一个全局方法,用于向响应式对象添加一个新的属性,并确保该属性是响应式的。在默认情况下,Vue.js只能检测到已经存在的对象的属性并将其转换为响应式属性,但对于后期动态添加的属性,Vue.js无法自动进行响应式转换。这时,就可以使用Vue.set方法手动将属性转换为响应式。 Vue.set方法接受三个参数:对象、属性名和属性值。例如: Vue.set(vm.someObject, 'newProperty', 'Hello, Vue!'); 上面的代码将someObject对象的newProperty属性设置为'Hello, Vue!',并将其转换为响应式属性。这样,当属性值发生修改时,Vue可以检测到并响应式地更新视图。 Vue.set方法的主要优势在于它能够将属性添加为响应式属性,使得Vue.js能够跟踪和响应属性值的变化,从而及时更新视图。此外,Vue.set方法还可以用于在数组中添加新元素,使其成为响应式。 三、Vue.delete:删除响应式对象的属性 Vue.delete是Vue.js提供的另一个全局方法,用于从响应式对象中删除指定的属性。与Vue.set方法类似,Vue.delete也可以触发响应式更新,使Vue能够正确追踪到属性的删除操作。 Vue.delete方法接受两个参数:要删除的属性所在的对象和要删除的属性名。例如: Vue.delete(this.someObject, 'someProperty'); 上面的代码将从someObject对象中删除someProperty属性,并相应地更新视图。 需要注意的是,Vue.delete方法只能用于响应式对象,而不能用于普通的JavaScript对象。此外,在删除数组中的元素时,Vue.js提供了一些数组特定的方法(如splice、push、pop等)来确保数组的响应式更新。因此,在删除数组元素时,应优先使用这些方法而不是Vue.delete。 四、实际应用中的注意事项 在使用Vue.set和Vue.delete等工具函数时,需要注意以下几点: 避免直接操作对象或数组:在Vue.js中,直接修改对象或数组的属性或元素可能不会触发视图更新。因此,应优先使用Vue.set和Vue提供的数组方法来修改响应式数据。 注意命名冲突:在添加新属性时,应确保属性名不与现有属性名冲突,以避免覆盖现有属性。 了解Vue.js的响应式机制:为了更好地使用Vue.set和Vue.delete等工具函数,需要了解Vue.js的响应式机制以及它是如何跟踪和响应数据变化的。 常见考点 1. Vue 内置工具函数 问题: Vue 提供了哪些内置工具函数? 这些工具函数的使用场景是什么? 考察点: Vue.set 和 Vue.delete: Vue 中的 Vue.set 和 Vue.delete 是为了响应式系统而提供的工具函数,它们用于确保在数组或对象中添加、删除元素时,能够保持数据的响应性。 Vue.set:用于向对象中添加新的属性,并确保该属性是响应式的。 Vue.set(obj, 'newProp', value); Vue.delete:用于删除对象或数组中的元素,并保持响应性。 Vue.delete(obj, 'propertyName'); Vue.delete(arr, index); Vue.nextTick: Vue.nextTick 是一个异步队列,用于在 DOM 更新后执行回调函数。它保证了在 Vue 完成 DOM 更新后,回调才会被执行。 Vue.nextTick(() => { console.log('DOM has been updated!'); }); Vue.util: Vue 提供了一些工具函数,如 Vue.util.extend 用于对象的深拷贝,Vue.util.merge 用于合并对象。 2. 自定义工具函数的常见应用 问题: 你在 Vue 项目中常用哪些自定义工具函数?请列举一些例子。 如何编写一个简单的工具函数来简化 Vue 中的日期格式化? 考察点: 日期格式化工具函数: 在 Vue 项目中,常常需要对日期进行格式化。你可以创建一个工具函数来统一处理日期格式。 在组件中使用该工具函数: // utils/date.js export function formatDate(date, format = 'YYYY-MM-DD') { const d = new Date(date); const map = { 'YYYY': d.getFullYear(), 'MM': String(d.getMonth() + 1).padStart(2, '0'), 'DD': String(d.getDate()).padStart(2, '0'), 'HH': String(d.getHours()).padStart(2, '0'), 'mm': String(d.getMinutes()).padStart(2, '0'), 'ss': String(d.getSeconds()).padStart(2, '0'), }; return format.replace(/YYYY|MM|DD|HH|mm|ss/g, matched => map[matched]); } import { formatDate } from './utils/date'; export default { data() { return { date: '2024-12-15T12:00:00', }; }, computed: { formattedDate() { return formatDate(this.date, 'YYYY-MM-DD HH:mm:ss'); } } }; 深拷贝工具函数: 在某些场景下,你可能需要深拷贝对象,避免对原始对象进行修改。 export function deepClone(obj) { return JSON.parse(JSON.stringify(obj)); } 3. Vue 的过滤器(Filters) 问题: Vue 中的过滤器有什么作用?举个例子说明。 过滤器和工具函数有何区别,何时使用过滤器,何时使用工具函数? 考察点: Vue 过滤器: 过滤器是用于格式化输出数据的一种工具,在 Vue 模板中可以直接使用,但仅适用于模板中的数据绑定。常用的场景是格式化日期、数字等。 Vue.filter('currency', function (value) { return '$' + value.toFixed(2); }); // 在模板中使用 {{ price | currency }} 工具函数与过滤器的区别: 过滤器:通常用于模板中,只能格式化输出。 工具函数:可以在组件的任何地方使用,提供更强的逻辑处理能力,适合更复杂的功能(如数据计算、事件管理等)。 4. Vuex 与工具函数 问题: 如何通过工具函数简化 Vuex 的一些操作? 你会如何编写一个工具函数来帮助管理 Vuex 中的 getter、mutation 或 action? 考察点: 在 Vuex 中,常常需要对数据进行集中管理。可以通过工具函数来简化 Vuex 操作,避免重复代码。 ...

October 5, 2024

Reflect

一、Reflect有什么用? 一句话,Reflect没什么用,除了装装逼,让人看起来高大上以外,并不具有什么牛逼之处。 准确讲应该是这样的,Reflect更像是一种语法变体,其挂在的所有方法都能找到对应的原始语法,也就是Reflect的替代性非常强。 其实从Reflect这个单词本身字面意思就能体会出Reflect的神韵,Reflect的中文意思是“反射”,阳光照在镜子上反射,其实光子还是那些光子,只是变化了方向。 举例说明: Reflect对象挂载了很多静态方法,所谓静态方法,就是和Math.round()这样,不需要new就可以直接使用的方法。 比较常用的两个方法就是get()和set()方法: Reflect.get(target, propertyKey\[, receiver\]) Reflect.set(target, propertyKey, value\[, receiver\]) 就作用而言,等同于: target[propertyKey] target[propertyKey] = value; 比方说页面上有个输入框,其DOM对象变量是input,平时我们对整个输入框赋值使用的语句多半是: input.value = 'zhangxinxu'; 就可以直接使用Reflect.set()方法代替: Reflect.set(input, 'value', 'zhangxinxu') 效果是一模一样的。 又例如,我们希望对input的value属性重新定义,使该输入框value属性发生变化的时候可以同时触发'change'事件,下面是使用大家普遍比较熟悉的Object.defineProperty()方法实现的示意: const props = Object.getOwnPropertyDescriptor(input, 'value'); Object.defineProperty(input, 'value', { ...props, set (v) { let oldv = this.value; props.set.call(this, v); // 手动触发change事件 if (oldv !== v) { this.dispatchEvent(new CustomEvent('change')); } } }); 上述代码我们完全可以使用Reflect对象实现,具体的JavaScript代码如下所示。 const props = Reflect.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value'); Reflect.defineProperty(input, 'value', { ...props, set (v) { let oldv = this.value; props.set.call(this, v); // 手动触发change事件 if (oldv !== v) { this.dispatchEvent(new CustomEvent('change')); } } }); 我们可以测试下,假设页面HTML如下: <input id="input"> 测试代码为: input.addEventListener('change', () => { document.body.append('变化啦~'); }); input.value = 'zhangxinxu'; 此时,就可以看到页面上出现了“变化啦~”文字 ...

October 2, 2024

模块化

前言 在JavaScript发展初期就是为了实现简单的页面交互逻辑,寥寥数语即可;如今CPU、浏览器性能得到了极大的提升,很多页面逻辑迁移到了客户端(表单验证等),随着web2.0时代的到来,Ajax技术得到广泛应用,jQuery等前端库层出不穷,前端代码日益膨胀,此时在JS方面就会考虑使用模块化规范去管理。 本文内容主要有理解模块化,为什么要模块化,模块化的优缺点以及模块化规范,并且介绍下开发中最流行的CommonJS, AMD, ES6、CMD规范。 本文试图站在小白的角度,用通俗易懂的笔调介绍这些枯燥无味的概念,希望诸君阅读后,对模块化编程有个全新的认识和理解! 一、模块化的理解 1.什么是模块? 将一个复杂的程序依据一定的规则(规范)封装成几个块(文件), 并进行组合在一起 块的内部数据与实现是私有的, 只是向外部暴露一些接口(方法)与外部其它模块通信 2.模块化的进化过程 全局function模式 : 将不同的功能封装成不同的全局函数 编码: 将不同的功能封装成不同的全局函数 问题: 污染全局命名空间, 容易引起命名冲突或数据不安全,而且模块成员之间看不出直接关系 function m1(){ //... } function m2(){ //... } namespace模式 : 简单对象封装 作用: 减少了全局变量,解决命名冲突 问题: 数据不安全(外部可以直接修改模块内部的数据) let myModule = { data: 'www.baidu.com', foo() { console.log(`foo() ${this.data}`) }, bar() { console.log(`bar() ${this.data}`) } } myModule.data = 'other data' //能直接修改模块内部的数据 myModule.foo() // foo() other data 这样的写法会暴露所有模块成员,内部状态可以被外部改写。 IIFE模式:匿名函数自调用(闭包) 作用: 数据是私有的, 外部只能通过暴露的方法操作 编码: 将数据和行为封装到一个函数内部, 通过给window添加属性来向外暴露接口 问题: 如果当前这个模块依赖另一个模块怎么办? // index.html文件 <script type="text/javascript" src="module.js"></script> <script type="text/javascript"> myModule.foo() myModule.bar() console.log(myModule.data) //undefined 不能访问模块内部数据 myModule.data = 'xxxx' //不是修改的模块内部的data myModule.foo() //没有改变 </script> // module.js文件 (function(window) { let data = 'www.baidu.com' //操作数据的函数 function foo() { //用于暴露有函数 console.log(`foo() ${data}`) } function bar() { //用于暴露有函数 console.log(`bar() ${data}`) otherFun() //内部调用 } function otherFun() { //内部私有的函数 console.log('otherFun()') } //暴露行为 window.myModule = { foo, bar } //ES6写法 })(window) 最后得到的结果: ...

October 1, 2024

iOS中的内存管理

本文将系统介绍iOS中的内存管理机制,从程序内存布局、引用计数原理、ARC机制到系统级内存管理,帮助你建立完整的iOS内存管理知识体系。 一、程序内存布局 内存区域划分 在iOS应用程序中,内存按照用途和管理方式分为以下几个区域: 高地址 ┌─────────────────────────────────────┐ │ 栈区 (Stack) │ ↓ 向低地址增长 │ 局部变量、函数参数、返回地址 │ ├─────────────────────────────────────┤ │ ↕ │ │ 动态分配区 │ │ ↕ │ ├─────────────────────────────────────┤ │ 堆区 (Heap) │ ↑ 向高地址增长 │ 动态分配的对象实例 │ ├─────────────────────────────────────┤ │ 全局/静态区 (BSS) │ │ 未初始化的全局变量和静态变量 │ ├─────────────────────────────────────┤ │ 数据区 (Data) │ │ 已初始化的全局变量和静态变量 │ ├─────────────────────────────────────┤ │ 常量区 (Rodata) │ │ 字符串常量等 │ ├─────────────────────────────────────┤ │ 代码区 (Text) │ │ 编译后的代码 │ └─────────────────────────────────────┘ 低地址 各区域的特点: ...

May 2, 2026

OpenClaw 源码导读(二):Gateway 控制平面 — 一条 WebSocket 连上所有人

在系列第一篇里我们看到,OpenClaw 的核心隐喻是 “Gateway 是控制平面”。它不存转储消息、不排队、不持久化事件流,而是一个长在 127.0.0.1:18789 上的状态化 RPC 服务器,所有客户端(CLI、macOS.app、iOS/Android Node、WebChat 浏览器、Canvas iframe)都靠一条 WebSocket 接入。 这一篇拆开控制平面的五个层面: 进程入口:CLI 怎么从 openclaw gateway 走到 Node 进程常驻 WebSocket 协议:握手帧、RPC 帧、Event 帧、状态快照与版本号 鉴权矩阵:Token / Password / Device Token / Bootstrap Token / Tailscale Whois / Trusted Proxy Pairing:陌生客户端怎么合法加入(setup code、pairing store、allowFrom) Session 模型:Session Key 组成规则、多 Agent 路由、Session ID 模糊匹配 注:下文所有 src/ 路径都指 openclaw/openclaw 仓库根下。 一、CLI 到 Gateway 的六段路径 第一篇概览过 src/entry.ts 和 src/cli/run-main.ts,这里按调用序重新画一遍,把每个关键函数在 graph 上标出来: flowchart TD A0["openclaw gateway --port 18789"] --> A1 A1["entry.tsmain guard + process.title"] --> A2 A2["normalizeEnv + enableCompileCache()"] --> A3 A3["ensureCliRespawnReady()按需 spawn 子 Node"] --> A4 A4["parseCliContainerArgsparseCliProfileArgs"] --> A5 A5["tryHandleRootVersionFastPathtryHandleRootHelpFastPath"] -->|miss| A6 A6["runMainOrRootHelp → runCli(argv)"] --> B0 B0["run-main.tscontainer? container in progress"] --> B1 B1["loadCliDotEnv (~/.openclaw/.env)"] --> B2 B2["assertSupportedRuntime (Node≥22.16)"] --> B3 B3["tryRouteCli(fast-path 子命令路由)"] -->|miss| B4 B4["buildProgram() + commander 注册"] --> B5 B5["installUnhandledRejectionHandler"] --> B6 B6["registerPluginCliCommandsFromValidatedConfig (lazy)"] --> B7 B7["program.parseAsync"] --> C0 C0["gateway 命令匹配 → gatewayCommandAction"] --> C1 C1["lock 文件 + 端口 acquire"] --> C2 C2["HTTP/WS server 启动"] --> C3 C3["channels/plugins/cron/canvas-host 启动"] --> C4["Ready"] 1. 为什么要自己 spawn 自己 ensureCliRespawnReady() 是 src/entry.ts 里最容易被忽视但非常重要的段落: ...

May 2, 2026

http2.0/http3.0

前言 HTTP/2 相比于 HTTP/1,可以说是大幅度提高了网页的性能,只需要升级到该协议就可以减少很多之前需要做的性能优化工作,当然兼容问题以及如何优雅降级应该是国内还不普遍使用的原因之一。 虽然 HTTP/2 提高了网页的性能,但是并不代表它已经是完美的了,HTTP/3 就是为了解决 HTTP/2 所存在的一些问题而被推出来的。 一、HTTP 协议 HTTP 协议是 HyperText Transfer Protocol(超文本传输协议)的缩写,它是互联网上应用最为广泛的一种网络协议。所有的 WWW 文件都必须遵守这个标准。伴随着计算机网络和浏览器的诞生,HTTP1.0 也随之而来,处于计算机网络中的应用层,HTTP 是建立在 TCP 协议之上,所以HTTP 协议的瓶颈及其优化技巧都是基于 TCP 协议本身的特性,例如 tcp 建立连接的 3 次握手和断开连接的 4 次挥手以及每次建立连接带来的 RTT 延迟时间。 二、HTTP/1.x 的缺陷 连接无法复用:连接无法复用会导致每次请求都经历三次握手和慢启动。三次握手在高延迟的场景下影响较明显,慢启动则对大量小文件请求影响较大(没有达到最大窗口请求就被终止)。 HTTP/1.0 传输数据时,每次都需要重新建立连接,增加延迟。 HTTP/1.1 虽然加入 keep-alive 可以复用一部分连接,但域名分片等情况下仍然需要建立多个 connection,耗费资源,给服务器带来性能压力。 Head-Of-Line Blocking(HOLB):导致带宽无法被充分利用,以及后续健康请求被阻塞。HOLB是指一系列包(package)因为第一个包被阻塞;当页面中需要请求很多资源的时候,HOLB(队头阻塞)会导致在达到最大请求数量时,剩余的资源需要等待其他资源请求完成后才能发起请求。 HTTP 1.0:下个请求必须在前一个请求返回后才能发出,request-response对按序发生。显然,如果某个请求长时间没有返回,那么接下来的请求就全部阻塞了。 HTTP 1.1:尝试使用 pipeling 来解决,即浏览器可以一次性发出多个请求(同个域名,同一条 TCP 链接)。但 pipeling 要求返回是按序的,那么前一个请求如果很耗时(比如处理大图片),那么后面的请求即使服务器已经处理完,仍会等待前面的请求处理完才开始按序返回。所以,pipeling 只部分解决了 HOLB。 如上图所示,红色圈出来的请求就因域名链接数已超过限制,而被挂起等待了一段时间。 协议开销大: HTTP1.x 在使用时,header 里携带的内容过大,在一定程度上增加了传输的成本,并且每次请求 header 基本不怎么变化,尤其在移动端增加用户流量。 安全因素:HTTP1.x 在传输数据时,所有传输的内容都是明文,客户端和服务器端都无法验证对方的身份,这在一定程度上无法保证数据的安全性 三、SPDY 协议 因为 HTTP/1.x 的问题,我们会引入雪碧图、将小图内联、使用多个域名等等的方式来提高性能。不过这些优化都绕开了协议,直到 2009 年,谷歌公开了自行研发的 SPDY 协议,主要解决 HTTP/1.1 效率不高的问题。谷歌推出 SPDY,才算是正式改造 HTTP 协议本身。降低延迟,压缩 header 等等,SPDY 的实践证明了这些优化的效果,也最终带来 HTTP/2 的诞生。 ...

July 28, 2025

forwardRef 该怎么用?

React.forwardRef 对于ref转发,官网是这样描述的 Ref 转发是一项将 ref 自动地通过组件传递到其一子组件的技巧。对于大多数应用中的组件来说,这通常不是必需的。但其对某些组件,尤其是可重用的组件库是很有用的。 想详细了解的可以去看官方文档 Refs 转发 – React (docschina.org) 现在让我们直奔主题吧! React.forwardRef(render)的返回值是react组件,接收的参数是一个 render函数,函数签名为render(props, ref),第二个参数将其接受的 ref 属性转发到render返回的组件中。 这项技术并不常见,但在以下两种场景中特别有用: 转发 ref 到组件内部的DOM 节点上 在高阶组件中转发ref 转发 ref到组件内部的DOM节点 比如我们想要将一个组件内部的某个元素暴露出去, 就可以这么做 // App.js import React from 'react'; import Foo from './component/Foo'; export default class App extends React.Component { constructor(props) { super(props); this.state = {}; this.input = React.createRef(); // 1 // ↑5 } handleClick = (e) => { const input = this.input.current; // 6 console.log(input); console.log(input.value); input.focus(); } render() { return ( <> <button onClick={this.handleClick}>click to get value</button> {/*2*/} <Foo ref={this.input}/> </> ) } } // Foo.jsx import React from 'react'; // 3 const Foo = React.forwardRef((props, myRef) => { return ( <div> <p>....一些其他节点</p> {/*4*/} <input type="text" defaultValue='ref 成功转发到 Foo 组件内部的 input节点上' ref={myRef}/> <p>....一些其他节点</p> <p>....一些其他节点</p> </div> ); }); export default Foo; 仔细看代码中标记的数字,这是ref转发的流程: ...

December 17, 2024