
## **什么是IntersectionObserver** 首先,说IntersectionObserver之前先谈谈懒加载,传统的实现方式便是通过监听scroll事件调用dom的getBounddingClientRect()方法,通过坐标位置判断是否在视口从而选择加载方式,也就是说它必须监听scroll,即使防抖了触发平路也很高,所以新出了一个API,IntersectionObserver,它可以监听dom是否在可视区域内。 ## **用法** ```text const io = new IntersectionObserver(callback, option); ``` 他说个构造函数,第一个参数是当所有dom中的可见状态发生改变时触发的回调,初始化立即执行一次 ```text const io = new IntersectionObserver((entries) => { }); ``` entries是一个数组,第一项为其对于信息,我们战且只关注一个属性`isIntersecting`它表示该dom是否是可见状态,接下来看实力上的方法 ### **observe** 开启监视,它接受一个参数,值是Element类型,也就是监视的dom ```text io.observe(dom) ``` ### **unobserve** 停止观察,参数和observe一致 ### **disconnect** ```text // 关闭观察器,也就是结束这个实例的所有监听 io.disconnect() ``` ## **应用** 图片懒加载指令设计 ```text const ob = (el: Element, bind: any) => { const io = new IntersectionObserver(([{ isIntersecting }]) => { const show = () => { io.unobserve(el); (el as HTMLImageElement).src = bind.value; io.disconnect(); }; if (isIntersecting) { show(); } else { (el as HTMLImageElement).src = LoadingImg; } }); io.observe(el); }; ```


## **概念** 模块化其实是指解决一个复杂问题时 `自顶向下逐层把系统划分成若干模块` 的过程,每个模块完成一个特定的子功能(单一职责),所有的模块按某种方法组装起来,成为一个整体,从而完成整个系统所要求的功能 ## **早期js模块化方案** ### **普通函数** 因为 JS 中函数是有独立作用域的,所以也可以让变量私有化,而在多个文件中使用时,无法保证它们不与其它模块发生命名冲突, 而且模块成员之间看不出直接关系.此时便有了命名空间这个概念 ### **命名空间** 命名空间模式是房子变量函数在定义时发生命名从图的一种模式 把模块写成一个对象,所有的模块成员都放到这个对象里面: ```text var myModule = { name: "a1ex", getName: function (){ console.log(this.name) } } myModule.getName() // a1ex ``` 但是这样写你会发现,我们可以通过i对象内属性重新赋值而改变内部状态 ### **立即执行函数** 它是利用函数闭包的特性来实现私有数据和共享方法 ```text var myModule = (function() { var name = 'a1ex' function getName() { console.log(name) } return { getName } })() ``` 此时只能通过getName区获取内部name,实现了name私有化 ## **JS模块化规范演进** ### **CommonJS规范** `CommonJS` 规范中规定每个文件就是一个独立的模块,有自己的作用域,模块的变量、函数、类都是私有的,外部想要调用,必须使用 `module.exports` 主动暴露,而在另一个文件中引用则直接使用 `require(path)` `require` 命令则负责读取并执行一个 JS 文件,并返回该模块的 `exports` 对象 `CommonJS` 规范适用于服务端,也就是只适用于 `NodeJS` ,其实简单来说就是 `Node` 内部提供一个构造函数 `Module`,所有模块都是构造函数 `Module` 的实例 ```text function Module(id, parent) { this.id = id this.exports = {} this.parent = parent // ... } ``` 每个模块内部,都有一个 `module` 实例,该对象就会有下面几个属性: - `module.id` 模块的识别符,通常是带有绝对路径的模块文件名 - `module.filename` 模块的文件名,带有绝对路径 - `module.loaded` 返回一个布尔值,表示模块是否已经完成加载 - `module.parent` 返回一个对象,表示调用该模块的模块 - `module.children` 返回一个数组,表示该模块要用到的其他模块 - `module.exports` 表示模块对外输出的值 `CommonJS` 规范的特点: - 所有代码都运行在模块作用域,不会污染全局作用域 - 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果,要想让模块再次运行,必须清除缓存 - 模块加载的顺序,按照其在代码中出现的顺序 简单来说他会通过字符串拼接的方式去组成一个函数,把文件内容放到函数内部执行 ```text 'function(exports,require,module){ 文件内容 }' ``` 通过`vm.runInThisContext(code)` 会创建一个独立的沙箱环境,执行对以上的编译,运行并返回结果。该方法运行的代码没有权限访问本地作用域,但是可以访问 Global 全局对象。 三个参数分别为: - module 实例的 `exports` 对象 - require 模块导入方法 - require查找规则 require(X) - X是一个node核心模块 比如path、http => 直接返回核心模块并停止查找 - X是一个路径 => 先查找该名字文件,然后依次查找.js,.json,.node文件 - 如果还是没有,则再该名字的文件夹下的index.js文件,然后依次查找.js,.json,.node文件 - X既不是路径也不是模块 => 从当前目录的node_modules文件夹查找 - module 实例本身 当模块内由多个`module.export`时,导出的对象为最后一个`module.export` `exports`就相当与`module.export` ,模块默认导出`module.export` ```text exports = { c: 2 }; // 这样之后exports指向新地址,它不知想module.export ``` > CommonJs 就是模块化的社区标准,而 Nodejs 就是 CommonJs 模块化规范的实现,它对模块的加载是同步的,也就是说,只有引入的模块加载完成,才会执行后面的操作,在 Node 服务端应用当中,模块一般存在本地,加载较快,同步问题不大,在浏览器中就不太合适了,你试想一下,如果一个很大的项目,所有的模块都同步加载,那体验是极差的,所以还需要异步模块化方案,所以 AMD规范 就此诞生。 ### **AMD规范** 专门为浏览器环境设计的,它定义了一套异步加载标准来解决同步的问题 ```text define(id?: String, dependencies?: String[], factory: Function|Object) ``` - `id` 即模块的名字,字符串,可选 - dependencies `指定了所要依赖的模块列表,它是一个数组,也是可选的参数,每个依赖的模块的输出将作为参数一次传入`factory`中。如果没有指定`dependencies`,那么它的默认值是` ["require", "exports", "module"] - `factory` 包裹了模块的具体实现,可为函数或对象,如果是函数,返回值就是模块的输出接口或者值 ```text // 定义依赖 myModule,该模块依赖 JQ 模块 define('myModule', ['jquery'], function($) { // $ 是 jquery 模块的输出 $('body').text('test') }) // 引入依赖 require(['myModule'], function(myModule) { // todo... }) ``` 它就是通过 `define` 方法,将代码定义为模块,通过 `require` 方法,实现代码的模块加载,使用时需要下载和导入,也就是说我们在浏览器中想要使用 `AMD` 规范时先在页面中引入 `require.js` 就可以了。 ### **CMD规范** `CMD` 的出现较为晚一些,它汲取了 `CommonJS` 和 `AMD` 规范的优点,也是专门用于浏览器的异步模块加载。 ```text define(function(require, exports, module) { // 同步引入 var a = require('./a') // 异步引入 require.async('./b', function (b) { }) // 条件引入 if (status) { var c = requie('./c') } // 暴露模块 exports.aaa = 'hahaha' }) ``` ### **CMD和AMD** | **规范** | **推崇** | **代表** | | ------ | ------ | --------- | | AMD | 依赖前置 | requirejs | | CMD | 依赖就近 | seajs | `AMD` 执行过程中会将所有依赖前置执行,也就是在自己的代码逻辑开始前全部执行,而 `CMD` 如果 `require` 引入了但整个逻辑并未使用这个依赖或未执行到逻辑使用它的地方前是不会执行的 ### **ES Module** 2015年6月,`ECMAScript2015` 也就是我们说的 `ES6` 发布了,JS 终于在语言标准的层面上,实现了模块功能,使得在编译时就能确定模块的依赖关系,以及其输入和输出的变量,不像 `CommonJS` 、`AMD` 之类的需要在运行时才能确定(例如 FIS 这样的工具只能预处理依赖关系,本质上还是运行时解析),成为浏览器和服务器通用的模块解决方案。 其模块化功能主要由俩个命令构成:exports和import,export命令由于规定模块的对外接口,import命令用于输入其他模块的功能。ES6还提供了export default的命令。为模块指定默认输出。对应的import语句不需要大括号。这也更接近AMD的引用写法。 ES6 Module不是对象,import命令被JavaScript引擎静态分析,在编译的时候就引入模块代码。而不是在代码运行时加载,所以无法实现条件加载。也就使得静态分析成为可能。 export可以导出的是对象中包含多个属性、方法,export default只能导出一个可以不具名的函数。我们可以输用import引入。同时我们也可以直接使用require使用,原因是webpack启用了server相关。 导入: ```text import { fn } from './xxx' // export导出的方式 import fn from 'xx' // export default方式 ``` ES6模块运行机制与commonjs运行机制不一样。js引擎对脚本静态分析的时候,遇到模块加载指令后会生成一个只读引用。等到脚本真正执行的时候。才会通过引用模块中获取值,在引用到执行的过程中,模块中的值发生变化,导入的这里也会跟着发生变化。ES6模块是动态引入的。并不会缓存值。模块里总是绑定其所在的模块。


## **nextjs13导向** ### **⚠️注意知识点** 1. 服务端组件和客户端组件的概念 2. ·`layout`概念 3. `App router` 4. `Css module` 5. `Image`的作用 1. 限制网站使用的图片资源domain 6. Data fetching 1. fetch option 7. 内置`NotFound` 8. SWR 1. mutate 9. SEO config 1. generateMetadata 10.


# **node概念** ## **高并发** - 并发: 同一时间内,多个进程,通过单个cpu切换运行 - 并行:多个cpu同时处理多个进程 - 高并发: 系统在一定时间内处理大量请求 ## **进程和线程** - 进程: 一个独立的应用程序是操作系统进行资源分配的一个单位 - 特点:动态性 并发性 独立性 结构性 - 线程: 进程调度的最小单位 - 优点: 容易调度 提高到并发性 开销少 - 多线程:操作系统可以同时运行多个任务(程序) - 优点 - 任务交给后台处理 - 提升速度 - 消耗资源少 - ios系统应用 - 缺点 - 线程切换频繁导致系统变慢 - 累计资源比较庞大 - 容易出bug - 考虑线程死锁问题(合理利用线程) ## **node分布式** ### **node 特性** > 单线程,非阻塞式异步I/O,事件驱动 - 单线程: node中js与其他线程无法共享状态 - 不需要关注其他线程 没有线程切换开销 不用担心死锁 - cpu利用率不充分 无法进行大量计算 - 异步I/O - 事件驱动 eventloop ### **node分布式架构** - nginx:负载均衡 - node 集群 处理业务逻辑 - redis: 同步状态 ### **node集群使用** - 启动多线程 ```text const http = require("http"); // node 单线程 // http // .createServer((req, res) => { // console.log("path", req.url); // res.end("dasdas"); // }) // .listen(3392, () => { // console.log("server running at http://localhost:3392/"); // }); // 多线程 // cluster模块解决多核cpu利用率 const cluster = require("cluster"); const numCPU = require("os").cpus().length; // 内核数 if (cluster.isMaster) { // 是主进程 for (let i = 0; i < numCPU; i++) { cluster.fork(); // 创建新进程 } // 崩溃重启新进程 cluster.on("exit", (...rest) => { console.log(rest); cluster.fork(); }); } else { http .createServer((req, res) => { console.log("path", req.url); if (req.url === "/stop") { throw new Error("进程崩溃"); } res.end("dasdas"); }) .listen(3392, () => { console.log("server running at http://localhost:3392/"); }); } ``` - 轮询调用mysql -


## **nuxt个人博客开发历程** > 关于nuxt博客开发历程, 遇到很多问题, 也解决了很多问题, 对于博客细节功能也是因为各个原因阉割了一些,但是也算完成了 nuxt 个人博客的初版。 ### **什么是同构注水(Isomorphic JavaScript)** 同构注水是一种前端开发技术,它使得在浏览器和服务器之间共享同一代码的 JavaScript 应用程序成为可能。在使用同构注水技术时,开发人员可以编写一组通用代码,这些代码可以同时运行在浏览器和服务器上,从而提供更快速的加载速度和更好的用户体验。 具体来说,同构注水技术通过在服务器上预先渲染应用程序的初始 HTML 和 CSS,并将其发送到浏览器,从而实现更快速的页面加载速度和更好的搜索引擎优化。在浏览器中,这些初始数据被用于在客户端重建应用程序的初始状态,从而提供更好的用户体验。 ### **什么是nuxt** > nuxt 是vue的SSR框架,它提供了一种直观和可扩展的方式,可以用Vue.js创建类型安全、性能和生产级的全栈web应用程序和网站。 特点: - 开发更快 - 打包更小 - 支持 `vite` - 支持 `vue3` - 支持自动引入 - 支持文件路由 - 支持布局系统 - 支持多种渲染模式 - 支持 `typescript` - 支持 `composition-api` 1. 在composables目录下的模块的同名导出会自动被引入 2. 在components下的组件也将被自动引入 1. 如果你在components下嵌套文件 ```text components - post -detail.vue ``` 可以通过**<PostDetail />**访问 3. 约定式路由:在pages下的文件将被自动导入路由,包括子路由,嵌套路由,动态路由([id].vue),匹配未命中路由(...slug.vue) 1. 路由组件:NuxtLink、NuxtPage、NuxtChild 2. 定义路由元信息:definePageMeta({foo:"bar"}) 4. 状态共享可以通过composables实现, 也可以通过 **@nuxt/pinia**集成 5. 数据获取本次选用的是useFetch封装 6. ...... ### **博客接口设计** > 针对于同构项目nuxt的后端接口路由编写也采用了约定式的方式, 在server/api下的文件默认被注册为后端匹配路由 ```text -api -index.ts -> 封装useFetch请求函数 -https.ts -> 导出request实例 env区分开发生产环境 -server -api -> 约定式接口 -koa -> 请求后端服务器接口 -https.ts -> 导出request实例 env区分开发生产环境 ``` ### **客户端渲染方式** 1. clientOnly组件, 他可以将内部包裹的代码不在服务端水合,而是在客户端渲染,这也说明该段代码不利于seo优化 ```text <div class="comment" :class="[isCenter ? 'isCenter' : '']"> <ClientOnly> <Comment :path="`/post/${post_id}`" /> </ClientOnly> </div> ``` ### **遇到的一些点** 1. vueuse/motion 报错 1. 版本原因, 通过将 ^2.0.0-beta.12 改成 ^2.0.0-beta.23 2. 由于服务端无法获取到dom,导致有一些自定义指令如v-lazy,v-typing,message会受到影响需要区分对应环境 ```text import AXMessage from "./src/AXMessage.vue"; import { render, VNode, h, RendererNode, RendererElement } from "vue"; enum EMessageType { SUCCESS = "success", DANGER = "danger", INFO = "info", WARNING = "warning", } let seed = 0; let body: any = ""; if (process.client) { body = document.body; } const instance: VNode< RendererNode, RendererElement, { [key: string]: any; } >[] = []; const close = (id: number) => { // 获取该实例的index const idx = instance.findIndex((vm: any) => vm.props.id === id); // 找不到就不关 if (idx === -1) { return; } // 获取该实例 const vm = instance[idx]; const removedHeight = vm.el!.offsetHeight; // 删除实例 instance.splice(idx, 1); for (let i = idx; i < instance.length; i++) { // 直接赋值组件的 top 为减去移除组件后的高度 const pos = parseInt(instance[i].el!.style["top"]) - removedHeight - 16; // topOffset = topOffset - removedHeight - 16 同理 // 将所有dim的高度降低一个dom位 instance[i].component!.props.topOffset = pos; } }; export default function (type: EMessageType, message: string, duration = 3000) { // 据顶部距离 let topOffset = 20; // 创建message容器 const container = document.createElement("div"); // 计算当前元素距离顶部的偏移量 instance.forEach((vm: any) => { // 每有一个就加 当前高度 + 间隔空隙 topOffset += (vm.el.offsetHeight || 0) + 16; }); // 保存id const id = seed; const vm = h(AXMessage, { id, type, message, duration, topOffset, onClose() { // 离开前去除数组内dom close(id); }, onDestroy() { // 动画结束后清除dom container.remove(); }, }); // 渲染组件到container上 render(vm, container); // 添加 container 到 body body.appendChild(container); // 保存组件实例 instance.push(vm); seed++; } export { EMessageType }; ``` 3. setup通过expose暴露方法给外部 ```text defineExpose({ blur: blur }); ``` 4. 国际化 1. `modules: ["@nuxtjs/i18n"],i18n: { vueI18n: { legacy: false, locale: "zh", messages: { zh: { "nav": { "首页": "首页", "归档": "归档", "关于": "关于", "分类": "分类", "标签": "标签", "友链": "友链", "登录": "登录", "搜索": "搜索", "个人主页": "主页", "退出登录": "退出" },...` 5. 全局路由拦截中间件 ```text import message, { EMessageType } from "@/components/message"; import { useCaChe } from "@/hooks/useCache"; const { getCache } = useCaChe("token"); const { getCache: getWalineCache } = useCaChe("WALINE_USER"); export default defineNuxtRouteMiddleware((to, from) => { if (to.path === "/mine") { if (!getCache() || !getWalineCache(true)?.display_name) { message(EMessageType.INFO, "请登录~"); return navigateTo("/login"); } } else { return true; } }); export default defineNuxtRouteMiddleware((to, from) => { if (to.fullPath === "/") { return navigateTo("/home"); } }); ``` 6. seo优化 1. `app: { pageTransition: false, head: { title: "a1ex的博客", meta: [ { name: "description", content: "这是a1ex的一个技术博客", }, { name: "keywords", content: "a1ex, blog, 博客, 前端, vue", }, { name: "viewport", content: "width=device-width,initial-scale=1", }, ], style: [], link: [ // { rel: "stylesheet", href: "https://markdown-it.github.io/index.css" }, { rel: "stylesheet", href: "//at.alicdn.com/t/c/font_3531202_ch99hg9lb5.css", } ], },},` 2. 语义化标签, 跳转采用nuxtlink,不用编程式 3. 动态title ```text useHead({ title: () => `分类${linkName.value ? "-" + linkName.value : ""}`, }); ``` 7. ...


# **react基础** ## **如何使用react** 1. Quick start和api Reference和installation的概念 > 写React工程时, 并不会用到所有的api 只会用到特别常用的api 1. 使用方式 1. script标签引入 2. 脚手架使用 2. react核心包 1. React 1. React.createElement("div",{},"hello react") -> ReactEl 2. ReactDOM 1. ReactDOM.createRoot(el ) // 创建工作节点 2. root.render(ReactEl) // 渲染页面 > 核心功能放在React内, 不包含任何宿主环境代码(document.getElementById) React将对宿主操作能力放到ReactDOM中 唤醒相机,录音放在ReactNative中处理 1. jsx使用 一种表达html结构的语法 1. React.createElement("div",{},"hello react") -> 传入的对象 1. {onClick:()=>{}} => 也就是说,给ReactEl加上属性 [class -> className] 2. 由于多个dom被React.createElement会导致不好书写而且麻烦,则有jsx 3. 使用jsx前提, 导入babel解析(语法降级)jsx文件 1. script挂上type="text/babel",表达是将代码交给babel解析, 默认值为text/javascript 2. 浏览器只会解析script的type为text/javascript和module,而babel解析type为text/babel中的代码,通过转换后加入新的script标签并写入其中 3. 用书写jsx的方式创建ReactEl 1. `// 创建工作节点 const root = ReactDOM.createRoot(document.getElementById("root")); // 传入ReactEl const reactEl = ( <div> 123<h1>456</h1> </div> ); root.render(reactEl);` ## **React-cli脚手架的使用** 1. 什么是脚手架,提供一系列预设,让开发者无需考虑除了自身意外的其他外部问题 2. npx -> npm的附带产物 1. 去执行一段命令, npx会先看第一个参数是否有安装,没有则用npm临时安装,再执行后续命令 ```text npx create-react-app my-app ``` ## **React组件** 1. 组件允许我们将UI划分为独立的,可重用的部分 2. 要求 1. 组件名必须大写(小写为ReactEl,大写为组件) 2. 组件必须返回ReactEl 1. null/ReactEl/组件/可迭代对象 3. 组件状态 1. 组件内部数据发生变化,如何使页面发生变化 1. 渲染 === 函数组件执行 2. 改变了页面依赖的值,要重新渲染函数组件 -> [count, setCount] = useState(0) 1. 组件状态发生变化 2. 组件属性发生变化 2. 组件状态更新是异步的,状态的更新是批量进行的 4. 组件属性 1. 给组件传递属性就相当于给函数传递参数 ## **React事件机制** 1. jsx语法 -> babel -> React.createEl -> 真实dom 2. jsx转化的真实dom身上的事件监听不会绑定在任何dom上,React会保存jsx的身上对应注册的事件,并在root上处理 3. React事件池机制 -> e.target无法被捕捉(共用event提高性能) ## **受控标签和非受控标签** 1. 受控标签:状态完全由他人控制,自身没有任何决定的权力 1. 受控属性 -> value 2. 非标签组件:自由的 1. 只能通过defaultValue设置初始值,监听事件获取内部值 ## **hooks使用之useState** > hooks:允许开发者在不写类组件下的情况下去生成state以及一些其他曾经是类组件专属的东西 useState是一个react的hook, 让我们可以在组件内定义状态 1. useState(initState) -> initState如果是函数的话, 必须是纯函数, 参数不会解析 2. react严格模式 3. immutable state 不可变状态, 每一次状态传递都是不可变的 ## **hooks使用之useEffect** > 用来处理副作用 http请求/dom操作 1. useEffect(setup, dependencies?) 1. setup初始化 2. dependencies? 依赖数组 2. 执行时机: 1. 当我们使用useEffect去注册setup,React会在该组件每次挂载完毕到页面时会执行setup函数,异步执行 2. 当依赖项发生变更时, 会执行对应setup 3. 应用场景 1. http请求 4. 清除副作用 1. 组件被卸载时内部的监听该销毁 2. setup函数有一个返回值,为清理函数,会在组件销毁时执行(销毁监听事件,定时器) ```text useEffect(() => { const defaultValue = 10; setCount(defaultValue); const handleKeyDown = (e) => { console.log(e.code); }; window.addEventListener("keydown", handleKeyDown); return () => { window.removeEventListener("keydown", handleKeyDown); }; }, [count]); ``` ## **hooks自定义实现之useRequestLoading()** ```text export default function useRequestLoading(PromiseFn) { const [loading, setLoading] = useState(false); const execRequset = async () => { setLoading(true); await PromiseFn(); setLoading(false); }; return { loading, execRequset, }; } ``` ```text // 原写法 const [useList, setUserList] = useState([]); const [loading, setLoading] = useState(false); const fetchUserList = async () => { setLoading(true); const useListResp = await getUserList(); setUserList(useListResp); setLoading(false); requestIdleCallback(() => {}); }; useEffect(() => { fetchUserList(); }, []); return ( <div> <h1>用户列表</h1> {loading ? ( <div>loading...</div> ) : ( useList.map((user) => <div key={user.id}>{user.name}</div>) )} </div> ); // hook写法 const [useList, setUserList] = useState([]); const { loading, execRequset } = useRequestLoading(); useEffect(() => { execRequset(async () => { const result = await getUserList(); setUserList(result); }); }, []); return ( <div> <h1>用户列表</h1> {loading ? ( <div>loading...</div> ) : ( useList.map((user) => <div key={user.id}>{user.name}</div>) )} </div> ); ``` ## **hooks自定义实现之useForceUpdate** ```text export default function useForceUpdate() { const [_, setCompState] = useState({}); function forceUpdate() { setCompState({}); } return forceUpdate; } ``` ## **hooks自定义实现之useScrollWatcher** ```text export default function useMouseWatcher(moveCallback) { useEffect(() => { const removeEvent = () => { window.removeEventListener("mousemove", moveCallback); }; window.addEventListener("mousemove", moveCallback); return removeEvent; }, []); } // 实现 const [pos, setPos] = useState({ x: 0, y: 0 }); const removeEvent = useMouseWatcher((e) => { console.log("listender", e.pagx, e.pageY); setPos({ x: e.pageX, y: e.pageY }); }); return ( <div> <h2>滚动监听</h2> <button onClick={removeEvent}>移除监听</button> <div> pos:{pos.x}-{pos.y} </div> </div> ); ``` ## **内置hooks使用** > 用来长期稳定的维护某一个函数引用, 1. useCallback(initFn,depenece) 1. 只有当依赖发生变化时,initFn的应引用才会改变 ```text const addCount2 = useCallback(() => { console.log("执行了addCount2函数", count2); setCount2((pre) => pre + 1); }, [count]); // 当count发生变化时,addCount2的引用才刷新 ``` 2. dependness 依赖项发生变化以后,函数内引用会刷新 2. useMemo(initFn) -> 类似vue的计算属性 1. 第一个参数是一个函数,会被React直接执行并缓存, 2. 第二个为依赖 当以来发生变化时吗, 重新缓存 3. useRef -> 类似vue的 dom ref > <></> 语法糖 -> React.Fragment 1. 构建一个状态,这个状态脱离react控制,他的变化不会造成组件重新渲染,同时状态不会再次初始化 ```text const [timer, setTimer] = useState(60); let timerId = useRef(null); console.log("刷新了页面"); const start = useCallback(() => { console.log("点击了start"); timerId.current = setInterval(() => { setTimer((pre) => pre - 1); }, 1000); }, []); const stop = useCallback(() => { if (timerId.current) clearInterval(timerId.current); }, [timerId]); ``` 2. 挂真实dom,自动在useEffect内获取当前dom ```text import React, { useRef } from "react"; export default function TestInput() { const inputElRef = useRef(); const focus = () => { inputElRef.current.focus(); }; return ( <> <input ref={inputElRef} type="text" /> <button onClick={focus}>focus input!</button> <hr /> </> ); } ``` 3. 可读可写的, 4. ForwordRef() -< 给函数组件扩展一个ref属性作为第二参数 5. useImperativeHandle 当以来发生变化时, 才会给ref.current刷新值 1. 第一个参数ref 2. 第二个参数fn -< 返回值为ref.current 3. 第三个参数为依赖项 6. useContent > 解决props以外的数据共享,将多层组件数据共享简化, 用来做全局数据共享的 1. 通过ThemeContent.Provider传递给内容上下文 1. 通过ThemeContent.Provide传递value 2. 后代组件通过useContent(ThemeContent), 获取value 7. useLayoutEffect() 1. useEffect方法当首次渲染工作完成并将真实dom生成页面以后, 并肩对应回调函数推入**异步队列**等待执行 2. useLayoutEffect方法当首次渲染工作完成并将真实dom生成页面以后, 并肩对应回调函数推入**同步队列**等待执行 ## **浏览器渲染帧探究** 1. 浏览器一帧需要做的事 1. 处理上一帧的用户交互事件 2. 调用requestAnimationFrame 3. 执行重排重绘 4. 调用requestIdleCallback 2. 掉帧 3. react调度机制 ## **React渲染原理之concurrency(并发性, 可中断渲染)** 1. React渲染一个组件到页面中要做哪些事情 1. 拿到React.createElement所返回的ReactEl节点 -> 是个对象 2. 通过render进行渲染 1. 如果是react元素节点的话就生成dom插入 appendChild 2. 如果是组件这会在渲染保存对应的hooks以及触发对应的hooks 3. 更新 -> diff 2. react将渲染流程分为了两个阶段 1. render:负责将需要渲染的组件的内部逻辑以及react内部逻辑并执行得到一份`fiber`清单[记录了要展示的真是dom树是什么样子,要增加哪些dom] 2. commit:负责将`fiber`清单转换成真实dom
