包管理器

Pnpm是怎么解决幽灵依赖的

hard link + symbolic link

如何确定一个项目用的包管理器版本:enigne字段
1
2
3
4
"engines": {
"node": ">=14.0.0",
"npm": ">=7.0.0" // 项目要求的 npm 最低版本
}
如何避免业务项目被发到npm上
在 package.json 中添加 private 字段

在项目的 package.json 文件中添加 private: true 字段,这是最直接的方式。当 private 为 true 时,npm 会拒绝发布该项目。

使用 .npmignore 或 .gitignore 文件

将项目中的所有文件添加到忽略列表,确保没有文件被发布。.npmignore 会覆盖 .gitignore 的规则。

设置预发布钩子

在 package.json 中添加 prepublishOnly 脚本,使其在发布前失败:

使用 .npmrc 文件锁定发布源

在项目根目录创建 .npmrc 文件,设置为私有源或无效源:

阅读全文 »

React 整体渲染流程

为什么要有React Fiber?

见:React Fiber的意义

jsx、ReactElement、虚拟DOM 、Fiber的关系

树里的一个 React 元素(ReactElement)对应一个 Fiber,一个 Fiber 也对应树里的一个元素。
见:JSX、ReactElement、FiberNode、DomElement

常见的生命周期函数都是在哪些渲染阶段被调用

见:常见的生命周期函数都是在哪些渲染阶段被调用

diff算法的过程

单节点和多节点Diff:Diff算法

fiberRootNode与rootFiber的结构

fiberRootNode 是“整棵应用的根管理对象(Root 容器)”,而 rootFiber 是“Fiber 树里的第一个 Fiber 节点(HostRootFiber)”。

见:FiberRootNode存储的信息Fiber Node存储的信息

setState后React都做了什么

见:从一次 useState 的 setState 开始后发生了什么

React Scheduler为什么一定要把任务包在宏任务里执行?

思考能否使用如下方案:直接同步执行,限制好执行时间(5ms)
答案是,不行。

Scheduler本质上做的不仅仅是分片,还要把控制权交还给浏览器

  • 纯同步执行 + 自己看时间,理论上可以“分段”,但本质上你还是在当前调用栈里,浏览器拿不到控制权,不能及时处理输入/渲染。
  • React 需要的是协作式调度:做一小段 -> 让出线程 -> 下轮再继续。这就必须依赖异步边界(MessageChannel/setTimeout 这种 task 机制)。

React Scheduler 为什么不用requestIdleCallback实现

  1. 兼容性差 Safari 并不支持 https://caniuse.com
  2. 控制精细度 React 要根据组件优先级、更新的紧急程度等信息,更精确地安排渲染的工作
  3. 执行时机requestIdleCallback(callback) 回调函数的执行间隔是 50ms(W3C规定),也就是 20FPS,1秒内执行20次,间隔较长。
  4. 差异性 每个浏览器实现该API的方式不同,导致执行时机有差异有的快有的慢
requestIdleCallback的替代方案?

MessageChannel

选择 MessageChannel 的原因,是首先异步得是个宏任务,因为宏任务中会在下次事件循环中执行,不会阻塞当前页面的更新。MessageChannel 是一个宏任务。

没选常见的 setTimeout,是因为MessageChannel较快执行,在 0~1ms 内触发,像 setTimeout 即便设置 timeout 为 0 还是需要4~5ms。相同时间下,MessageChannel 能够完成更多的任务。

并发渲染

支持 new concurrent renderer(并发模式的渲染)

  • 并发模式不是一个功能,而是一个底层设计。
  • 它可以帮助应用保持响应,根据用户的设备性能和网速进行调整。
  • 通过渲染可中断来修复阻塞渲染机制。在 concurrent 模式中,React 可以同时更新多个状态。
  • 区别就是使同步不可中断更新变成了异步可中断更新。
  • useDeferredValue 和 startTransition 用来标记一次非紧急更新。

starTransition:用于标记非紧急的更新,用 starTransition 包裹起来就是告诉 React,这部分代码渲染的优先级不高,可以优先处理其它更重要的渲染。

useTransition:除了能提供 startTransition 以外,还能提供一个变量来跟踪当前渲染的执行状态。

React 的并发机制允许 React 在渲染过程中根据任务的优先级进行调度和中断,从而确保高优先级的更新能够及时渲染,而不会被低优先级的任务阻塞。

并发机制的工作原理

时间分片(Time Slicing): React 将渲染任务拆分为多个小片段,每个片段在主线程空闲时执行。这使得浏览器可以在渲染过程中处理用户输入和其他高优先级任务,避免长时间的渲染阻塞用户交互。

优先级调度(Priority Scheduling): React 为不同的更新分配不同的优先级。高优先级的更新(如用户输入)会被优先处理,而低优先级的更新(如数据预加载)可以在空闲时处理。

可中断渲染(Interruptible Rendering): 在并发模式下,React 可以中断当前的渲染任务,处理更高优先级的任务,然后再恢复之前的渲染。这确保了应用在长时间渲染过程中仍能保持响应性。

并发机制的优势
  • 提升响应性: 通过优先处理高优先级任务,React 能够更快地响应用户输入,提升用户体验。
  • 优化性能: 将渲染任务拆分为小片段,避免长时间的渲染阻塞,提升应用的整体性能。
  • 更好的资源利用: 在主线程空闲时处理低优先级任务,充分利用系统资源。
如何启用并发模式

要在 React 应用中启用并发模式,需要使用 createRoot API:

1
2
3
4
5
6
7
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'

const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<App />)

在并发模式下,React 会自动根据任务的优先级进行调度和渲染。

useTransition 本质:把一部分更新标记到“过渡 lane(低优先级车道)”,让紧急更新(输入、点击)先渲染,过渡更新可以被打断、延后。

useDeferredValue 本质:把某个值的更新“映射”到过渡 lane,上游状态先变,依赖这个值的重渲染滞后一点,起到“值级别的 useTransition”效果。

什么是lane模型

见:Lane模型

状态更新与批量更新机制

同一个上下文中的setState会被合并

React 17以及之前:

  • onClick 里的多个 setState 会被批处理
  • setTimeout / Promise.then 里的更新通常不会自动批处理(会各自触发)

React 18(createRoot)

引入了更广泛的自动批处理:

  • React 事件里批处理
  • setTimeout、Promise、原生事件回调里也会自动批处理(多数场景)

setState 是同步还是异步的

useState怎么实现的第一次拿初始状态,后续拿之前状态

判断挂载还是Update,如果是Update阶段根据BaseState和Update对象计算出最新的state

useState等hooks为什么不能写在函数组件外面

因为hook要挂到当前Fiber上

React18 的自动批处理与 flushSync

  • 在 react17 中,只有 React 事件会进行批处理,原生 js 事件、promise,setTimeout、setInterval 不会。
  • react18 中,将所有事件都进行批处理,即多次 setState 会被合并为 1 次执行,提高了性能。

flushSync:退出批量更新,强制立即刷新视图。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import React,{useState} from "react"
import {flushSync} from "react-dom"

const App=()=>{
const [count,setCount]=useState(0)
const [count2,setCount2]=useState(0)

return (
<div className="App">
<button onClick=(()=>{
// 第一次更新
flushSync(()=>{
setCount(count=>count+1)
})
// 第二次更新
flushSync(()=>{
setCount2(count2=>count2+1)
})
})>点击</button>
<span>count:{count}</span>
<span>count2:{count2}</span>
</div>
)
}
export default App

其他 React18/19 新特性

  • 放弃对 IE 浏览器的支持:react18/19 引入的新特性全部基于现代浏览器,如需支持需要退回到 react17 版本。
  • Suspense 不再需要 fallback 捕获。
  • 支持 useId:在服务器和客户端生成相同的唯一一个 id,避免 hydrating 的不兼容。
  • useSyncExternalStore:用于解决外部数据撕裂问题。
  • useInsertionEffect:这个 hooks 只建议在 css in js 库中使用,这个 hooks 执行时机在 DOM 生成之后,useLayoutEffect 执行之前,无法访问 DOM 节点引用,一般用于提前注入脚本。

React Hooks

见:Hooks

封装自定义 Hooks

实现 useTimeout hook

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// callback 回调函数, delay 延迟时间
function useTimeout(callback, delay) {
const memorizeCallback = useRef();

useEffect(() => {
memorizeCallback.current = callback;
}, [callback]);

useEffect(() => {
if (delay !== null) {
const timer = setTimeout(() => {
memorizeCallback.current();
}, delay);
return () => {
clearTimeout(timer);
};
}
}, [delay]);
};

实现一个 dom 可见性的 hook

实现的一个关键点是 Ref 的更新在 useEffect 执行前

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import { useEffect, useState, useRef } from "react";
const useInView = (
options = {
root: null,
rootMargin: "0px 0px",
threshold: 1,
},
triggerOnce = false, // 是否只触发一次
) => {
const [inView, setInView] = useState(false);
const targetRef = useRef(null);

useEffect(() => {
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
setInView(true);
if (triggerOnce) {
// 触发一次后结束监听
observer.unobserve(entry.target);
}
} else {
setInView(false);
}
});
}, options);

if (targetRef.current) {
// 开始监听
observer.observe(targetRef.current);
}

return () => {
if (targetRef.current) {
// 组件卸载时结束监听
observer.unobserve(targetRef.current);
}
};
}, [options, triggerOnce]);

return [targetRef, inView];
};

export default useInView;

状态管理相关

React Hooks 如何实现类似 Redux 的状态管理

useContext + useReducer。

Redux 性能问题

Redux vs Mobx

Redux RTX

ImmerJS

React Router 路由模式与实现原理

几种模式

<BrowserRouter> /<HashRouter>

  • hash 模式(<HashRouter>):在 url 后面加上 #,如 http://127.0.0.1:5500/home/#/page1
  • history 模式(<BrowserRouter>):允许操作浏览器的曾经在标签页或者框架里访问的会话历史记录

hash 模式

URL 示例:https://example.com/#/about、https://example.com/#/users/123

history模式

URL 示例:https://example.com/about、https://example.com/users/123

history模式最重要的是部署需要配合Nginx,所有路由都返回 index.html

事件循环

见:浏览器架构和事件循环

this相关

见:this指向

原型和原型链

见:JS原型和原型链

JS二进制相关

见:JS二进制:File、Blob、FileReader、ArrayBuffer、Base64

执行上下文、作用域链

见:JavaScript执行上下文

JS相关API

见:JS相关API

其他

let、const的区别

  • 块级作用域
  • 重复声明变量会报错
  • 不能在声明前访问
阅读全文 »

Webpack 是什么

webpack 是一种前端资源构建工具,一个静态模块打包器(module bundler)。
在webpack 看来, 前端的所有资源文件(js/json/css/img/less/…)都会作为模块处理。
它将根据模块的依赖关系进行静态分析,打包生成对应的静态资源(bundle)。

Webpack 五个核心概念

Entry

入口(Entry):指示 webpack 以哪个文件为入口起点开始打包,分析构建内部依赖图。

Output

输出(Output):指示 webpack 打包后的资源 bundles 输出到哪里去,以及如何命名。

Loader

Loader:让 webpack 能够去处理那些非 JS 的文件,比如样式文件、图片文件(webpack 自身只理解
JS)

Plugins

插件(Plugins):可以用于执行范围更广的任务。插件的范围包括,从打包优化和压缩,
一直到重新定义环境中的变量等。

阅读全文 »

React 的理念

设计理念:UI = f(state)

React 把 UI 看成「由状态推导出来的纯函数」:每次状态变化时,重新计算出一份新的 UI 描述(虚拟 DOM)→ React 帮你高效算出差异 → 最后用最小代价更新到真实界面。也就是:UI = f(state)

这个理念可以拆成三点心智模型:

  • 响应式编程:只要状态(state)变了,React 会负责「重新算 UI + 更新到界面」,不需要手动操作 DOM。
  • 组件化:页面拆成一个个组件,每个组件内部有自己的状态和逻辑,对外只暴露 props,方便复用和维护。
  • 单向数据流:数据从父组件单向流向子组件;子组件要影响父组件,只能通过回调把「意图」往上抛,这让数据流向更可预测、方便调试。
阅读全文 »

process.env

processNode.js 中的 一个全局变量,提供来有关当前 Node.js 进程的信息并对其进行控制。而process 中的 env 则是返回包含用户环境的对象。可以通过 process.env 拿到当前项目运行环境的信息。

设置环境变量

通过cli的方式进行设置
1
2
// index.js中
console.log(process.env.PROT)

Windows (cmd.exe)

1
set PROT=10086 && node index.js

Linux, macOS (Bash)

1
PROT=10086 node index.js
阅读全文 »

代码自动格式化工具:prettier

配置过程:https://prettier.io/docs/en/precommit.html

yarn add --dev --exact prettier

新建.prettierrc.json,.prettierignore文件

设置git commit预提交钩子

npx mrm@2 lint-staged

规范git commit工具:commitlint

配置过程:https://github.com/conventional-changelog/commitlint

yarn add --save-dev @commitlint/config-conventional @commitlint/cli

新建 commitlint.config.js文件,引入规则module.exports = {extends: [‘@commitlint/config-conventional’]}

阅读全文 »

背景:

  1. 希望利用 css 变量实现 dark 和 light 模式的切换
  2. 原有的工程都是 less 形式定义的 css,并且还有 less 的函数,比如 fade 等,不想手动改 less 的函数,希望该插件能支持解析 less 函数
  3. 需要支持局部不切换模式,比如某个区域是固定的 light 模式

步骤

第一步:less 变量转换成 css 变量

这一步比较简单,less 已经提供了字段用于转换,只需要添加一个配置项就可以,就是globalVars属性。

可以查看example 代码参考

1
2
3
4
5
6
{
loader: 'less-loader',
options: {
globalVars: LessGlobalCSSVars,
}
}

LessGlobalCSSVars大概长这个样子

1
2
3
4
5
{
"bg-body": "var(--bg-body)",
"static-white": "var(--static-white)",
...
}
阅读全文 »
0%