0%

Redux

官方推荐使用封装了 Redux 核心的 @reduxjs/toolkit(RTK) 包, 包含了构建 Redux 应用所必须的 API 方法和常用依赖, 简化了大部分 Redux 任务, 阻止了常见错误, 并让编写 Redux 应用程序变得更容易

@reduxjs/toolkit/query

独立可选的入口, 允许定义端点(REST, GraphQL或任何异步函数)并生成 reducer 和中间件来完整管理数据获取, 加载状态更新和结果缓存, 还可以自动生成 React Hooks, 可用于组件获取数据

@reduxjs/toolkit(RTK)

  • 通过单一清晰的函数调用简化 store 设置, 同时保留完全配置 store 选项的能力
  • 消除意外的 mutations
  • 消除手写任何 actionCreator 或 actionType 的需求
  • 消除编写容易出错的手动不可变更新逻辑的需求, createSlice 使用 Immer 库来编写 reducer, 可以直接修改 state 状态而不需要使用解构语法
  • 允许将相关的代码放在一个文件中, 而不是分布在多个独立文件中
  • 提供优秀的 TypeScript 支持, 其 API 被设计成很好的安全性, 同时减少代码中需要定义的类型数量
  • RTK Query 可以消除编写任何 thunk, reducer, actionCreator 或者副作用狗子来管理数据获取和跟踪加载状态的需求

configureStore

特点

  • slice reducers 自动传递给 combineReducers
  • 自动添加了 redux-thunk 中间件
  • 添加了 Devtools 中间件来捕获更多意外的变更
  • 自动设置了 Redux Devtools Extension
  • 中间件和 Devtools 增强器被组合在一起添加到了 store 中
阅读全文 »

React Router

React Router v7 示例

工作模式

  • Declareative 声明式, 使用路由组件匹配 url, 导航路由
  • Data 在 React rendering 外配置路由, 支持 data APIs
  • Framework 包含 Data mode 模式的全栈框架
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
// npx create-react-router@latest my-react-router-app
// Declareative
createRoot(root).render(
<BrowserRouter>
<Routes>
<Route path="/" element={ <Root /> } />
</Routes>
</BrowserRouter>
);

// Data
const router = createBrowserRouter([
{path: '/', Component: Root, loader: rootLoader}
]);
createRoot(root).render(<RouterProvider router={router}/>);

// Framework
// routes.ts
export default [
index('./home.tsx'), // 默认渲染组件
route('about', './about.tsx'),
route('dashboard', './dashboard.tsx', [ // 嵌套路由, 添加路由段
index('./home.tsx'),
route('settings', './setting.tsx')
]),
layout('./auth/layout.tsx', [ // 使用 布局, 但不会添加路由段
route('login', './auth/login.tsx'),
route('register', './auth/register.tsx')
]),
...prefix("projects", [ // 路由前缀
index("./projects/home.tsx"),
layout("./projects/project-layout.tsx", [
route(":pid", "./projects/project.tsx"),
route(":pid/edit", "./projects/edit-project.tsx"),
]),
])
]
  • 自动将 loaderData, actionData, params, matches 作为 props 传递给组件
1
2
3
4
5
6
7
8
9
10
11
12
export async function loader(){} // 路由组件渲染之前执行
export async function clientLoader({params}){} // 客户端获取数据
export async function action(){}
export async function clientAction(){}
export function ErrorBoundary(){} // 路由发生错误时渲染
export function headers(){} // 定义响应头
export function links(){} // 定义页面 head 中的 <link> 元素信息
export function meta(){} // 定义页面 head 中的 meta 信息
// 允许在 useMatches 中向 路由 匹配 中添加任意内容, 像面包屑
export const handle = {}
// 是否允许弹出路由重新验证不影响其数据的操作, 默认所有路由在操作后都会重新验证
export function shouldRevalidate(){}

以下部分为 React Router v6

阅读全文 »

react-transition-group

Transition

包含 4 个状态

  • entering
  • entered
  • exiting
  • exited

props

  • nodeRef 执行动画的关联 DOM 节点, 早期的版本使用 findDOMNode(deprected) 查找 DOM 节点, 报错的替换方案
  • in 切换 enter 和 exit 的状态
  • appear 控制组件首次挂载时的默认行为
  • enter 控制进入的动画
  • exit 控制退出的动画
  • timeout 动画的时长
  • addEvenetListener 添加自定义的事件
  • onEnter
  • onEntering
  • onEntered
  • onExit
  • onExiting
  • onExited
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import {useState, useRef} from 'react';
import {Transition} from 'react-transition-group';

function App(){
const [isProp, setInProp] = useState(false);
const nodeRef = useRef(null);

return (
<>
<Transition nodeRef={nodeRef} in={inProp} timeout={500}>
<h2>react-transition-group Transition component</h2>
</Transition>
<button onClick={() => setInProp(true)}>Click to Enter</button>
</>
)
}
阅读全文 »

React

React 18.3.1

设计思想

  • 变换, 设计 React 的核心前提是认为 UI 只是把数据通过映射关系变换成另一种形式的数据。同样的输入必会有同样的输出。这恰好就是纯函数。
  • 抽象, 需要把 UI 抽象成多个隐藏内部细节,又可复用的函数。通过在一个函数中调用另一个函数来实现复杂的 UI
  • 组合, 将两个或者多个不同的抽象通过组合再次抽象成一个抽象

不变性, 不直接操作数据源, 副本替换

  • 可以保持以前版本的数据完好无损, 并在以后重用它们
  • 使组件比较其数据是否已更改的成本非常低, 提高子组件的渲染性能

状态

  • 只有当在组件树中 相同的 位置渲染 相同的 组件时, React 才会一直保留着组件的 state

  • 在一般安全的情况下采用批处理方式处理 state 更新

  • 使用不可变的数据模型, 把可以改变 state 的函数串联起来作为原点放置在顶层

  • 状态不存在于组件内, 状态是由 React 保存的, React 通过组件在渲染树中的位置将它保存的每个状态与正确的组件关联起来

    • 这个变量是否通过 props 从父组件中获取,如果是,则不是一个状态
    • 这个变量是否在组件的整个生命周期中都保持不变,如果是,则不是一个状态
    • 这个变量是否可以通过其他状态(state)或者属性(props)计算得到,如果是,则不是一个状态
    • 这个变量是否在组件的 render 方法中使用,如果不在,则不是一个状态

不能直接声明 async 函数式组件

  • 返回类型不匹配, React 组件需要返回 React 元素(JSX), 而 async 函数默认返回一个 Promise, 导致 React 无法正确渲染组件
  • 生命周期和状态管理, 异步操作通常涉及到副作用, 这些不应该在组件的渲染阶段进行, 它们应该在组件的生命周期方法或者特定的钩子中
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
// state
let count = 0;
let prevCount = count;
// events
window.addEventListener("click", () => { count++ }, false);
// render
const render = () => {
document.body.innerHTML = count;
}
// diff
const reconcile = () => {
if (prevCount !== count) {
render();
prevCount = count;
}
}
// scheduler
const workloop = () => {
reconcile()
// window.scheduler.postTask(() => { //... })
// 为什么 react 官方不使用这个 API
// 因为 requestIdleCallback, scheduler.postTack 无法满足优先级调用,
// 有了优先级, 就可以知道当下什么任务最紧急
window.requestIdleCallback(() => {
workloop();
})
}
workloop();

并发 React

React 在底层实现上使用了非常复杂的技术, 如优先队列,多级缓冲. 使得 React 能够同时准备多个版本的 UI.

并发渲染的关键特性是渲染可中断, React 即使渲染被中断, UI 也会保持一致, 它会在整个 DOM 树被计算完毕前一直等待, 完毕后再执行 DOM 变更. 这样做, React 就可以在后台提前准备新的屏幕内容, 而不阻塞主线程. 这意味着用户输入可以被立即响应, 即使存在大量渲染任务, 也能有流畅的用户体验.

自动批处理

当 React 在一个单独的重渲染事件中批量处理多个状态更新以此实现优化性能. 没有自动批处理的话, 仅能够在 React 事件处理程序中批量更新.

在 React 18 之前, 默认情况下 promise、setTimeout、原生应用的事件处理程序以及其他任何事件中的更新都不会被批处理.

1
2
3
4
5
6
7
8
9
10
11
12
13
// React 18 之前: 只有 React 事件会被批处理
setTimeout(() => {
setCount(c => c + 1);
setFlag(f => !f);
// React 会渲染两次, 每次更新一个状态(没有批处理)
}, 1000);

// React 18 之后: React 事件, promise, setTimeout, 原生事件, 其他任何事件都被批处理
setTimeout(() => {
setCount(c => c + 1);
setFlag(f => !f);
// React 仅会重渲染一次(批处理);
}, 1000);
阅读全文 »

敲黑板

隐私协议

  • 2.32.3 开始支持

  • 2023-09-15 之后, 隐私相关功能默认开始, 对于未声明的处理用户信息的接口或组件直接禁用

  • wx.getPrivacySetting 查询微信侧记录的用户是否有待同意的隐私政策信息, 通过返回结果 res 中的 needAuthorization 字段获取, true 表示还没同意过

  • wx.openPrivacyContract 此接口打开 wx.getPrivacySetting 获取到的开发者在小程序管理后台配置的 《小程序用户隐私保护指引》名称信息的页面

  • wx.requirePrivacyAuthorize 模拟隐私接口调用, 并触发隐私弹窗逻辑

  • wx.onNeedPrivacyAuthorization 监听隐私接口需要用户授权事件, 当需要用户进行隐私授权时会触发. 触发事件时, 开发者需要弹出隐私协议说明,
    并在用户同意或拒绝授权后调用回调接口 resolve 触发原隐私接口或组件继续执行.

    • resolve 是一个函数, 主动调用触发原隐私接口或组件继续执行
    • eventInfo 表示触发本次事件的关联信息
1
2
3
4
5
6
7
8
<!-- 用户触发此组件时, 微信会同步收到同意信息 -->
<!-- 事件回调表示用户已同意隐私政策后的处理逻辑 -->
<button
open-type="agreePrivacyAuthorization"
bind:agreeprivacyauthorization="handleAgreePrivacyAuthorization"
>
同意
</button>
阅读全文 »

敲黑板

  • 开启 charles 无法代理 https, 检查电脑和手机上的 charles 证书是否过期

电脑删除证书

  1. 使用 win + r 键调起系统运行窗口, 在输入框中输入 mmc 命令后回车
    charles-11
  2. 在弹出的对话框选择 “文件” -> “添加或删除管理单元”
    charles-12
阅读全文 »

@babel/cli

install

1
npm i -D @babel/cli @babel/core @babel/preset-env

运行

参数

  • --watch | -w 监听文件改变自动编译
  • --out-file | -o 输出指定文件名
  • --out-dir | -d 编译整个目录
    • 编译目录下所有文件输出合并为一个文件
1
2
3
4
5
npx babel index.js -w # 编译并监听 index.js

npx babel index.js -o index.min.js # 编译 index.js 文件输出到 index.min.js

npx babel src -d dist # 编译 src 目录下文件输出到 dist 下
阅读全文 »

全局 API

全局 API 应用实例

  • Vue.config -> app.config
  • Vue.config.productionTip -> 移除
  • Vue.config.ignoreElements -> app.config.compilerOptions.isCustomElement
  • Vue.component -> app.component
  • Vue.directive -> app.directive
  • Vue.mixin -> app.mixin
  • Vue.use -> app.use
  • Vue.prototype -> app.config.globalProperties
  • Vue.extend -> 移除

全局 API Treeshaking

  • Vue.nextTick() -> nextTick()
  • Vue.observable -> reactive()
  • Vue.version -> version
  • Vue.compile(仅完整构建版本)
  • Vue.set(仅兼容构建版本)
  • Vue.delete(仅兼容构建版本)

模板指令

v-model

参见 vue3.md 的 v-model 指令部分

key 使用改变

对于 v-if, v-else-if, v-else 的各分支项 key 将不再是必须的, Vue 会自动生成唯一的 key

v-if 和 v-for 优先级

两者作用于同一个元素上时,v-if 会拥有比 v-for 更高的优先级

v-bind 合并行为

v-bind 的绑定顺序会影响渲染结果

  • vue 2.x 独立绑定的 attribute 会覆盖 v-bind 中的 attribute
  • vue 3.x 根据声明顺序决定如何被合并
1
2
3
4
5
6
7
8
9
<!-- 模板 -->
<div id="red" v-bind="{ id: 'blue' }"></div>
<!-- 结果 -->
<div id="red"></div>

<!-- 模板 -->
<div v-bind="{ id: 'blue' }" id="red"></div>
<!-- 结果 -->
<div id="red"></div>
阅读全文 »

状态码

  • 100 Continue 请求继续

  • 101 Switching Protocols 协议切换,通过 upgrade 消息头切换协议

  • 102 Processing 继续执行

  • 200 OK 请求成功

  • 204 No Content 没有响应实体

  • 206 Partial Content 服务器处理部分 GET 请求

  • 301 Moved Permanently 资源永久移动,请求方法可能会被客户端改写, 通常用于 GET 和 HEAD 请求的响应

  • 302 Move Temporarily 资源临时移动

  • 303 See Other 参见其他

  • 304 Not Modified 资源没有改动

  • 305 Use Proxy 使用代理

  • 307 Temporary Redirect 资源临时从不同的 URI 响应

  • 308 Permanent Redirect 永久重定向, 禁止客户端更改请求方法, 通常用于 POST 请求的响应

  • 400 Bad Request 错误的请求

  • 401 Unauthorized 需要验证

  • 403 Forbidden 服务器拒绝执行

  • 404 Not Found 资源未找到

  • 405 Method Not Allowed 请求方法不能用于相应资源

  • 408 Request Timeout 请求超时

  • 413 Request Entity Too Large 请求体超长

  • 415 Unsupported Media Type 不支持的媒体类型

  • 500 Internal Server Error 服务器错误

  • 502 Bad Gateway 网关错误

  • 504 Gateway Timeout 网关超时

  • 505 HTTP Version Not Supported 服务器不支持的使用的 HTTP 版本

URL 编码常用

  • %21 !
  • %22 “
  • %23 #
  • %24 $
  • %25 %
  • %26 &
  • %27 ‘
  • %28 (
  • %29 )
  • %2F /
  • %30-9 0..9
  • %3A :
  • %3B ;
  • %3C <
  • %3D =
  • %3E >
  • %3F ?
  • %40 @
  • %41 A

HSTS

HTTP Strict-Transport-Security, HTTP 严格传输安全 是一种安全策略机制, 旨在强制客户端仅通过 HTTPS 协议与服务器进行通信, 并且在未来的一段时间内, 所有对该站点的请求都必须使用 HTTPS.

通过在 HTTP 响应头中包含一个特殊的指令: Strict-Transport-Security 来实现.

浏览器接收到这个响应头, 会将该站点标记为 HSTS 站点, 并在指定的时间内自动将所有的 HTTP 请求转换为 HTTPS 请求。即使是手动输入的 http url 或者点击的一个 http 链接也会被自动升级为 HTTPS 请求.

部分主流浏览器维护了一个预加载的 HSTS 站点列表, 这些站点默认被强制使用 HTTPS, 无需等待首次访问后的响应头设置.

1
Strict-Transport-Security: max-age=<expire-time>; includeSubDomains; preload
  • max-age, 浏览器记录的只能使用 HTTPS 访问站点的最大时间(单位秒)
  • includeSubDomains, 可选, 表示该策略适用于主域名及其所有子域名
  • preload, 可选, 表示该站点希望被列入浏览器的 HSTS 预加载列表
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
$ curl -I https://www.douyin.com
HTTP/1.1 404 Not Found
Server: Tengine
Content-Type: text/plain; charset=utf-8
Connection: keep-alive
Date: Wed, 11 Dec 2024 09:11:02 GMT
cache-control: no-store
Vary: Accept-Encoding, Accept-Encoding
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
X-Download-Options: noopen
X-Content-Type-Options: nosniff
content-security-policy: upgrade-insecure-requests ;frame-ancestors https://pc.xgo.bytedance.net https://tcs.bytedance.net https://*.douyin.com https://aidp.bytedance.com https://aidp.bytedance.net https://www.aidp-cqc.com;script-src 'report-sample' 'strict-dynamic' 'nonce-4QE2wZ2rc-ztbxrA3IWHy' 'wasm-unsafe-eval' 'unsafe-eval' 'self' *.bytedance.com *.bytedance.net *.pstatp.com *.bytednsdoc.com *.bytescm.com *.douyin.com *.bytegoofy.com *.snssdk.com *.byted-static.com *.huoshanstatic.com *.douyinstatic.com *.ibytedapm.com *.zijieapi.com *.bytetos.com *.yhgfb-cn-static.com *.byteimg.com;report-uri https://i.snssdk.com/log/sentry/v2/api/slardar/main/?ev_type=csp&bid=douyin_web;report-to main-endpoint
Reporting-Endpoints: main-endpoint="https://mon.zijieapi.com/monitor_browser/collect/batch/security/?bid=douyin_web", default="https://mon.zijieapi.com/monitor_browser/collect/batch/security/?bid=douyin_web"
X-Tt-Logid: 20241211171102BFBC2AC0A24F4C05B24E
X-Agw-Info: N1yG692xcEzUcPRkqur4q1w8MlUT0hGFq0kNWMMV07yBP0rl8Ro_MQ8j_XpZ93UiGF_yYjjOLU0-LJkSol7AQbN5g-cwiWw79Mh3Nn_6upuBUoABSTZXiw5xsf5QXhFwSLwtY4xBUmCBdBQIjt7NUQ==
Server-Timing: inner; dur=276,tt_agw; dur=264
Set-Cookie: ttwid=1%7Ct5T1yUApy2aoqf8tihUlKDs27fKNe5akRSMvf5zx5nk%7C1733908262%7C209a31899a105d3c878e20feea33411d21a34e256860ec7250eec309baab71e4; Domain=.douyin.com; Path=/; Expires=Sat, 06 Dec 2025 09:11:02 GMT; HttpOnly
Set-Cookie: UIFID_TEMP=92559570181449f7274d01beb9fdcc50ede300fee49b9150f96a9b11ec20d282f33b1ce26fee42ba9461631433a50fa5108eb52097c37c530c81c602f7edb85602265a215753110a5ddc46f4e991edf4; path=/; expires=Sat, 25 Apr 2026 09:11:02 GMT; domain=douyin.com; samesite=none; secure
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Access-Control-Allow-Credentials: true
x-tt-trace-host: 011ff03de9066d57c184f3d11219de2a9150664283b7d9b81d900f399ddf465413a8c909a66ed35f5f7f27db6607d80ca8d1103636a7e5a90d5b92105a23ba0862ae5cdac345035fa6422cb605c9b9dddb594f35c29470ce5c159141f9bdd32f9a
x-tt-trace-tag: id=03;cdn-cache=miss;type=dyn
server-timing: cdn-cache;desc=MISS,edge;dur=0,origin;dur=303
x-alicdn-da-ups-status: endOs,0,404
Via: live5.cn2073[303,0]
Timing-Allow-Origin: *
EagleId: 3db6831717339082625713776e

CSP

内容安全策略 是一个额外的安全层, 用于检测并消弱某些特定类型的攻击, 包括 跨站脚本(XSS) 和数据注入攻击等. 它通过定义哪些资源可以被加载和执行来限制网页的攻击面.

CSP 通过 HTTP 响应头或 HTML <meta> 标签传递给浏览器, 浏览器接收到这些策略后根据规则阻止不符合条件的资源加载或执行.

1
2
3
4
Content-Security-Policy: default-src 'self'; img-src *; 
media-src media1.com media2.com;
script-src userscripts.example.com;
report-uri /_/csp_reports
1
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src https://*; child-src 'none';" />
  • default-src, 默认策略, 适用于所有未明确指定的资源类型, 如果指定了其他指令将覆盖此值
  • script-src, 允许加载的 js 源, 可以指定域名、协议或路径, 或者使用关键字 self,unsafe-inline,unsafe-eval
  • style-src, 允许加载的 CSS 样式表源
  • img-src, 允许加载的图像源
  • connect-src, 允许通过脚本接口加载的 URL
  • font-src, 允许加载的字体源
  • object-src, 允许加载的插件对象源, 如 flash
  • frame-src, 允许嵌入的框架源
  • media-src, 允许加载的音频和视频源
  • child-src, (使用 frame-src 和 worker-src 代替)允许加载的子资源, 如 iframe 或 worker
  • form-action, 允许提交表单的目的URL
  • base-uri, 允许使用的 <base> 标签URI
  • sandbox, 启用类似沙盒的限制
  • prefetch-src, 允许预加载或预渲染的资源源
  • report-uri, 指定违反 CSP 时发送报告的 URL
  • report-to, 指定一个或多个组名, 用于接收 CSP 违规报告, 需要配合 Report-To HTTP 头使用

CSP 仅报告模式, 对任何违规行为将会报告一个指定的 URI 地址, 但不具有强制性

1
2
3
4
5
Content-Security-Policy-Report-Only: default-src 'self'; 
img-src *;
media-src media1.com media2.com;
script-src userscripts.example.com;
report-uri /_/csp-reports

HTTP cache mode

  • force-cache, 自己先在缓存中查找资源, 如果有不管是否过期直接返回
  • default, 自己先在缓存中查找资源, 然后验证资源是否过期, 如果过期再询问服务器资源是否过期
  • no-cache, 自己先在缓存中查找资源, 然后再询问服务器资源是否过期
  • reload, 不查看缓存, 直接从服务器获取资源, 然后使用下载的资源更新缓存
  • no-store, 不查看缓存, 直接从服务器获取资源, 并且不会更新缓存资源

MutationObserver

MutationObserver 接口提供监视对 DOM 树所做更改的能力, 用于替代 Mutation Events 的新 API, 与 Events 不同的是: 事件是同步触发, 即 DOM 发生变动会立刻触发相应事件, MutationObserver 则是异步触发, DOM 发生变动以后并不会马上触发, 而是要等到当前所有 DOM 操作都结束后才触发, 所有监听操作以及相应的处理都是在其他任务执行完成之后异步执行的, 并且在 DOM 更改触发之后,将更改记录存储在数组之中, 统一进行回调通知

构造函数

创建并返回一个新的 MutationObserver 实例, 会在指定的 DOM 发生变化时被调用

  • 参数 callback

    当被指定的节点或子树以及配置项有 Dom 变化时会被调用, 回调函数有两个参数:

    • MutationRecord 描述所有被触发改动的记录对象数组
    • MutationObserver 调用该函数的 MutationObserver 对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 创建一个观察器并传入回调函数
const observer = new MutationObserver(function (MutationRecord, observer) {
console.log(MutationRecord, observer);
// [{
// addedNodes: NodeList []
// attributeName: ""
// attributeNamespace: null
// nextSibling: null
// oldValue: ""
// previousSibling: null
// removedNodes: NodeList []
// target:
// type: "attributes"
// }]
});
// 指定观察变动的 DOM 节点和配置项
observer.observe(document.querySelector('#someElement'), {
subtree: true,
childList: true,
attributes: true,
});
// 停止观察器
observer.disconnect();
  • 返回值 MutationObserver 对象
阅读全文 »