Bilibili 专栏文章关键内容提取与 PDF 生成工作流
概述
本工作流用于从 Bilibili 专栏文章中提取关键内容并生成高质量 PDF,去除了页面中的导航、侧边栏、标签页和底部区域等干扰元素,专注于文章正文内容。
整理几道常见的 hard 题目。
[TOC]
将我们所写的代码转化为机器能识别的一种树形结构。本身有一堆节点(Node)构成。每个节点都代表一种结构,不同结构用类型(Type)区分。
JS 为了统一 ECMAScript 标准,社区中衍生出了 ESTree Spec 标准。
| 类型 | 说明 |
|---|---|
| File | 文件(顶层节点包含 Program) |
| Program | 程序(包含 body) |
| Directive | 指令(如 “use strict”) |
| Comment | 注释 |
| Statement | 语句 |
| Literal | 字面量(基本数据类型、复杂数据类型等值类型) |
| Identifier | 标识符(变量名、属性名、函数名、参数名等) |
| Declaration | 声明(变量声明、函数声明、Import、Export 声明等) |
| Specifier | 关键字(ImportSpecifier、ImportDefaultSpecifier、ImportNamespaceSpecifier、ExportSpecifier 等) |
| Expression | 表达式 |
| 属性 | 说明 |
|---|---|
| type | 节点类型 |
| start | 记录该节点代码字符串起始下标 |
| end | 记录该节点代码字符串结束下标 |
| loc | 内含 line、column 属性,分别记录开始结束的行列号 |
| leadingComments | 开始的注释 |
| innerComments | 中间的注释 |
| trailingComments | 结尾的注释 |
| extra | 额外信息 |
有两个工具:
如下的代码:
1 | function test(args) { |
转化为 AST:
1 | { |
变量声明就是 VariableDeclaration,包含 declarations 数组和 kind,之所以是数组是因为有 const a = 1, b = 2; 这种写法。
数组中的每个元素是 VariableDeclarator,包含 id 和 init。
id 即 Identifier 是变量名。
init 即初始值,包括 type 和 value,此处 type 为 Literal,value 为 1。
kind 即声明类型,此处是 const。
Babel 解析代码后生成的 AST 是以 ESTree 作为基础,并略作修改。
| 工具 | 说明 |
|---|---|
| @babel/core | Babel 转码的核心包,包括了整个 babel 工作流(集成@babel/types) |
| @babel/parser | 解析器,将代码解析为 AST |
| @babel/traverse | 遍历/修改 AST 的工具 |
| @babel/generator | 生成器,将 AST 还原成代码 |
| @babel/types | 包含手动构建 AST 和检查 AST 节点类型的方法 |
| @babel/template | 可将字符串代码片段转化为 AST 节点 |
相当于是指令,告诉 Babel 如何转换代码。
主要分为两类:
@babel/parser,负责将代码转化为 AST。官方插件以 babel-plugin-syntax 开头。@babel/core,负责转换 AST 的形态。绝大情况下我们都是在编写转换插件。插件的本质是编写各种 visitor 去访问 AST 的节点,并进行 parse,将节点 transform 最终 generate 出代码。
如 ES6 => ES5 let 转 var:
1 | export default function (babel) { |
变量名长度限制:
1 | module.exports.rules = { |
1 | <Button>{props.name}</Button> |
转化为
1 | types.jsxElement( |
1 | <> |
转化为
1 | types.jsxFragment(types.jsxOpeningFragment(), types.jsxClosingFragment(), [ |
[TOC]
解决问题:
依赖和源码。304 NOT MODIFIED 进行协商缓存,对依赖模块通过Cache-Control: max-age=31536000, imuutable进行强缓存。支持原生 ESM 语法的 script 标签、原生 ESM 动态导入和 import.meta 的浏览器。
传统浏览器可以通过 @vitejs/plugin-legacy 支持。
[TOC]
Rollup 是一个 JS 模块打包工具,用 ES6 的格式来打包。
对于浏览器,Rollup 可以打包成 ES6 模块。
1 | iife 会将代码封装起来,以便给 script 标签使用 |
对于 Node.js,Rollup 可以打包成 CommonJS 格式。
1 | rollup main.js --file bundle.js --format cjs |
对于浏览器和 Node.js,Rollup 可以打包成 UMD 格式。
1 | umd 格式需要一个包名 |
在根目录中的 rollup.config.mjs。
1 | // rollup.config.mjs |
package.json 在 script 中添加"build": "rollup -c"。在 module 字段指定 ES6 模块入口,main 字段指定 CommonJS 或 UMD 模块入口。
找到外部模块。
将 CommonJS 转化为 ES6(ES2015)。
把 lodash 作为外部导入的例子:
1 | import resolve from "@rollup/plugin-node-resolve"; |
@rollup/browser 添加到 optimizeDeps.exclude 中。使用 @rollup/plugin-babel 和 @rollup/plugin-node-resolve 插件。添加到 rollup.config.mjs:
1 | import resolve from "@rollup/plugin-node-resolve"; |
同时创建一个 src/.babelrc.json 文件:
1 | { |
还需要安装 @babel/core 和 @babel/preset-env。
导入:
1 | // 具名导入 |
导出:
1 | // 具名导出 |
[TOC]
可以将某些状态更新标记为不紧急的。
相比于 setTimeout
startTransition 是立刻执行的,setTimeout 是延迟执行的。startTransition 可被中断的,setTimeout 是阻塞的。1 | const [isPending, startTransition] = useTransition(); |
实现类似于防抖节流的延迟渲染,是可被中断的,没有固定的时间延迟。
1 | function Father() { |
useId 用于生成唯一的 id。
1 | const id = useId(); |
用于同步外部状态,常用于集成外部 React 库。
1 | const value = useSyncExternalStore(externalStore.subscribe, externalStore.read); |
类似于 useEffect,在 useLayoutEffect 获取布局前执行。一般用于动态插入 style 标签或 SVG 的 defs。
缺点是不能获取到 refs,也不能触发 React 更新。
1 | // 在你的 CSS-in-JS 库中 |
注意:useInsertionEffect 在 SSR 阶段是不会执行的。
有两个重要特性:
适用场景:计算量大的游戏应用、地图应用和机器学习应用。
Fiber 简介:
关系:
startTransition 和 useTransition 实现过渡更新模式。useDeferredValue 实现延迟渲染。Suspense。RSC(React Server Components)。依赖于错误边界(ErrorBoundary)组件,可以捕获子组件抛出的任何错误。
此前 Suspense 主要用来配合 React.lazy 实现代码拆分和懒加载。
React 18 中的 Suspense 新增了对 SSR 的支持。属于特殊的错误边界组件。在子组件抛出 Promise 时会渲染 fallback UI,直到 Promise resolved 之后重新渲染。
现在,即时 Suspense 的值为 null 或 undefined,也不会跳过。
对比 SSR 和 SSG:
RSC 的优点:
缺点:
可以暂停 React 数中的呈现,在后台获取内容并将其分块流式传输到客户端时显示一个正在加载的组件作为占位符,一旦准备就绪就会无缝切换。
都支持流式 Suspense。
用于 Node 的流式传输。
用于 Deno 或 Cloudflare Workers。
两者都接收一个新选项:onRecoverableError。
createRoot 用于创建根节点。替代 reactDOM.render。
1 | const root = createRoot(document.getElementById("root")); |
hydrateRoot 用于服务器渲染。替代 reactDOM.hydrate。
在 React 18 之前,异步更新比如同一个 Pormise 里的多个 setState 不会自动合并,需要手动调用 unstable_batchedUpdates 合并更新。
从 React 18 开始 createRoot,React 会自动合并异步更新。
可以用 React.flushSync() 退出批处理。
useState 返回一个数组,第一个元素是状态值,第二个元素是更新状态的函数,在这个回调函数里可以获取到更新后的 state。
在 React 18 开始,setState 在异步函数里不会立刻更新状态,React 会将多个 setState 合并成一个更新。
1 | const [count, setCount] = useState(0); |
useEffect 用于执行副作用操作,比如数据请求、DOM 操作等。
第二个参数是依赖数组,只有依赖数组中的值发生变化时,才会执行副作用操作。
1 | useEffect(() => { |
useContext 用于在函数组件中获取上下文。
1 | const value = useContext(MyContext); |
useReducer 用于复杂的状态逻辑。
第一个参数是 reducer 函数,第二个参数是初始状态,第三个参数是初始化函数。
1 | function reducer(state, action) { |
useCallback 用于缓存函数。
1 | const memoizedCallback = useCallback(() => { |
useMemo 用于缓存计算结果。
1 | const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]); |
useRef 用于获取 DOM 元素或缓存变量。
1 | const inputEl = useRef(null); |
useImperativeHandle 用于自定义暴露给父组件的实例值。
1 | forwardRef((props, ref) => { |
可以将子组件暴露给父组件,让父组件可以直接调用子组件的方法。
useLayoutEffect 与 useEffect 类似,但是会在浏览器 layout 之后执行。
1 | useLayoutEffect(() => { |
适用于要测量计算 DOM 尺寸或者位置的情况,避免出现闪烁或布局抖动。
useDebugValue 用于在 React 开发者工具中显示自定义 hook 的标签。
1 | function useFriendStatus(friendID) { |
在如 useEffect、useCallback、useMemo 等 hooks 中,如果使用外部变量,React 只会记住该变量的初始值,除非依赖项更新否则而不会更新。
在如 useEffect、useCallback、useMemo 等 hooks 中,如果依赖项是一个对象,那么每次更新时都会生成一个新的对象,导致依赖项发生变化,从而导致副作用操作执行多次。
1 | const [state, setState] = useState({ count: 0 }); |
建议的方式:
不推荐的方式:
useImperativeHandle 暴露子组件给父组件。不推荐使用,因为会破坏封装性。componentWillMount:组件挂载前调用。componentDidMount:组件挂载后调用。componentWillReceiveProps:组件接收到新的 props 时调用。shouldComponentUpdate:组件接收到新的 props 或者 state 时调用,用于判断是否需要重新渲染。componentWillUpdate:组件更新前调用。componentDidUpdate:组件更新后调用。componentWillUnmount:组件卸载前调用。componentDidCatch:组件发生错误时调用。componentWillReceiveProps:组件接收到新的 props 时调用。constructor:构造函数。static getDerivedStateFromProps:静态方法,用于派生状态。render:渲染函数。componentDidMount:组件挂载后调用。shouldComponentUpdate:组件接收到新的 props 或者 state 时调用,用于判断是否需要重新渲染。getSnapshotBeforeUpdate:在更新 DOM 之前获取快照。componentDidUpdate:组件更新后调用。componentWillUnmount:组件卸载前调用。componentDidCatch:组件发生错误时调用。componentDidCatch:组件接收到新的 props 时调用。React.memo 缓存组件。useMemo 缓存计算结果。useCallback 缓存函数。useRef 缓存 DOM 元素或变量。useLayoutEffect 替代 useEffect。React.lazy 和 React.Suspense 实现组件懒加载。React.PureComponent 代替 Component。shouldComponentUpdate 避免不必要的渲染。React.createContext 代替 props 传递数据。WebView 是一种嵌入到客户端的浏览器控件,可以加载网页,展示 HTML 内容。
JSBridge 是客户端与 H5 交互的桥梁,通过 JSBridge 可以实现客户端与 H5 的双向通信。
H5:
Native:
类似 JSONP 的思路,在 url 里面拼接 callback 参数。
离线包是客户端将 web 资源本地缓存,请求 web 页面时拦截 webview 的请求,并优先使用本地缓存的静态资源进行响应,以此来优化页面的加载性能。
appJson.json 文件,用于存放离线包的信息,包括离线包需要过滤的文件、客户端预加载的接口列表等。document.DOMContentLoaded 事件触发的时间。此时 defer 的 js 已执行完毕。但是图片等资源还未加载完成。window.onload 事件触发的时间,同样也是 document.readyState 变为 complete 的时间。PerformanceNavigationTiming。替代 Performance.timing。兼容性还不太行。
整理几道并查集 + DFS 的题目。